[Objective-C] libextobjc の @weakify と @strongify について

ちょっと遅れた話題ですが、libextobjcライブラリを使用して、weak変数を使う方法があることを知ったので調査してみました。

これまでの記述法と問題点

これまでわたしは、この記事ARC+Blocks+llvm4.0時代のコード記述作法 | Zero4Racer PRO Developer’s Blog で書いたルールにしたがって、ivar(クラス内の変数)を基本的に使わず、block内で使用する場合に、weak化して使用する方法を使ってきました。WEAKSELFMAKE;というマクロを作成して、selfのマクロを作成しています。

#define WEAKSELFMAKE __weak typeof(self) wself = self

使用する時は、

{
WEAKSELFMAKE;
 SomeViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([SomeViewController class])];
controller.block_completed = ^ (NSNumber * toReturn) {
wself.numberValue = toReturn.copy;
[wself redlowItems];
}
}

のような感じです。これで結構シンプルに書けてほぼ問題がないのですが、スコープ内のstrongの変数を使う場合は、weak化を自分で行う必要がありました。ちなみにStoryBoardIDをクラス名にしてNSStringFromClass([SomeViewController class])で書くのが最近のお気に入りです。

{
WEAKSELFMAKE;
 SomeViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([SomeViewController class])];
__weak typeof(controller) wcontroller = controller;
controller.block_completed = ^ (NSNumber * toReturn) {
wself.numberValue = toReturn.copy;
[wself redlowItems];
[wcontroller dismissViewControllerAnimated:YES completion:NULL];
}
}

@weakify, @strongifyで出来ること

jspahrsummers/libextobjc は、Objective-Cを便利にする拡張機能ライブラリです。cocoapodで、
pod 'libextobjc', '~> 0.4'
を追加すれば簡単に追加出来ます。上のサンプルを書き直すと、

{
 SomeViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([SomeViewController class])];
@weakify(self);
@weakify(controller);
controller.block_completed = ^ (NSNumber * toReturn) {
@strongify(self);
@strongify(controller);
self.numberValue = toReturn.copy;
[self redlowItems];
[controller dismissViewControllerAnimated:YES completion:NULL];
}
}

こんな感じになります。self, controller というオリジナルと同じ変数名を使える代わりに、使う前に、@strongify(self)をしてあげないといけないのがポイントです。

動作原理

Objective-Cのコードに慣れていたらこれが若干気持ち悪いコードに見えます。selfだと循環参照になってしまう気がしてしまうからです。reactive cocoa – Explanation of how weakify and strongify work in ReactiveCocoa – Stack Overflow この記事には、マクロを展開した後のコードが載せられていて分かりやすいです。

@weakifyのマクロ展開
@weakifyのマクロ展開

これを見ると、@weakifyマクロで self_weak_ というweak変数を作って、@strongifyマクロでself=self_weak_;と新しいself変数を作成していることが分かります。同じ変数名(self)だったら、ローカルスコープのselfの方が優先されるため、selfを使っても循環参照しないんですね。

あと、@weakifyという、予約語を作っているように見えますが、これも強引で、後ろに autorelease (このスクリーンキャプチャの場合は try {} @finally{} 付けることによって、展開後に@autorelease という形になるようにして、無理矢理@予約語のように見せてるんですね。マクロでこんな強引なことが出来るんですね。これの残念なのは、Xcode上での予約された@propertyのようなものと実際には違うので、コード上の色がおかしくなることです。

@weakifyサンプル
@weakifyサンプル

まとめ

実際これをプロジェクト規約に取り込むかですが、若干微妙なところですね。@strongifyを忘れるとselfを使っているところで循環参照してしまうのと、2行使ってしまうのでコードが煩雑になるので、現在のWEAKSELFMAKE;マクロの方が綺麗な気もします。それで、展開したマクロのself_weak_を使ってあげれば綺麗になると思いました。

{
 SomeViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([SomeViewController class])];
@weakify(self);
@weakify(controller);
controller.block_completed = ^ (NSNumber * toReturn) {
self_weak_.numberValue = toReturn.copy;
[self_weak_ redlowItems];
[controller_weak_ dismissViewControllerAnimated:YES completion:NULL];
}
}

これで、@strongifyを書き忘れることもないですし、selfを内部で使っていた時に色で見分けるのも簡単になります。しばらくこれで使ってみようと思います。

参考

cocoapods – libextobjc
libextobjc/extobjc at master · jspahrsummers/libextobjc
Safx: libextobjcの@strongifyと@weakifyについて
Objective-C – weakify/strongify マクロを使うと weak self パターンが簡単に書ける – Qiita

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください