Block的引用循環問題 (ARC & non-ARC)

  

Block實現原理

首先探究下Block的實現原理,因爲Objective-C是C語言的超集,既然OC中的NSObject對象實際上是由C語言的struct+isa指針實現的,那麼Block的內部實現估計也同樣,如下三篇Blog對Block的實現機制作了詳細研究:ide

雖然實現細節看着頭痛,不過發現Block果真是和OC中的NSObject相似,也是用struct實現出來的東西。這個是LLVM項目compiler-rt分析的block頭文Block_private.h頭文件中關於Block的struct聲明:svn

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 
struct Block_descriptor {  unsigned long int reserved;  unsigned long int size;  void (*copy)(void *dst, void *src);  void (*dispose)(void *); };  struct Block_layout {  void *isa;  int flags;  int reserved;  void (*invoke)(void *, ...);  struct Block_descriptor *descriptor;  /* Imported variables. */ }; 

咱們發現Block_layout中也有一個isa指針,像極了NSobject內部實現struct中的isa指針。這裏的isa可能指向三種類型之一的Block:函數

  • _NSConcreteGlobalBlock:全局類型Block,在編譯器就已經肯定,直接放在代碼段__TEXT上。直接在NSLog中打印的類型爲__NSGlobalBlock__。
  • _NSConcreteStackBlock:位於棧上分配的Block,即__NSStackBlock__。
  • _NSConcreteMallocBlock:位於堆上分配的Block,即__NSMallocBlock__。

爲何會有這麼多種類呢?首先來看全局類型Block,看例子:atom

1
2 3 4 5 6 7 8 9 10 11 12 
void addBlock(NSMutableArray *array) {  [array addObject:^{  printf("global block\n");  }]; }  void example() {  NSMutableArray *array = [NSMutableArray array];  addBlock(array);  void (^block)() = [array objectAtIndex:0];  block(); } 

爲何addBlock中添加到array中的Block屬於全局Block呢?由於它不須要運行時(Runtime)任何的狀態來改變行爲,不須要放在堆上或者棧上,直接編譯後在代碼段中便可,就像個c函數同樣。這種類型的Block在ARC和non-ARC狀況下沒有差異。spa

這個Block訪問了做用域外的變量d,在實現上就是這個block會多一個成員變量對應這個d,在賦值block時會將方法exmpale中的d變量值複製到成員變量中,從而實現訪問。指針

1
2 3 4 5 6 7 
void example() {  int d = 5;  void (^block)() = ^() {  printf("%d\n", d);  };  block(); }

若是要修改d呢?:code

1
2 3 4 5 6 7 8 9 
void example() {  int d = 5;  void (^block)() = ^() {  d++;  printf("%d\n", d);  };  block();  printf("%d\n", d); }

因爲局部變量d和這個block的實現不在同一做用域,僅僅在調用過程當中用到了值傳遞,因此不能直接修改,而須要加一個標識符__block int d = 5;,那麼block就能夠實現對這個局部變量的修改了。若是是這種block標識的變量,在Block實現中再也不是簡單的一個成員變量,而是對應一個新的結構體表示這個block變量。block的本質是引入了一個新的Block_byref{$var_name}{$index}結構體,被block關鍵字修飾的變量就被放到這個結構體中。另外,block結構體經過引入Block_byref{$var_name}{$index}指針類型的成員,得以間接訪問到Block的外部變量。這樣對Block外的變量訪問從值傳遞轉變爲引用,從而有了修改內容的能力。對象

正常咱們使用Block是在棧上生成的,離開了棧做用域便釋放了,若是copy一個Block,那麼會將這個Block copy到堆上分配,這樣就再也不受棧的限制,能夠隨意使用啦。例如:ip

1
2 3 4 5 6 7 8 9 10 11 12 13 14 
typedef void (^TestBlock)();  TestBlock getBlock() {  char e = 'E';  void (^returnedBlock)() = ^{  printf("%c\n", e);  };  return returnedBlock; }  void example() {  TestBlock block = getBlock();  block(); }

函數getBlock中聲明並賦值的returnedBlock,一開始是在棧上分配的,屬於NSStackBlock,若是是non-ARC狀況下return這個NSStackBlock,那麼其實已經被銷燬了,在函數中example()使用時就會crash。若是是ARC狀況下,getBlock返回的block會自動copy到堆上,那麼block的類型就是NSMallocBlock,能夠在example()中繼續使用。要在Non-ARC狀況下正常運行,那麼就應該修改成:作用域

1
2 3 4 5 6 7 
TestBlock getBlock() {  char e = 'E';  void (^returnedBlock)() = ^{  printf("%c\n", e);  };  return [[returnedBlock copy] autorelease]; }

Block中的循環引用問題

扯了這麼多,回到Block的循環引用問題,因爲咱們不少行爲會致使Block的copy,而當Block被copy時,會對block中用到的對象產生強引用(ARC下)或者引用計數加一(non-ARC下)。

若是遇到這種狀況:

1
2 3 4 5 6 7 8 9 
@property(nonatomic, readwrite, copy) completionBlock completionBlock;  //======================================== self.completionBlock = ^ {  if (self.success) {  self.success(self.responseData);  }  } };

對象有一個Block屬性,然而這個Block屬性中又引用了對象的其餘成員變量,那麼就會對這個變量自己產生強應用,那麼變量自己和他本身的Block屬性就造成了循環引用。在ARC下須要修改爲這樣:

1
2 3 4 5 6 7 8 9 
@property(nonatomic, readwrite, copy) completionBlock completionBlock;  //======================================== __weak typeof(self) weakSelf = self; self.completionBlock = ^ {  if (weakSelf.success) {  weakSelf.success(weakSelf.responseData);  } };

也就是生成一個對自身對象的弱引用,若是是倒黴催的項目還須要支持iOS4.3,就用__unsafe_unretained替代__weak。若是是non-ARC環境下就將__weak替換爲__block便可。non-ARC狀況下,__block變量的含義是在Block中引入一個新的結構體成員變量指向這個__block變量,那麼__block typeof(self) weakSelf = self;就表示Block別再對self對象retain啦,這就打破了循環引用。

相關文章
相關標籤/搜索