国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

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

2019-11-14 19:32:15
字體:
來源:轉載
供稿:網友

 

2010年WWDC發布iOS4時Apple對Objective-C進行了一次重要的升級:支持Block。說到底這東西就是閉包,其他高級語音例如java和C++已有支持,第一次使用Block感覺滿簡單好用的,但是慢慢也遇到很多坑。本文聊聊ARC和non-ARC下Block使用中的引用循環問題,最近遇到了好幾次這種問題,還是深入記錄下。先來套題目熱熱身,貌似能夠全部答對的人蠻少的

Block實現原理

首先探究下Block的實現原理,由于Objective-C是C語言的超集,既然OC中的NSObject對象其實是由C語言的struct+isa指針實現的,那么Block的內部實現估計也一樣,以下三篇Blog對Block的實現機制做了詳細研究:

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

123456789101112131415
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,看例子:

123456789101112
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情況下沒有差別。

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

1234567
void example() {  int d = 5;  void (^block)() = ^() {      printf("%d/n", d);  };  block();}

如果要修改d呢?:

123456789
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到堆上分配,這樣就不再受棧的限制,可以隨意使用啦。例如:

1234567891011121314
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情況下正常運行,那么就應該修改為:

1234567
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下)。

如果遇到這種情況:

123456789
@property(nonatomic, readwrite, copy) completionBlock completionBlock;//========================================self.completionBlock = ^ {        if (self.success) {            self.success(self.responseData);        }    }};

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

123456789
@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啦,這就打破了循環引用。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 黄大仙区| 湖北省| 绥中县| 保康县| 娱乐| 乌拉特中旗| 乌兰浩特市| 朝阳县| 民丰县| 青海省| 白玉县| 七台河市| 布尔津县| 乐业县| 红桥区| 福清市| 舟曲县| 根河市| 文安县| 临安市| 涟水县| 平凉市| 永康市| 灵武市| 敦煌市| 崇义县| 讷河市| 唐山市| 五指山市| 获嘉县| 辰溪县| 新泰市| 宿松县| 冷水江市| 海淀区| 江源县| 正定县| 全州县| 景德镇市| 高安市| 花莲县|