35
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

phina.jsAdvent Calendar 2018

Day 5

safari(iOS12)で"Total canvas memory use exceeds the maximum limit"が出る場合の対処法

Posted at

phina.jsに限った話じゃないのですが、phina.jsを使っている時に遭遇した問題なのでphina.jsのアドベントカレンダーとして書いてみたりします。

#前置き

phina.jsで、結構大きめの画像を大量に扱うアプリケーションを作っていたのですが、iOS12のsafariでテストしていた時に、

"Total canvas memory use exceeds the maximum limit(288MB)"
スクリーンショット 2018-12-05 18.16.33.png

という警告が画像の読み込み時に発生する様になりました。
この警告が発生してからgetContext('2d')を実行すると必ずnullが返り、それ以降canvasからcontextの取得が出来なくなってしまいます。
288MBの部分は機種によっては255MBとかになるみたいです(iPhone Xは288MB)
翻訳すると、「総キャンバスメモリ使用量が上限を超えています」との事。

検索してみると、stackoverflowで外人さんが同じ現象で困っておりました。
親切な事にテスト用コードまで付けてくれています。
以下のコードがそれです(一部変更してます)

let ctxs = []
let counter = 0

// create a 1MB image
const createImage = () => {
    const size = 512

    const canvas = document.createElement('canvas')
    canvas.height = size
    canvas.width = size

    const ctx = canvas.getContext('2d')
    ctx.strokeRect(0, 0, size, size)
    return canvas
}

const createImages = n => {
    // create n * 1MB images
    for(let i = 0; i < n; i++ ){
        const canvas = createImage()
        ctxs.push(canvas)
    }
    console.log(`done for ${ctxs.length} MB`)
}

512x512で1MBなんですなー。初めて知りました。
このコードをsafariのデバッグコンソールに貼り付けてテストが出来ます。
試しに、createImages(288)を実行して、その後に、createImages(1)をやると・・・

error.png やはり同じ警告とエラーが・・・

###調べてみると
iOS11以前は、2151MBまで確保が出来た様です(手持ちのiPhone7(iOS11)調べ)
それがiOS12からは288MB。
もう少し調べてみると、WebKitのリポジトリに記述があり、iOS12からcanvasの最大使用量がRAMサイズの4分の1に縮小された模様です。
https://github.com/WebKit/webkit/commit/5d5b478917c685e50d1032ccf761ca53fc8f1b74#diff-b411cd4839e4bbc17b00570536abfa8f
余計な事しやがって
そもそもiOS11までは2分の1じゃない気がするんですが。

最初はsafariのバグじゃないかと思って放置今後のアップデートで直らないかなーと思ってたのですが、望みが薄そうなので、このメモリ量でやりくりするしかありません。
とりあえず、今まで使ってはそのままにしていたcanvasを解放してメモリを節約していきましょう。

###で、解放ってどうやんのよ
ブラウザではメモリの解放を手動で出来ないので、delete命令等でオブジェクトの参照を切って、あとは神の見えざる手(GC)に頼るしかありません。
不要になったcanvasをdelete canvasで削除してみます。


const deleteImages = () => {
  for( let i = 0; i < ctxs.length; i++ ){
      delete ctxs[i];
  }
  ctxs = [];
}

###これで大丈夫・・・
GCが動くと言ったな。アレは嘘だ。
deleteしても、nullを代入しても、参照は切れているのに、canvasの総メモリとしてはカウントされたままの模様です。

どうしようかと思って苦し紛れに試したのが以下の方法です。


const deleteImages = () => {
  for( let i = 0; i < ctxs.length; i++ ){
      ctxs[i].width = 0;
      ctxs[i].height = 0;
      delete ctxs[i];
  }
  ctxs = [];
}

deleteの前に、canvasのwidthとheightを0にしただけ。
これでいいのかよ、と思う様な解決方法。
恐らく、canvasだけに限った話ならdeleteも要らないんじゃないかと。

###解決はしましたが
とりあえずの方法なので、これが正しく安全な方法とは思えません。
stackoverflowで困ってた外人さんにもお伝えしたところ、この方法で問題の解決が出来た模様で、まぁ間違ってはいないみたいです。

なにか知ってる親切なお方がいましたら、コメントで教えてくれると非常にありがたいです。

35
22
1

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
35
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?