Hatena::ブログ(Diary)

oct inaodu

 | 

2011-01-31

iPad、画像リソースのメモリリーク

iPadSafariは、2カ所から画像のメモリに接続するとリークしガベージされない。

iOS、3.2、4.2.1、どちらも起きる。


例えば、2つのimg要素が同じsrcを設定すると、その画像リソースはページのリロードまで2度と離されない。

以下のサンプルは、画像ロードが途中で停止し、以降は新しい画像を表示できない。

デスクトップブラウザのように、別のimg要素でプリフェッチして、他のimg要素で表示する手法は使えない。


// imagesにsrcのリストを用意
var elmImage = document.body.appendChild(document.createElement('img'));
var elmOther = document.createElement('img');
(elmImage.onload = function(){
	document.title = images.length;
	
	// 2カ所からリンク
	elmOther.src = elmImage.src;
	
	if(images.length)
		elmImage.src = images.shift();
})();

またLast-Modifiedなどで、ブラウザキャッシュし、そのリソースを使った時もリークする。

大抵のウェブサーバーは、この挙動だけど。

ブラウザキャッシュがちょうど一杯になるか越えるぐらいの画像を用意して、以下を2度実行すると、最初のサンプルと同様に途中で停止する。

(画像が多すぎるとサイクルしてキャッシュヒットしなくなり、少なすぎると停止するまでの量にならないので実験する場合は注意)

この挙動は、yubichizで同じ箇所を繰り返し表示させているとロードが停止する現象などで確認できる。


// imagesにsrcのリストを用意
// images = images.concat(images); // これで2周しても可
var elmImage = document.body.appendChild(document.createElement('img'));
(elmImage.onload = function(){
	document.title = images.length;
	
	if(images.length)
		elmImage.src = images.shift();
})();

いろいろテストする場合は、iPadの設定からSafariキャッシュをクリアし、Safari再起動してから行うこと。


関連

img要素をたくさん生成できない問題の回避方法。使い終わったら、srcを空にする(data uri使わなくても大丈夫だったとおもう)。

canvasを併用していろいろやる方法は基本的に遅い。

data uri(base 64)は、もっと遅い。

実行間隔を調整する


密に処理が実行されてしまうのを避けるため、二つのかたちがあるとのこと。

下のデモ。

throttleは、0.5秒ごとに点の位置が変わる。

debounceは、0.5秒いると赤くなる。



throttle

ひとつ目は一定間隔以内の呼び出しは間引いて無視する方法。

イベントの発生頻度が多く、処理が重い場合に使う。

Function.prototype.throttle = function(threshold, alt){
	threshold = threshold || 100;
	
	var me = this;
	var last = Date.now();
	return function(){
		var now = Date.now();
		if(now - last < threshold){
			if(alt)
				alt.apply(this, arguments);
			return;
		}
		
		last = now;
		me.apply(this, arguments);
	};
}

debounce

もうひとつは、一定間隔呼び出されなかったときに、はじめて処理を実行する方法。

補完や通信、ツールチップマウスアウトでメニューを閉じる場合などに使う。

Function.prototype.debounce = function(threshold){
	var me = this;
	var timeout;
	
	return function(){
		var self = this;
		var args = arguments;
		
		if(timeout)
			clearTimeout(timeout);
		
		timeout = setTimeout(function(){
			me.apply(self, args);
			timeout = null; 
		}, threshold || 100); 
	};
}
 |