[讀書筆記]iOS與OS X多線程和內存管理 [Blocks部分-4]

2.3.5 __block變量存儲域
Block變量從棧複製到堆時對__block變量產生的影響

__block變量存儲域
影響
從棧複製到堆並被Block持有
被Block持有
在多個Block中使用__block變量時,第一個Block從棧複製到堆時,__block變量也一併從棧複製到堆。在之後的Block從棧複製到堆時,__block變量被Block持有,__block變量引用計數增加。之前__block變量結構中的__forwarding能夠做到“不管__block變量在棧上還是堆上都能訪問”,原因是棧上的__block變量在複製到堆上時,他的成員變量__forwarding的值替換爲複製後堆上的__block變量的地址。

2.3.6 截獲對象
看下面代碼:

    typedef  void (^aBlock)( id);
   
aBlock ablock;
    {
       
id array=[[NSMutableArrayalloc]init];
        ablock=^(
id objct){
            [array
addObject:objct];
           
NSLog(@"array count %d",[arraycount]);
        };
   
    }
    ablock([[
NSObjectalloc]init]);
    ablock([[NSObjectalloc]init]);
     作者寫到:“執行該代碼後,程序會強制結束,array隨變量作用域的結束而被廢棄”。但在我的實際運行中,array對象正常使用,程序也正常運行(Xcode5.1.1和Xcode6.1)。所以可能蘋果已經修改這方面的機制,作者關於截獲對象的結論不再列出,使用時截獲對象的規則可參照截獲自動變量的規則。
     這一節提到_Block_object_assign函數和_Block_objec_dispose函數,前者相當於retain方法,將對象賦值在對象類型的結構體成員變量中(例如上面的代碼中將一個對象加入到array中);後者相當於release方法,釋放賦值在對象類型的結構體成員變量中的對象(如從array中釋放某個object)。
以下情況Block會從棧複製到堆:
  • 調用Block的copy實例方法
  • Block作爲函數返回值
  • 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時
  • 在方法名含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中傳遞Block時。

除以下情景外,推薦調用Block的copy實例方法(即如果系統已自動調用,我麼就不需調用):
  • Block作爲函數值返回時
  • 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時
  • 在方法名含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中傳遞Block時。



2.3.7 __block 對象和變量
此節沿用上節結論,略過

2.3.8 Block循環引用

Person.m文件代碼

主程序代碼:
{
...
   Person *per=[[Personalloc]init];
    NSLog(@"o");
...
}
執行結果是該源碼中的dealloc方法未被調用.原因是,Person類的對象持有blk_,blk_中持有id類型變量self.並且由於Block語法賦值給成員變量blk_(此時爲ARC模式,變量默認爲__strong類型),所以棧上的Block複製到堆,並持有所使用的self,這就造成了循環引用。編譯器也給出了警告。
爲避免循環引用可使用__weak修飾變量,並將self賦值給變量使用。如下:

-(id)init{
   
self=[superinit];
   
id __weak tmp=self;
   
blk_=^{
       
NSLog(@"self %@",tmp);
    };
   
return self;
}
在該代碼中,由於當Block存在時,持有此Block的Person對象肯定存在(blk_屬於Person對象),所以不需要對tmp進行判空。
在iOS4及更低版本中,沒有ARC機制,使用__unsafe_unretained修飾符代替__weak修飾符。
下面的代碼也會造成循環引用,因爲Block語法內使用的obj屬於對象的成員變量,想要截獲對象的成員變量,自然也截獲了對象本身self.
//arc打開狀態
@interface Person(){
   
void(^blk_)(void);
   
id obj;
}
@end
@implementation Person
-(
void)dealloc{
   
NSLog(@"person dealloc");
}
-(
id)init{
   
self=[superinit];
   
blk_=^{
       
NSLog(@"obj %@",obj);
    };
   
return self;
}

使用__block變量也能避免循環引用,如下代碼:
//arc打開狀態
@interface Person(){
   
void(^blk_)(void);
}
@end
@implementation Person
-(
void)dealloc{
   
NSLog(@"person dealloc");
}
-(
id)init{
   
self=[superinit];
   
__block id tmp=self;
   
blk_=^{
       
NSLog(@"obj %@",tmp);
       tmp=
nil;
    } ;
   
return self;
}
-(
void)execBlock{
   
blk_();
}

該代碼沒有引起循環引用,但是如果不調用execBlock這個方法,即不執行對tmp的賦值,就會引起循環引用。
  • Person類對象持有Block;
  • Block持有__block變量
  • __block持有Person類對象
通過執行execBlock,__block變量tmp對Person類對象的強引用失效。

使用__block變量避免循環引用有如下優點,
  • 通過__block變量可控制對象的持有時間
  • 在不能使用__weak修飾符的環境下代替__unsafe_unretained修飾符,不必擔心懸垂指針。
缺點是爲避免循環引用必須執行Block。

2.3.9 copy/release

ARC無效時,需要手動將Block從棧複製到堆,用copy方法來複制,release方法來釋放。只要Block位於堆上,則可以通過retain方法持有,對於棧上的Block調用retain方法不起任何作用。在c語言中也可以使Block,此時使用Block_copy和Block_release代替copy/release實例方法。另外,ARC無效時,__block說明符被用來避免Block循環引用,這是因爲Block從棧複製到堆時,若Block使用的變量爲附有__block說明符的id類型對象或對象類型自動變量,不會被retain,反之如果沒有__block說明符則會被retain。
在ARC有效和無效時,__block說明符的用途大不一樣,請注意。


Block部分到此結束。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章