Block 循環引用(中)
不會造成循環引用的block
大部分GCD方法
1 dispatch_async(dispatch_get_main_queue(), ^{ 2 [self doSomething]; 3 });
因為self並沒有對GCD的block進行持有,沒有形成循環引用。目前我還沒碰到使用GCD導致循環引用的場景,如果某種場景self對GCD的block進行了持有,則才有可能造成循環引用。
block並不是屬性值,而是臨時變量
1 - (void)doSomething { 2 [self testWithBlock:^{ 3 [self test];4 }]; 5 } 6 7 - (void)testWithBlock:(void(^)())block { 8 block(); 9 } 10 11 - (void)test { 12 NSLog(@"test"); 13 }
這裏因為block只是一個臨時變量,self並沒有對其持有,所以沒有造成循環引用
block使用對象被提前釋放
有這種情況,如果不只是ClassA持有了myBlock,ClassB也持有了myBlock。
當ClassA被someObj對象釋放後
此時,ClassA對象已經被釋放,而myBlock還是被ClassB持有,沒有釋放;如果myBlock這個時被調度,而此時ClassA已經被釋放,此時訪問的ClassA將是一個nil對象(使用__weak
另一個常見錯誤使用是,開發者擔心循環引用錯誤(如上所述不會出現循環引用的情況),使用__weak
。比如
1 __weak typeof(self) weakSelf = self; 2 dispatch_async(dispatch_get_main_queue(), ^{ 3 [weakSelf doSomething]; 4 });
因為將block作為參數傳給dispatch_async時,系統會將block拷貝到堆上,而且block會持有block中用到的對象,因為dispatch_async並不知道block中對象會在什麽時候被釋放,為了確保系統調度執行block中的任務時其對象沒有被意外釋放掉,dispatch_async必須自己retain一次對象(即self),任務完成後再release對象(即self)。但這裏使用__weak
註:如果是在MRC模式下,使用__block
修飾self,則此時block訪問被釋放的self,則會導致crash。
該場景下的代碼
1 // ClassA.m 2 - (void)test { 3 __weak MyClass* weakSelf = self; 4 double delayInSeconds = 10.0f; 5 dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 6 dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 7 NSLog(@"%@", weakSelf); 8 }); 9 } 10 11 // ClassB.m 12 - (void)doSomething { 13 NSLog(@"do something"); 14 ClassA *objA = [[ClassA alloc] init]; 15 [objA test]; 16 }
運行結果
[5988:435396] do something [5988:435396] self:(null)
解決方法:
對於這種場景,就不應該使用__weak
來修飾對象,讓dispatch_after對self進行持有,保證block執行時self還未被釋放。
block執行過程中對象被釋放
還有一種場景,在block執行開始時self對象還未被釋放,而執行過程中,self被釋放了,此時訪問self時,就會發生錯誤。
對於這種場景,應該在block中對 對象使用__strong
修飾,使得在block期間對 對象持有,block執行結束後,解除其持有。
1 - (void)testBlockRetainCycle { 2 ClassA* objA = [[ClassA alloc] init]; 3 __weak typeof(objA) weakObjA = objA; 4 self.myBlock = ^() { 5 __strong typeof(weakObjA) strongWeakObjA = weakObjA; 6 [strongWeakObjA doSomething]; 7 }; 8 objA.objA = self; 9 }
註:此方法只能保證在block執行期間對象不被釋放,如果對象在block執行執行之前已經被釋放了,該方法也無效。
參考資料:block的循環引用問題
Block 循環引用(中)