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的实现机制做了详细研究:

  • A look inside blocks: Episode 1
  • A look inside blocks: Episode 2
  • A look inside blocks: Episode 3

虽然实现细节看着头痛,不过发现Block果然是和OC中的NSObject类似,也是用struct实现出来的东西。这个是LLVM项目compiler-rt分析的block头文Block_private.h头文件中关于Block的struct声明:

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

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情况下没有差别。

这个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呢?:

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到堆上分配,这样就不再受栈的限制,可以随意使用啦。例如:

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啦,这就打破了循环引用。

转载于:https://www.cnblogs.com/yjg2014/p/5077566.html

Block的引用循环问题 (ARC non-ARC)相关推荐

  1. 李洪强iOS经典面试题36-简单介绍 ARC 以及 ARC 实现的原理

    李洪强iOS经典面试题36-简单介绍 ARC 以及 ARC 实现的原理 问题 简单介绍 ARC 以及 ARC 实现的原理. 考查点 ARC 是苹果在 WWDC 2011 提出来的技术,因此很多新入行的 ...

  2. [Object-C]_[初级]_[关于块block的引用外部变量的规则]

    场景 在开发 Object-C 程序时, 很多情况下会用到它的块 block 特性, 这个 block 其实就是 lambda 表达式. 这个 block 和 lambda有什么区别, 还有什么需要注 ...

  3. 防止iOS中私有属性在block中的循环引用

    想看答案可以直接瞅瞅底下代码. 对于一般的@property修饰的属性我们可以使用__weak转换一下self来修饰 __weak typeof(self) weakSelf = self;//然后把 ...

  4. Copy(定义,特点,深复制,浅复制)(非ARC,ARC的运用范围)

    什么是copy? Copy的字面意思是"复制","拷贝",是一个产生副本的过程. 作用:利用一个源对象产生一个副本对象. 特点: 1,修改源对象的属性和行为,不 ...

  5. iOS Block弱引用

    先weak再strong.可以很好的管理Block内部对self的引用 常规写法 __weak typeof(self) weakSelf = self;self.Button.rac_command ...

  6. ESP32 LVGL8.1 ——arc 圆弧 (arc 19)

    提示:本博客作为学习笔记,有错误的地方希望指正 文章目录 一.arc 简介 1.1概述 Overview 1.2部分和风格 Parts and Styles 1.3使用 Usage 1.3.1 值和角 ...

  7. linux怎么执行arc文件,arc文件扩展名,arc文件怎么打开?

    .arc 文件类型1:Nintendo Archive File 文件说明:Archive file used by Nintendo games; contains game properties, ...

  8. Xcode非ARC项目转ARC,ARC项目中支持非ARC也就是共存

    1. Xcode非ARC项目转ARC 选中工程>Edit > Refactor > Convert to Objective -C ARC 然后就是下一步,save保存  eable ...

  9. 百度地图得引用 循环打印坐标

    map () {let that = this;//创建Map实例var map = new BMap.Map("XSDFXPage");// 初始化地图,设置中心点坐标var p ...

最新文章

  1. torch nll_loss
  2. 统计学要学的计算机课程有哪些,统计学专业主要课程学什么_课程设置安排及分类...
  3. 工作几年的感想(一)
  4. java中软填空面试题,通过这9个Java面试题,就可以入职华为啦
  5. 涨姿势 | 一文读懂备受大厂青睐的ClickHouse高性能列存核心原理
  6. 免费电子书:Azure Web Apps开发者入门
  7. [原創]全面的權限控制方法(功能權限+數據權限+特殊權限(行,列))
  8. hdfs java 权限管理,HDFS的权限管理
  9. python图片压缩算法_Optipng,jpegoptim应用,用python实现图片压缩,让你的网站变得更快...
  10. [C++11] 智能指针
  11. 你真的会玩SQL吗?透视转换的艺术
  12. Spark在Ubuntu中搭建开发环境
  13. 360极速浏览器插件不见了
  14. IDEA前进后退快捷键设置
  15. 关于用LaTeX写英文论文
  16. php 2038,php处理大于2038年以后日期的一种方法
  17. 阿里云Elasticsearch搜索
  18. 浏览器无法启动百度网盘应用的解决办法
  19. windows10 脚本获取超级管理员权限
  20. 昆仑通态mcgs通过西门子200PLC200smart通讯

热门文章

  1. 十年千篇!人脸识别相关技术最全论文合辑
  2. 深度学习(七十二)ssd物体检测
  3. Bezier(贝塞尔)曲线的轨迹规划在自动驾驶中的应用(一)
  4. Java日志框架-logback的介绍及配置使用方法(纯Java工程)
  5. Mysql 数据 导入 导出
  6. pdfbox java.lang.outofmemoryerror_java - PDFBox 2.0.1挂起渲染pdf页面 - 堆栈内存溢出
  7. 教育部计算机考研大纲,2018考研大纲从哪里看?
  8. 动物行为检测计算机视觉_基于红外热成像和计算机视觉的动物行为研究系统便是其中一例...
  9. php post请求 下载文件,POST请求 下载文件
  10. CountDownLatch/CyclicBarrie用法记录