とりあえずお絵描きソフトが完成したものの、実際のiPhoneで動かすと遅い。遅すぎる。しかも図形を書いて行くうちにどんどん遅くなって行く。これはタッチが起きるごとに、[self setNeedsDisplay]によって画面の全領域を再描画するという効率の悪い点による。
 そこで、setNeedsDisplayInRectを用いて再描画を行う範囲をタッチが起きた周辺のみに限定するチューニングを行う。再描画のイメージを以下の図に示す。

doctornovaのブログ


このようにタッチが起きた場所(x,y)を中心として半径がradius(今までは5pixel)の円をすっぽり包むような領域を再描画領域とする。この領域の設定はCGRectMakeを使って、

rectToRedraw = CGRectMake(x - radius, y - radius, radius * 2, radius * 2);

のようにセットすれば良い。ここでrectToRedrawはmyCampusクラスのメンバとして保持しておくことにする。これを使ってタッチイベントが起きたときの再描画をsetNeedsDisplayから以下にかえる。

[self setNeedsDisplayInRect:rectToRedraw];

このメッセージでは引数としてrectを取り、drawRectメソッドにrectを渡してその領域のみを再描画するためのメッセージである。
 drawRectメソッドでは、まず受け取ったrectから中心位置の属するimageDataタイルのインデックスを求める。

tileToRedrawIX = CGRectGetMidX(rect) / tileWidth;
tileToRedrawIY = CGRectGetMidY(rect) / tileHeight;

ここで、CGRectGetMidXはrect型から中心の座標を得るための関数。tileWidthはimageDataタイルの幅。本ソフトでは4x8のタイルなので、tileWidthは4でtileHeightは8。さらに、さっきの図から再描画しなければならないimageDataタイルは最大でも上下2タイル隣、左右に3タイルとなるので、この領域のimageDataタイルのみを再描画する。一般的にはtileWidth, tileHeight, radiusを用いて、

redrawMarginX = (2 * radius) / tileWidth + 1;
redrawMarginY = (2 * radius) / tileHeight + 1;

と表される。よって再描画はimageDataの総当たりではなく次のようなループで行える。


for(iX = tileToRedrawIX - redrawMarginX; iX <= tileToRedrawIX + redrawMarginX; iX ++)
{
if(iX < 0 || iX >= numberOfTilesX) continue;
for(iY = tileToRedrawIY - redrawMarginY; iY <= tileToRedrawIY + redrawMarginY; iY ++)
{
if(iY < 0 || iY >= numberOfTilesY) continue;
if(imageData[iX][iY] == 0) continue;
for(iByteOffSet = 0; iByteOffSet < 32; iByteOffSet ++)
{
if((1 & (imageData[iX][iY] >> iByteOffSet)) == 1)
{
x = iX * tileWidth + iByteOffSet % tileWidth;
y = iY * tileHeight + (iByteOffSet / tileWidth) % tileHeight;
CGContextFillEllipseInRect(context,
CGRectMake(x - radius, y - radius, radius * 2, radius * 2));
}
}
}
}


これによって大幅にパフォーマンスが向上する。

以上の改良と各定数をdefineにして整理したソースコードを以下にあげておく。

チューニングされたmyCampus.hとmyCampus.mのソースコード