「iOSデバッグ&最適化技法 for iPad/iPhone」のXcode 3.4.2補完PDF作ってまして…
 で、しばらくこっちが手つかずだったんですわ。

$テン*シー*シー-0
バハバグも作ってたでな

 テンプレート消えるし、UIApplicationMain関数の引数は変わるし、@autoreleasepool導入とかされちゃうし、サンプルソース使えばなんとか読み進められるけど、できれば本読みながらXcode試す形を保ちたいわけですよ。ちゅーわけで補完PDFを提供する事にしました。
 来週ぐらいには公開できそう。
 公開したとたんにWWDCでXcode更新されそうな気がしますがねえええ。

 そんなこんなで233回はXcode 3.4.2補完PDFの制作との並列処理でいくぜい。


 前回体験したとおり、並列処理ってのは、使い勝手にかなり影響を及ぼすわけです。
 「料理中は注文受け付けません」ってのと「料理中も注文受け付けます」の店じゃ、味によほどの違いがない限り、みんな後者の店を使いたいわけですよ。

$テン*シー*シー-1

 料理人と受け付け係がそれぞれ1人ずついればいんだけどね~。
 でもiPhone 4まではCPUが一つなんで、そのままだと「料理中は注文受け付けません」になっちゃうわけですよ。
 それが、その(230)の実装。

 んじゃ、なんで前回のような芸当がiPhone 4S以外でもできるのかというと、数ミリ秒単位で処理を中断、再開して「受け付け」、「料理」を切り替えながら実行させてるわけなんですわ。
 これを時分割っていいます。

$テン*シー*シー-2

 蛍光灯が1秒の間に60回近く点滅しているのに気づけないように、数ミリ秒間隔で切り替わる作業は、人間の目には同時進行のようにみえるわけです。

$テン*シー*シー-3

 当然、切り替えには、現在進行中の処理を後から再開するための準備等で、多少のオーバーヘッドがあるので、一つ一つを単独で処理するより実際は時間がかかる事になる。

$テン*シー*シー-4

 だけど、そのロスを考えても、人間にとって並列処理の方がはるかに使い勝手がいいわけです。

 でもって、最近だとiPhone 4Sに代表されるように、CPUが実質2つ以上ある機種が主流になってきてるんで、上のような時分割による疑似並列処理じゃなくてモノホンの並列処理になったりもするんですよ。

注)CPUとしては1つなんだけど、内部で同時に動作できる処理装置(コア)が2つ以上あるんですな。インテルのCore 2 コメントで修正(感謝):Core 2 DuoのDuoってネーミングはそこから来とります。あと、2つ以上のCPUやCPUコアがあっても、時分割して担当させます。でないと、コア数以上の処理を同時におこなわせたりの応用がきかなくなる。

 どんな間隔で時分割にするか、どのCPUコアを割り当てるか等のスケジューリングはOSがめんどうみてくれます。
 プログラマは
並列同時処理を希望します。
並列で同時におこないたい処理はこれです。

という2点をOSに伝えればいいわけです。

 これが、前回の

$テン*シー*シー-5

だったわけですな。
 これで_baseviewのloadingimagesメソッドが並列処理されるわけです。

 てなわけで、並列処理はアプリケーションの使い勝手を向上させるために、積極的に使うべき機能だというのは確かなんですが、私の書いた2冊の本(デバッグ本、ドリル本)では扱ってません。
 というのも、並列で処理する以上、どうしても共有する資源(メモリとか通信装置とか)を協調しながら使わないと駄目なんで、ターゲットアクションパターンや通知、メモリ管理も知らない状態で手を出すと大やけどしちゃうんですな。

 例えば、今回の場合、たまたま最初のトップ画面にサムネイル画面を出してるわけだけど、これをドリルダウンした先で使うように変更し、トップ画面とサムネイル画面を行ったり来たりしたらどうなるか?

$テン*シー*シー-6

 こいつを試すためにトップ画面用のビューコントローラ(TestViewControllerと命名)用意して、そこからサムネイル画面用ビューコントローラのThumbnailViewControllerを作って呼び出すようにしてみます。

 別ファイルにするのめんどくさいので、ThumbnailViewAppDelegate.mにこんな感じで実装。

#import "ThumbnailViewAppDelegate.h"
#import "ThumbnailViewController.h"


@interface TestViewController : UIViewController
@property (retain) NSArray* images;
@end

@implementation TestViewController
@synthesize images;

- (void)dealloc
{
[images release];
[super dealloc];
}

- (void)viewDidLoad
{
[super viewDidLoad];
UIButton* button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button setTitle:@"サムネイル画面にいく" forState:UIControlStateNormal];
[button addTarget:self action:@selector(goThumb)
forControlEvents:UIControlEventTouchUpInside];
button.frame = CGRectMake(10, 100, 300, 44);
[self.view addSubview:button];
}

- (void)goThumb
{
ThumbnailViewController* thumbnailViewController
= [[[ThumbnailViewController alloc] initWithNibName:@"ThumbnailViewController"
bundle:nil] autorelease];
thumbnailViewController.images = self.images;
[self.navigationController pushViewController:thumbnailViewController animated:YES];
}
@end

@implementation ThumbnailViewAppDelegate

@synthesize window = _window;
@synthesize viewController = _viewController;
  ・・・
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self buildDummys];
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];


NSArray* images = [self images];
TestViewController* testViewController = [[[TestViewController alloc]init] autorelease];
testViewController.images = images;
UINavigationController* navigationController
= [[[UINavigationController alloc] initWithRootViewController:testViewController]
autorelease];

self.viewController = navigationController;

// Override point for customization after application launch.
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}

 あとThumbnailViewAppDelegate.hのUIViewControllerプロパティもワーニング消すためにThumbnailViewControllerやめてUIViewControllerに変更。ま、気にならん人は放置プレイでもOK。
@property (strong, nonatomic) UIViewController *viewController;

 なにやってるかわからない人は、ちょっと古いけどiPhoneアプリ開発ドリル 目次(UIViewController)を読むか、ドリル本のQ27~Q29を読むべし(ジュンク堂は座って読めるよ)。



 これで、"サムネイル画面にいく"ボタンを押すとサムネイル画面に進み"Back"ボタンで元に戻るようになる。
 でもって、この行ったり来たりを3回くらい実行。
 何が起こるかというと、ThumbnailViewControllerの-viewDidLoadメソッドはドリルダウンされるたびに呼び出されるので、そのたびに並列処理として-loadingimagesが実行されるわけですよ。
 便宜上、GUIを受けもつ大元の処理側をメイン並列処理、-loadingimages側を-loadingimages並列処理と呼ぶと、以下のように、どんどん-loadingimages並列処理が増えていくんですな。

$テン*シー*シー-7

 よりハッキリさせるために-loadingimagesメソッドで
printf("%p\n", self);

として、実行中の処理がどのオブジェクトに属する処理か表示させときます。
 でもって、
[NSThread sleepForTimeInterval:1];

とする事で、1秒間処理を強制的に停止させます。これで1画像に1秒使うようになる。
- (void)loadingimages
{
int tag = 1;
for (NSString* filepath in _images) {
   ・・・
[self performSelectorOnMainThread:@selector(setimage:)
withObject:dic waitUntilDone:NO];

printf("%p\n", self);
[NSThread sleepForTimeInterval:1];
}
}

 Runしてサムネイル画面とトップ画面を3回行き来させたコンソール出力がこれ。

$テン*シー*シー-8

 一度トップ画面に戻った時点で、1番目の-loadingimages並列処理が対象としている画面はディスプレィ上から切り離される。
 でもって次にサムネイル画面開いた時は、別の画面オブジェクトなわけで、それは2番目の-loadingimages並列処理が担当してるわけですよ。
 その時点で、1番目の-loadingimages並列処理が対象としてる画面は2度とディスプレィ上に現れることはないので、2番目の処理の足を引っ張ってるだけなんですな。
 でもって、またトップ画面に戻って、再度サムネイル画面開いたら3番目の-loadingimages並列処理が動き出して、1、2番目が足を引っ張り続けると…

 さすがにこれはCPU資源の無駄遣いなわけです。

 トップ画面に戻った時点で、1番目の-loadingimages並列処理は終了するべきなんだけど、どの並列処理が、どのタイミングで終わるべきかなんてのはOS側では判別しようがないんで、アプリケーションがなんとかしないと駄目なんですな。

 で、てっとりばやく対応するにはインスタンス変数を使って、ループを解除させる方法があります。

@interface ThumbTileView : UIScrollView {
NSArray* _images; // サムネイル元画像ファイルパスの配列

BOOL _wasCancelled;
}
@end
  ・・・
- (void)loadingimages
{
int tag = 1;
for (NSString* filepath in _images) {
   ・・・
[self performSelectorOnMainThread:@selector(setimage:)
withObject:dic waitUntilDone:NO];
printf("%p\n", self);
[NSThread sleepForTimeInterval:1];

if (_wasCancelled) {
break;
}
}
}
  ・・・

- (void)stop
{
_wasCancelled = YES;
}

  ・・・
@implementation ThumbnailViewController {
UIActivityIndicatorView* _activityIndicatorView;
ThumbTileView* _baseview;
}
@synthesize images;

- (void)dealloc
{
[_baseview stop];
[super dealloc];
}

  ・・・

 これで、即座に止まるわけではないけど、少なくとも何度行ったり来たりしても、-loadingimages並列処理は基本1つだけになり、まずまず実用的に不要な処理を止められるわけですよ。

 ただ~、このままだとサムネイル画面に行くたびに、いちから読みなおしなわけで、あんまり効率的じゃないんですな。

 サムネイル用の画像を管理する部分を別のオブジェクトとして、ThumbTileViewから取り外し、共有するべきなんですよ。
 
 で、まあ、ついでにスクロールして表示されたサムネイルから優先的に作りたいとか、巨大な数の画像ファイルを扱えるように、表示されている部分の上下数画面分以外はメモリに置かないようにしたいとか言い始めると、並列処理ならではのめんどくささが出てくるわけです。

 それは次回。

------------
サンプルプロジェクト:ThumbnailView-5.zip

 ちなみに並列プログラミングは、Appleのドキュメントそのものずばり「並列プログラミングガイト」てのに詳しく説明されてます。ただしスレッドとかタスクとか、いきなりラッシュされるんで、あ、ちょと覚悟はしておけ。

日本語ドキュメント