LoginSignup
17
18

More than 5 years have passed since last update.

WebView+Canvas vs ネイティブ パフォーマンス比較 iOS編

Posted at

WebView+Canvas+JavaScriptとネイティブのパフォーマンスを比較するために、実験を行ってみました。
非常に大雑把な比較になりますので、その点はご容赦ください。
Simulator Screen Shot 2016.02.29 11.37.23.png

検証方法
→色のついた正方形を複数画面内でランダムな方向に移動させる。WebViewとネイティブでそれぞれ正方形の数を変化させて、パフォーマンスの推移を測定する。

検証環境
→Xcode7.2.1 iPhone6 Plus iOS9.2.1

WebView+Canvas+JavaScript側の実装
→CanvasとJavaScriptのロジックを含むindex.htmlをバンドルし、UIWebViewで表示させる。20ミリ秒間隔で、fillRect関数を用いて複数の正方形を描画する。WKWebViewは原因不明の理由によりロジックがうまく動作せず、またパフォーマンスが表示されないため用いない。

ネイティブ側の実装
→UIViewで複数の青い正方形を作りself.viewにaddSubViewする。20ミリ秒間隔で、正方形の座標を変更する。

結果
スクリーンショット 2016-02-29 11.05.36.png
Nは正方形の数です。
スクリーンショット 2016-02-29 11.05.51.png
スクリーンショット 2016-02-29 11.06.01.png
予想通りネイティブの方がパフォーマンスは上ですが、WebViewの方がどうしようもないほどパフォーマンスが劣っているわけではなさそうです。
なお、ネイティブの方はN数が大きくなるとCPUの使用量が頭打ちになりますが、その分fpsを落として対応しているようです。WebViewの方は、N数が増えても確認できる範囲ではCPUの消費量は頭打ちになりませんでした。そのため、N数が多いとiPhoneが発熱するので少々危険かもしれません。

画像を用いたり、色数を増やしたりすると結果が異なってくるとは思うのですが、大雑把にWebView+Canvas+JavaScriptとネイティブのパフォーマンス差が把握できたかと思います。
Webの普遍性を優先するのか、ネイティブのパフォーマンスを優先するのか判断する際の一助になればと思います。

Androidでも同様の実験を行ってみたいですが、機種差が激しそうですね。

以下に、今回用いたコードを記述します。
→WebView + Canvas + JavaScript

index.html
<!DOCTYPE html>

<html>
<head>
  <title>-Experiment-</title>
</head>

<body style="padding:0px;margin:0px;">
  <canvas id="main_canvas"></canvas>
  <script type="text/javascript">

  var canvas = document.getElementById("main_canvas");
  canvas.style.width = window.innerWidth + 'px';
  canvas.style.height = window.innerHeight + 'px';
  canvas.style.margin = "0px";
  canvas.style.transform = "translate3d(0, 0, 0)";
  canvas.width = window.innerWidth * window.devicePixelRatio;
  canvas.height = window.innerHeight * window.devicePixelRatio;
  var context = canvas.getContext("2d");

  var balls = [];
  var speed = canvas.width * 0.002;
  var size = canvas.width * 0.05;
  for (var i=0; i<1000; i++) {
    var angle = Math.PI*2*Math.random();
    var ball = {
      size:size,
      position:{x:canvas.width/2, y:canvas.height/2},
      speed:{x:speed*Math.cos(angle), y:speed*Math.sin(angle)},
      border:{left:size/2, right:canvas.width-size/2, top:size/2, bottom:canvas.height-size/2},
    };
    balls.push(ball);
  }

  context.fillStyle = "#0000ff";

  var timer = setInterval(function(){

    context.clearRect(0, 0, canvas.width, canvas.height);

    for (var i=0; i<balls.length; i++){
      var ball = balls[i];
      ball.position.x += ball.speed.x;
      if (ball.position.x < ball.border.left) {
        ball.position.x = ball.border.left;
        ball.speed.x = Math.abs(ball.speed.x);
      };
      if (ball.position.x > ball.border.right) {
        ball.position.x = ball.border.right;
        ball.speed.x = -Math.abs(ball.speed.x);
      };
      ball.position.y += ball.speed.y;
      if (ball.position.y < ball.border.top) {
        ball.position.y = ball.border.top;
        ball.speed.y = Math.abs(ball.speed.y);
      };
      if (ball.position.y > ball.border.bottom) {
        ball.position.y = ball.border.bottom;
        ball.speed.y = -Math.abs(ball.speed.y);
      };

      context.fillRect(
        Math.round(ball.position.x-ball.size/2),
        Math.round(ball.position.y-ball.size/2),
        Math.round(ball.size),
        Math.round(ball.size)
      );
    }
  } , 20);

</script>
</body>
</html>
ViewController.swift
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        let url = NSBundle.mainBundle().pathForResource("index", ofType: "html");
        let reqURL = NSURL(string: url!)
        let req = NSURLRequest(URL: reqURL!)
        let webView = UIWebView(frame: CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height))
        webView.backgroundColor = UIColor.yellowColor()
        self.view.addSubview(webView)
        webView.loadRequest(req)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

→ネイティブ

ViewController.swift
import UIKit

class ViewController: UIViewController {

    var balls:[Ball] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        let speed = self.view.frame.size.width * 0.002
        let size = self.view.frame.size.width * 0.05
        for var i=0; i<1000; ++i{
            let ball = Ball()
            ball.frame = CGRectMake(0, 0, size, size)
            ball.center = self.view.center
            let rand = CGFloat(arc4random_uniform(UINT32_MAX)) / CGFloat(UINT32_MAX)
            let angle = CGFloat(M_PI*2)*rand
            ball.speed = CGVectorMake(speed*cos(angle), speed*sin(angle))
            ball.border = Border(
                left:size/2,
                right:self.view.frame.size.width-size/2,
                top:size/2,
                bottom:self.view.frame.size.height-size/2
            )
            ball.backgroundColor = UIColor.blueColor()
            self.view.addSubview(ball)
            balls.append(ball)
        }

        NSTimer.scheduledTimerWithTimeInterval(
            0.02,
            target: self,
            selector: "doFrame",
            userInfo: nil,
            repeats: true
        )
    }

    func doFrame(){
        for var i=0; i<balls.count; ++i {
            let ball = balls[i]
            ball.center.x += ball.speed!.dx
            if ball.center.x < ball.border?.left{
                ball.center.x = ball.border!.left!
                ball.speed?.dx = abs(ball.speed!.dx)
            }
            if ball.center.x > ball.border?.right{
                ball.center.x = ball.border!.right!
                ball.speed?.dx = -abs(ball.speed!.dx)
            }
            ball.center.y += ball.speed!.dy
            if ball.center.y < ball.border?.top{
                ball.center.y = ball.border!.top!
                ball.speed?.dy = abs(ball.speed!.dy)
            }
            if ball.center.y > ball.border?.bottom{
                ball.center.y = ball.border!.bottom!
                ball.speed?.dy = -abs(ball.speed!.dy)
            }
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}
Ball.swift
import UIKit

class Ball: UIView {
    var speed:CGVector?
    var border:Border?
}

struct Border {
    var left:CGFloat?
    var right:CGFloat?
    var top:CGFloat?
    var bottom:CGFloat?
}
17
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
18