Blocksの罠

プログラムのコーディングについて書くのはプロである以上責任もあるのであんまり書かないようにしてるんですよね。
でもちょっと気をつけなきゃいけない内容があったので書いてみたいと思います。

以下のコードを見て下さい。

@interface MyViewController () {
	NSInteger myInteger ;
}

@property (nonatomic, retain) NSMutableArray* myArray ;

@end

@implementation MyViewController

- (void)awakeFromNib {
	[super awakeFromNib] ;
	
	self.myArray = [NSMutableArray array] ;

	NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter] ;
	
	NSLog(@"%d", self.retainCount) ;
	
	[defaultCenter addObserverForName:@"MyNotification"
							   object:nil
								queue:nil
						   usingBlock:^(NSNotification *note) {
		myInteger = 0 ;
		[_myArray addObject:@"aaaa"] ;
	}] ;
	
	NSLog(@"%d", self.retainCount) ;
}

- (void)dealloc {
	NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter] ;

	[defaultCenter removeObserver:self] ;

	self.myArray = nil ;

	[super dealloc] ;
}

// 以下省略
@end

一見なんてことないViewControllerの初期化と終了のコードですが、これdeallocが呼ばれません。
2箇所のNSLogの出力を見るとselfの参照カウンターが1つ上がっています。

このソース、結論から言うと「循環参照」と言う状態になっています。
なぜaddObserverForNameの前後でカウンターが上がるかの理由は後で説明するとして、addObserverForNameが理由でselfの参照カウンターが上がってるとするならばおそらくremoveObserverで減るでしょう。しかし、addObserverForNameで上がっているために正しいタイミングでdeallocが呼ばれずremoveObserverも呼ばれない。

つまりこのViewControllerは画面から消えてもメモリ上に消されずに残ってしまいます。

この現象はBlocksの罠(とでもいいましょうか)なのです。

Blocksはコピーされる時、内部のオブジェクトの参照カウンターをすべて1つ上げます。

え?でもこの中でselfは使ってないじゃないか?

と言いたくなるかも知れませんが、暗黙に使われているのです。

myInteger = 0 ;
[_myArray addObject:@"aaaa"] ;

これは暗黙に

self->myInteger = 0 ;
[[self myArray] addObject:@"aaaa"] ;

と言うコードが呼ばれており、バッチリselfを使っています。
なのでaddObserverForNameの前後でBlockがコピーされる際に参照カウンターが1つ上がるのです。

これは結構な罠でしょ?だって呼ばれると期待してるはずのdeallocが呼ばれないのになかなか気が付きにくい。
(Blockで参照カウンターが上がることを知っていてもです。)

ではこれ、どうやって回避しましょうか?
ちゃんと方法があり、こう書きます。

	__block typeof(self) blockSelf = self ;
	
	[defaultCenter addObserverForName:@"MyNotification"
							   object:nil
								queue:nil
						   usingBlock:^(NSNotification *note) {
		blockSelf->myInteger = 0 ;
		[blockSelf.myArray addObject:@"aaaa"] ;
	}] ;

まず参照カウンターが上がらないBlock変数でselfを定義します。ちょっと変わった書き方ですが、typeofで変数宣言できます。これでself型の変数を定義できます。
宣言したBlock変数を使えばselfの参照を避けることができます。メンバ変数へのアクセスも「->演算子」でできます。

もちろん関数内で一時的に終わるBlock(UIViewのanimationとか)は気にしなくても大丈夫です。あくまでこれは相互参照の可能性がある部分でのことです。NSNotificationCenterなんかはいい例なのではないでしょうか?(もちろんviewWillAppearとviewWillDisappearで行うパターンもありますけど)

何にせよ、Blocksプログラミングを取り入れた時点で最終的にはdeallocにNSLogを書いてちゃんと呼び出したViewControllerが消えているかを確認するのがいいと思います。ちなみにこれは構文上間違ってないのでAnalyzeでも検出されません。

もしこの記事を読んで何か間違っていることや別のアプローチがあればコメントなりメールなりいただけたら幸いです。

しかし、いろんな言語をやってますが、やっぱりC++が最強ですね。
あの言語で表現できない世界はないと思ってます。

Objective-Cはよく言えば柔軟、悪く言えば緩い。
まぁ、嫌いじゃないですけどねw

PHP習得への道(2)

PHPはサーバーサイドで動作するスクリプト言語です。

コンパイル言語とスクリプト言語と言うくくりで分けると、スクリプト言語はそのタイミングで読み込みながら実行するので、ソースコードがそのままプログラムということになります(簡単にいうとね)。

さて、何で書く。

ソースコードって言ってみればただのテキストファイル。

僕はMacですが、Windows風に言うと「メモ帳」。これがあれば作成できる。

あ、その前に実行環境とかを整えないとね。
このあたりは僕は専門外なので良く分からないんですけど、大体のレンタルサーバーでは問題なくPHP5が動くと思います。

ちなみにMacですがPHPは初めから入ってます。読みこむように設定する必要がありますが、問題なく動作します。

PHPが動作するサーバーへメモ帳で作成した.phpファイルをアップしてブラウザで実行。

これはこれで面倒。
いい開発環境ってないだろうか?

DreamWeaver。確かにPHPのソースも書けるしFTPでアップロードもできる。しかしやっぱりウェブページ作成がメインのツールなのでちょっと面倒。

そこで紹介してもらったのがNetBeansという開発ツール。なんと無料。
いろんな言語が開発できるツールのようです。これは便利かもw

早速PHPの開発環境をインストール。FTPサーバーへのアップロードはもちろん、ローカルのPCでも実行してテストができたりとなかなか優れもの。編集もしやすいです。

開発環境や方法を整えるのって効率につながりますからね。常にいい方法を模索したいです。

えっと、PHP習得への道ですが、連載しようと思ってましたが実際にはもうガンガン作ってましてですね、ブログがかなり置いてきぼりです(笑)。

思い出しながら、困ったことや気がついたことや良かったことなんかを綴って行こうかと思います。

PHP習得への道(1)

僕はC言語の技術者です。で「スペシャリスト」だと思います。

「できる」って意味ではなく「特化してる」と言う意味で。
つまり、反対の言葉「ジェネラリスト」ではない、と言うことです。

そう、世の中「C言語でなんでも表現できる」と思っちゃうんですよ(笑)。
C言語っていうか、オブジェクト指向言語でね。世の中だいたい表現できると思います。
いつもそんなことを考えながら生活してます。物事をそういうふうに「設計」してみると面白いものです。

そんな「スペシャリスト」には逆にできないことが多いです。
僕はアプリケーションのインターフェイスや設計、プレゼンとか提案はすごい得意だけど、裏で動くシステムつまりサーバーサイドやデータベース系は苦手です。
と言うかあまりやってこなかったのもありますけどね。

フリーランスとは言えずっと出向して組織に属していたので、それぞれのスペシャリストがいたり、ジェネラリストがまとめてくれたりとずいぶん恵まれてました。自分で会社を起こして事業を始めてみてあらためてそういうチームで仕事をするということの大切さや重要性に気が付きました。

「これで何を作る」

ではなく

「何でこれを作る」

同じようで違う発想。

いろんな技術や手段があるなかで、実現方法がいろいろ多岐に渡る世の中になってきてます。
まさか数年前まで「クラウド」なんて言葉が出るなんて思わなかったわけだし、ウェブだってスタイルシートやJavascriptがこんなにメインになるとは思わなかった(ですよね?)。

コンパイル言語をあつかう僕らはスクリプト言語をバカにしてた記憶がありますが、今そんなこと言ったらいつの時代の人よ、と逆にバカにされそうです(^^;

前置きが長くなりましたが、サーバーサイドのスキルを一つ身につけたくて、ASP.NET、perl、PHPといろいろ触って見みた結果、PHPをちゃんと勉強して習得してみようと思いました。

サーバー側で何かが出来るとアイデアの幅がぐっと広がります。もちろん「餅は餅屋」、全部自分でやれると思っていない(思ってたらそれは驕り)ので、専門的な部分はその筋のスペシャリストに依頼。「何を依頼したらいいか?」を把握するためにもちゃんと勉強します。

(つづく)