1. __weak使用
1.1 ARC以后引入了__weak
的概念來修飾Objective-C
對象,使用這個關鍵字修飾的對象,對象的引用計數不會+1,這個關鍵字和__unsafe_unretained
有些類似,只是在對象釋放的時候__weak
會將引用的對象置為nil
,而__unsafe_unretained
不會,這將會導致野指針的產生,所以一般情況下,我們一般不屬于強引用某個對象的時候,可以使用__weak
進行修飾,典型的例子就是代理.例如
class UICollectionView.h
@property (nonatomic, weak, nullable) id <UICollectionViewDelegate> delegate;
1.2 一個對象在聲明的時候,如果什么修飾符都不寫,默認是strong,就會導致引用計數加+1,當出了這個對象聲明的scope
,引用計數就會-1,如果引用計數為0,這個對象就會被釋放.
當一個強引用的對象被Block捕獲的時候,引用計數就會增加,當這個只有當這個Block被釋放的時候這個強引用的對象的計數器才會回到原先的大小.
例如
void (^block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
TestObj *obj = [[TestObj alloc] init];
NSLog(@"before block retainCount:%zd",[obj arcDebugRetainCount]);
block = ^(){
NSLog(@"TestObj對象地址:%@",obj);
};
NSLog(@"after block retainCount:%zd",[obj arcDebugRetainCount]);
}
block();
}
return 0;
}
打印的結果為:
DemoWeak[19005:6761339] before block retainCount:1
DemoWeak[19005:6761339] after block retainCount:3
DemoWeak[19005:6761339] TestObj對象地址:<TestObj: 0x602000006b30>
比較有意思的是引用計數并不是簡單的+1,而是加2,這是由于block在創建的時候在棧上,而在賦值給全局變量的時候,被拷貝到了堆上,證明如下:
NSLog(@"堆%@",[block class]);
NSLog(@"棧%@",[^(){NSLog(@"TestObj對象地址:%@",obj);} class]);
DemoWeak[19026:6763489] 堆__NSMallocBlock__
DemoWeak[19026:6763489] 棧__NSStackBlock__
由于Block對對象的強引用,導致如果這個Block一直不釋放,那么所強引用的對象也就無法釋放,這樣就會導致對象的dealloc方法無法執行,以前就遇到了這種對象不釋放,但仍然發送通知的情況,找了好久,解決這個問題也很簡單,我們只需要將Block將要強引用的對象,弱引用就可以了,代碼如下
void (^block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
TestObj *obj = [[TestObj alloc] init];
__weak TestObj *weakObj = obj;
NSLog(@"before block retainCount:%zd",[obj arcDebugRetainCount]);
block = ^(){
NSLog(@"TestObj對象地址:%@",weakObj);
};
NSLog(@"after block retainCount:%zd",[obj arcDebugRetainCount]);
}
block();
}
return 0;
}
打印結果如下
DemoWeak[19065:6774290] before block retainCount:1
DemoWeak[19065:6774290] after block retainCount:1
可以看到弱引用前后,對象的計數器并沒有什么改變.
剛開始學習Block的時候,總是覺得由于Block只是定義,而并沒有執行,所以想當然的以為Block并沒有對內部的引用對象有任何影響,知道看到了Block編譯后的代碼,下面就是編譯后,截取的部分代碼
編譯后.png
從圖片上可以看出block在聲明的時候,就已經調用了初始化函數,保存了Block所捕獲到的引用對象(注意,由于使用__weak,無法編譯出.cpp源文件,所以在編譯時候,我使用了__unsafe_unretained,除了上面說到他和__weak之間的區別,實驗的結果都是一樣的)
2. __strong使用
除了在Block外使用__weak
對對象進行弱引用,我們偶爾還需要在Block內部對弱引用對象進行一次強引用,這是由于, 僅用__weak所修飾的對象,如果被釋放,那么這個對象在Block執行的過程中就會變成nil,這就可能會帶來一些問題,比如,數組,字典的插入.
正確的做法是,在Block執行的開始,檢驗弱引用的對象是否還存在,如果還存在,使用__strong進行強引用,這樣,在Block執行的過程中,這個對象就不會被置為nil,而在Block執行完畢后,對象的引用計數就會-1,這樣就不會導致對象無法釋放.
Block從外界所捕獲的對象和在Block內部強使用__strong強引用的對象,差別就在于一個是在定義的時候就會影響對象的引用計數(理由就是上面編譯后的代碼),一個是在Block運行的時候才強引用對象,執行完畢還是會-1
一般情況下,只使用__weak就能滿足大部分的需求了,只有在多線程處理的時候,需要在Block使用下__strong修飾對象,因為,單個線程,要么執行Block的時候對象還沒有被置為nil,那么直到Block被執行完畢,這個對象都不會被釋放(釋放也是需要線程調用函數的不是?),但是在多線程的情況下,就可能造成,在執行上半部分代碼的時候,對象還在,而在執行下半部代碼的時候對象已經被釋放,下面是一個例子:
TestObj *obj = [[TestObj alloc] init];
__weak TestObj *weakObj = obj;
NSLog(@"before block retainCount:%zd",[obj arcDebugRetainCount]);
block = ^(){
NSLog(@"TestObj對象地址:%@",weakObj);
dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
for (int i = 0; i < 1000000; i++) {
// 模擬一個耗時的任務
}
NSLog(@"耗時的任務 結束 TestObj對象地址:%@",weakObj);
});
};
NSLog(@"after block retainCount:%zd",[obj arcDebugRetainCount]);
block();
打印結果:
DemoWeek[19247:6816518] before block retainCount:1
DemoWeek[19247:6816518] after block retainCount:1
DemoWeek[19247:6816518] TestObj對象地址:<TestObj: 0x602000006af0>
DemoWeek[19247:6816518] TestObj 對象已釋放
DemoWeek[19247:6816544] 耗時的任務 結束 TestObj對象地址:(null)
可以看到在耗時任務執行前對象還是存在的,只是在執行完畢了后,對象被釋放了,如果我們使用__strong修飾就可以避免這種情況
block = ^(){
__strong TestObj *strongObj = weakObj;
if(! strongObj) return;
NSLog(@"TestObj對象地址:%@",strongObj);
dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
for (int i = 0; i < 1000000; i++) {
// 模擬一個耗時的任務
}
NSLog(@"耗時的任務 結束 TestObj對象地址:%@",strongObj);
});
打印結果:
DemoWeek[19280:6819437] before block retainCount:1
DemoWeek[19280:6819437] after block retainCount:1
DemoWeek[19280:6819437] TestObj對象地址:<TestObj: 0x602000006b30>
DemoWeek[19280:6819464] 耗時的任務 結束 TestObj對象地址:<TestObj: 0x602000006b30>
DemoWeek[19280:6819464] TestObj 對象已釋放
總結:
__weak修飾的對象被Block引用,不會影響對象的釋放,而__strong在Block內部修飾的對象,會保證,在使用這個對象在scope內,這個對象都不會被釋放,出了scope,引用計數就會-1,且__strong主要是用在多線程運用中,若果只使用單線程,只需要使用__weak即可