__block變量和對象
__block id obj = []; __block id __strong obj = []; 上述兩行代碼是等同的,ARC有效的時候,id類型以及對象類型變量必定會附加所有權修飾符,缺省為附有__strong修飾符。 看一下clang轉換的代碼: //__block變量用結構體部分 struct __Block_byref_obj_0 { void *__isa; __Block_byref_obj_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); __strong id obj; };
static void __Block_byref_id_object_copy_131(void* dst, void *src) { _Block_object_assign((char*)dst + 40, *(void**)((char*)src + 40), 131); }
static void __Block_byref_id_object_dispose_131(void *src) { _Block_object_dispose(*(void**)((vhar*)src + 40), 131); }
//__block變量聲明部分 __Block_byref_obj_0 obj = { 0, &obj, 0x2000000, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, [[NSObject alloc] init] };
當Block從棧賦值到堆時,使用_Block_object_assign函數,持有Block截獲的對象。 當堆上的Block被廢棄時,使用_Block_object_dispose函數,釋放Block截獲的對象。
只有__strong怎能沒有__weak呢? blk_t blk; { id array = [[NSMutableArray alloc] init]; id _weak array2 = array; blk = [^(id obj){ [array2 addObject:obj]; NSLog(@"array2 count = %d", [array2 count]); } copy]; } blk([[NSObject alloc] init]); blk([[NSObject alloc] init]); blk([[NSObject alloc] init]);
此時執行結果都是0. 這是因為 附有__strong修飾符的變量array在該變量作用域結束的同時被釋放、廢棄,nil被賦值在附有__weak修飾符的變量array2中。 如下同時__block __weak 雙修飾符: blk_t blk; { id array = [[NSMutableArray alloc] init]; __block id _weak array2 = array; blk = [^(id obj){ [array2 addObject:obj]; NSLog(@"array2 count = %d", [array2 count]); } copy]; } blk([[NSObject alloc] init]); blk([[NSObject alloc] init]); blk([[NSObject alloc] init]);
執行結果與之前相同。 因為即使附加了__block說明符,附有__strong修飾符的變量array也會在該變量作用域結束的同時非釋放廢棄,nil被賦值給附有__weak修飾符的變量array2中。
還有就是__autorelease 與 __block一起使用會發生編譯錯誤。
Block循環引用
當我們在Block使用__strong修飾符的對象類型自動變量,那麼當Block從棧復制到堆時,該對象為Block所持有。 這樣容易引起循環引用: typedef void (^blk_t)(void); @interface MyObject:NSObject { blk_t blk_; } @end
@implementation MyObject - (id)init { self = [super init]; blk_ = ^{NSLog(@"self = %@", self);}; return self; } - (void)dealloc { NSLog(@"dealloc"); } @end int main() { id o = [[MyObject alloc] init]; NSLog(@"%@", o); return 0; } 代碼中,dealloc一定未被調用。 MyObject類對象的Block類型成員變量blk_持有賦值為Block的強引用。 即MyObject類對象持有Block。 init實例方法中執行的Block語法使用附有__strong修飾符的id類型變量self。 並且由於Block語法賦值在了成員變量blk_中, 因此通過Block語法生成在棧上的Block此時由棧復制到堆。並持有所使用的self。 self持有Block,Block持有self~~~
為了避免循環引用,可聲明附有__weak修飾符的變量,並將self賦值使用。 id __weak tmp = self; blk_ = ^{NSLog(@"self = %@", tmp);};
此時,由於Block存在時,持有該Block的MyObject類對象即賦值在變量tmp中的self必定存在,因此不需要判斷變量tmp的值是否為nil。
下面代碼也會引起循環: @interface MyObject:NSObject { blk_t blk_; id obj_; } @end
@implementation MyObject - (id) init { self = [super init]; blk_ = ^{NSLog(@"obj_ = %@", obj_);}; return self; } @end Block中沒有self,也同樣截獲了self,引起循環。 其實在block語法中使用了obj_,其實就已經截獲了self: self->obj_。 與前面一樣,我們可以聲明__weak的臨時變量來避免循環引用。
我們還可以使用__block變量來避免循環引用: typedef void (^blk_t)(void); @interface MyObject:NSObject{ blk_t blk_; } @end @implementation MyObject - (id)init { self = [super init]; __block id tmp = self; blk = ^{ NSLog(@"self = %@", tmp); tmp = nil; }; return self; } - (void)execBlock{ blk_(); } - (void)dealloc{ NSLog(@"dealloc"); } @end
int main() { id o = [[MyObject alloc] init]; [o execBlock]; return 0; }
這裡並沒有引起循環引用,但是如果不調用execBlock實例方法,即不執行賦值給成員變量blk_的Block,便會循環引用並引起內存洩露。 如圖:
MyObject類對象持有Block Block持有__block變量 __block變量持有MyObject類對象。
通過執行execBlock實例方法,Block被執行,nil被賦值給__block變量tmp中。 因此__block變量tmp堆MyObject類對象的強引用失效。 如何避免?:
MyObject類對象持有Block Block持有__block變量。
比較一下 使用__block變量避免循環引用的方法和使用__weak修飾符及__unsafe_unretained修飾符避免循環引用: __block優點: 1、通過__block變量可控制對象的持有時間 2、在不能使用__weak修飾符的環境中不使用__unsafe_unretained修飾符即可
使用__block變量的缺點: 為避免循環引用必須執行Block。
但是當執行了Block語法,沒有執行Block路徑的時候,不能避免循環引用。 如果由於Block印發了循環引用時,根據Block的用途選擇使用__block變量、__weak修飾符或者__unsafe_unretained修飾符來避免循環引用。
但是當ARC無效時? 此時我們需要手動將Block從棧復制到堆,並且要釋放Block。 此時要使用copy和release。 在arc無效的時候,__block說明符被用來避免Block中的循環引用。這是由於當Block從棧賦值到堆時,若Block使用的變量為附有__block說明符的id類型或對象類型的自動變量,不會被retain。 若Block使用的變量為沒有__block說明符的id類型或對象類型的自動變量,就會被retain。 下列代碼在arc有效或者無效都會導致循環引用: typedef void (^blk_t)(void); @interface MyObject:NSObject{ blk_t blk_; } @end @implementation MyObject - (id)init{ self = [super init]; blk_ = ^{NSLog(@"self = %@", self);}; return self; } - (void)dealloc { NSLog(@"dealloc"); } @end
int main() { id o = [[MyObject alloc] init]; NSLog(@"%@", o); return 0; }
此時我們使用__block變量就可以避免:
__block id tmp = self; blk_ = ^{NSLog(@"self = %@", tmp);};
————2014/3/22 Beijing