主にEXTScopeの_@strongify
_と_@weakify
_を利用するために、iOSアプリケーションにlibextobjc( https://github.com/jspahrsummers/libextobjc )を統合し始めていますが、プロセスに深く入り込む前にいくつかの質問があります。
これは、これを処理する方法を検討しようと意図的に過度に複雑になっている例です。
_- (void)someMethod {
if (self.someBOOL) {
_someObjectInstanceVar = [Object objectWithCompletionHandler:^{
// self reference #1
if (self.someProperty) {
// self reference #2
[[Async HTTPRequest] sendAWithID:self.property.id completionHandler:^(void (^)(NewObject *newObject) {
// self reference #3
[self showViewWithObject:newObject handler:^{
// self reference #4
[self reloadData];
}];
}];
}
}];
else {
[[Async HTTPRequest] sendBWithID:self.property.id completionHandler:^{
// self reference #5
[self reloadData];
}];
}
}
_
私の理解では、非同期HTTPリクエストのような何かをしたい場合、_[self reloadData]
_のような完了ハンドラ参照selfの内部では、リクエストブロック自体ではないので強い/弱いで何もする必要はありません」完了ブロックを保持するため、保持サイクルに問題はありません。上記のコード例では、#5は保持サイクルが問題にならないケースだと思います。
主な関心事は、プロパティ/初期化パラメータとしてブロックを取るすべてのオブジェクトであり、ブロックプロパティを内部的に保持しています。 objectWithCompletionHandler
がインスタンス変数としてcompletionHandlerブロックを保持するsomeObject
メソッド内には、リークの原因になることがわかっているselfへの複数の参照があります。私の主な質問はそのような場合です。それを「安全」にするためにweakify
とstrongify
をどのように処理する必要がありますか?次のように、@ weakify呼び出しと@strongify呼び出しがそれぞれ1つあれば十分でしょうか。
_- (void)someMethod {
@weakify (self);
_someObjectInstanceVar = [Object objectWithCompletionHandler:^{
@strongify(self);
...
}
_
上記の@strongify(self)
参照は、自己参照#1、2、3、および4に使用するのに十分ですか、または内部で使用する新しい弱い/強い参照を取得する必要がありますか(動作しますか) sendAWithID
メソッドとネストされたreloadData
?
編集:質問がより意味を持ち、いくつかの構文エラーを修正するようにコードを修正しました。
@strongify
の仕組み@strongify
が呼び出された後、self
は異なるポインターアドレスになりますinsideブロックとは異なりますoutsideブロック。これは、@strongify
が毎回self
という新しいローカル変数を宣言するためです。 (これが-Wshadow
警告を抑制する理由です。この警告は、「ローカル変数が別のローカル変数をシャドウするたびに警告します。」) これらの関数の実装 を読んで理解する価値があります。したがって、名前が同じであっても、別々のstrong
参照として扱います。
@strongify
を使用するブロックを使用するたびに参照サイクルが作成されると仮定すると(これは正しくありません)、次のことができます。
- (void)someMethod {
if (self.someBOOL) {
@weakify(self);
_someObjectInstanceVar = [Object objectWithCompletionHandler:^{
@strongify(self);
// self reference #1
if (self.someProperty) {
@weakify(self);
// self reference #2
[[Async HTTPRequest] sendAWithID:self.property.id completionHandler:^(void (^)(NewObject *newObject) {
@strongify(self);
// self reference #3
@weakify(self);
[self showViewWithObject:newObject handler:^{
// self reference #4
@strongify(self);
[self reloadData];
}];
}];
}
}];
// etc…
}
ただし、@strongify
を初めて使用した後、は、self
がローカルのスタック変数を参照することに注意してください。これらは通常、定義されているスコープが終了すると破棄されます(プロパティに保存したり、ネストされたブロックで使用したりしない限り)。したがって、示したコードに基づいて、// self reference #1
の後にのみ必要です。
@weakify
および@strongify
をカバーする単体テスト を読むと、これらの関数の正しい使用法を明確にするのに役立ちます。
ブロックのネストされた各レベルで弱化/強化の複数のインスタンスが機能するかどうかの質問に答えるために、はい。ただし、最初の@strongify定義では、ブロック(およびその中にネストされているブロック)のすべての内部スコープに対して既にselfを定義しているため、これを行う必要はありません。
ただし、ブロックの有効期間が異なる場合は、ネストされたブロックごとに@strongifyを追加して、すべてのブロックが内部スコープに独自の保持サイクルを保持するようにします。
このケースを説明するgithub issueスレッドは次のとおりです。 https://github.com/jspahrsummers/libextobjc/issues/45
「self」によって保持されているブロック内で「self」を呼び出すと、「Retain Cycles」が発生し、メモリリークが発生します。理想的には次のようになります:
@interface A: NSObject // Some interface A
@property (nonatomic, copy, readwrite) dispatch_block_t someBlock; // See block is strongly retained here.
@end
*******************************************************
@implementation A
- (void) someAPI
{
__weak A * weakSelf = self; // Assign self to weakSelf and use it
// enter code here inside block to break retain cycles.
self.someBlock =
^{
A * strongSelf = weakSelf; // Assign weak self to strongSelf before
// using it. This is because weakSelf can go nil anytime and it may happen
// that only few lines from block get executed before weakSelf goes nil,
// and hence code may be in some bad state.
if (strongSelf != nil)
{
// Use strongSelf.
[strongSelf doSomethingAwesome];
[strongSelf doSomethingAwesomeAgain];
}
};
}
@end
ブロックが「自己」によって保持されていない場合、ブロック内で「自己」を使用しても安全であり、保持サイクルは作成されません。
注:「libextobjc」ライブラリを使用しても、メモリ管理の概念は変わりません。