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声明:

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
<span class="line-number" style="margin: 0px; padding: 0px;">8
<span class="line-number" style="margin: 0px; padding: 0px;">9
<span class="line-number" style="margin: 0px; padding: 0px;">10
<span class="line-number" style="margin: 0px; padding: 0px;">11
<span class="line-number" style="margin: 0px; padding: 0px;">12
<span class="line-number" style="margin: 0px; padding: 0px;">13
<span class="line-number" style="margin: 0px; padding: 0px;">14
<span class="line-number" style="margin: 0px; padding: 0px;">15
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
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,看例子:

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
<span class="line-number" style="margin: 0px; padding: 0px;">8
<span class="line-number" style="margin: 0px; padding: 0px;">9
<span class="line-number" style="margin: 0px; padding: 0px;">10
<span class="line-number" style="margin: 0px; padding: 0px;">11
<span class="line-number" style="margin: 0px; padding: 0px;">12
</span></span></span></span></span></span></span></span></span></span></span></span>
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变量值复制到成员变量中,从而实现访问。

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
</span></span></span></span></span></span></span>
void example() {
int d = 5;
void (^block)() = ^() {
printf("%d\n", d);
};
block();
}

如果要修改d呢?:

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
<span class="line-number" style="margin: 0px; padding: 0px;">8
<span class="line-number" style="margin: 0px; padding: 0px;">9
</span></span></span></span></span></span></span></span></span>
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到堆上分配,这样就不再受栈的限制,可以随意使用啦。例如:

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
<span class="line-number" style="margin: 0px; padding: 0px;">8
<span class="line-number" style="margin: 0px; padding: 0px;">9
<span class="line-number" style="margin: 0px; padding: 0px;">10
<span class="line-number" style="margin: 0px; padding: 0px;">11
<span class="line-number" style="margin: 0px; padding: 0px;">12
<span class="line-number" style="margin: 0px; padding: 0px;">13
<span class="line-number" style="margin: 0px; padding: 0px;">14
</span></span></span></span></span></span></span></span></span></span></span></span></span></span>
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情况下正常运行,那么就应该修改为:

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
</span></span></span></span></span></span></span>
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下)。

如果遇到这种情况:

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
<span class="line-number" style="margin: 0px; padding: 0px;">8
<span class="line-number" style="margin: 0px; padding: 0px;">9
</span></span></span></span></span></span></span></span></span>
@property(nonatomic, readwrite, copy) completionBlock completionBlock;

//========================================
self.completionBlock = ^ {
if (self.success) {
self.success(self.responseData);
}
}
};

对象有一个Block属性,然而这个Block属性中又引用了对象的其他成员变量,那么就会对这个变量本身产生强应用,那么变量本身和他自己的Block属性就形成了循环引用。在ARC下需要修改成这样:

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
<span class="line-number" style="margin: 0px; padding: 0px;">8
<span class="line-number" style="margin: 0px; padding: 0px;">9
</span></span></span></span></span></span></span></span></span>
@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啦,这就打破了循环引用。

深入理解block的更多相关文章

  1. 对于block的理解,block的面试题

    1.block跟swift中的闭包(closure)基本一样,都常用于值的回调,特别是在多线程的网络请求回调中,使用起来极为方便. 2.block的开头是"^",接着是由小括号所报 ...

  2. 理解block和inode

    什么是block和inode? 定义:block就像是杯子 inode就像是杯子的编号,因为杯子太多了 1.根据文件的大小,在磁盘中储存时会占用一个或多个block:那么究竟多大的文件会使用一个blo ...

  3. 理解 block

    开始:Block 简介 Block 是 iOS 4.0 和 Mac OSX 10.6 引入的一个新特性. Block 可以极大的简化代码. 他们可以帮助你减少代码, 减少对代理的依赖, 并且写出更加简 ...

  4. 从汇编代码理解 Block 的内存结构

    ❓ 在断点调试 iOS 程序碰到 block 作为函数的形参时,如果想知道该 block 本身的函数签名信息和函数体地址时,有哪些办法?

  5. Block使用

    1.对block的理解 >  block是iOS4.0之后出现的,是仿照java中匿名函数所创造的,它是c级别的语法,效率比协议-代理高 >  block的是一个匿名函数(没有名字的函数) ...

  6. 理解 Cinder 架构 - 每天5分钟玩转 OpenStack(45)

    从本节开始我们学习 OpenStack 的 Block Storage Service,Cinder 理解 Block Storage 操作系统获得存储空间的方式一般有两种: 通过某种协议(SAS,S ...

  7. Block、委托、回调函数原理剖析(在Object C语境)——这样讲还不懂,根本不可能!

    开篇:要想理解Block和委托,最快的方法是搞明白“回调函数”这个概念. 做为初级选手,我们把Block.委托.回调函数,视为同一原理的三种不同名称.也就是说,现在,我们把这三个名词当成一回事.在这篇 ...

  8. Block介绍(二)内存管理与其他特性

    我们在前一章介绍了block的用法,而正确使用block必须要求正确理解block的内存管理问题.这一章,我们只陈述结果而不追寻原因,我们将在下一章深入其原因. 一.block放在哪里 我们针对不同情 ...

  9. Magento Block设计分析(深入分析)

    Magento中Block是一个很重要的组件,它在Block中充当非常重要的角色,下面我们来分析一下Magento中Block是怎样设计的,我们应该怎样使用这个重要的角色. 1.Magento Blo ...

随机推荐

  1. Spring Boot 1 创建Demo

    Spring Boot的主要优点: 为所有Spring开发者更快的入门 开箱即用,提供各种默认配置来简化项目配置 内嵌式容器简化Web项目 没有冗余代码生成和XML配置的要求 入门操作: 1.打开ht ...

  2. oracle恢复备份数据

    sqlplus链接数据库: 1.sqlplus 用户名/密码@IP地址/数据库名称 2.sqlplus 用户名/密码@数据库名称 注:第二种方法要在tnsnames.oRA文件中配置数据库名称 链接断 ...

  3. autorelease基本概念

    // //  main.m //  01-autorelease基本概念 // //  Created by apple on 14-3-18. //  Copyright (c) 2014年 app ...

  4. 数据块损坏(block corruption)

    分为物理损坏和逻辑损坏-物理损坏一般指数据块头部不可以访问.数据块校验值不合法,数据块格式不再是oracle承认的格式-逻辑损坏一般是在物理性结构完整的情况下,数据的内容在含义上不正确,比如保存了不允 ...

  5. Java基础之处理事件——使窗口处理自己的事件(Skethcer 1 handing its own closing event)

    控制台程序. 为表示事件的常量使用标识符可以直接启用组件对象的特定事件组.调用组件的enableEvent()方法,并把想要启用事件的标识符传送为参数,但这只在不使用监视器的情况下有效.注册监听器会自 ...

  6. 寻找第K大的数

    在一堆数据中查找到第k个大的值. 名称是:设计一组N个数,确定其中第k个最大值,这是一个选择问题,解决这个问题的方法很多. 所谓“第(前)k大数问题”指的是在长度为n(n>=k)的乱序数组中S找 ...

  7. 学习OpenCV——用OpenCv画漫画

    闲的时候用OpenCV画漫画也挺有意思,虽然效果不好(达不到上面所实现的效果), 参数需要调整,还是大头贴而且噪声小的图像比较合适 而且可以熟悉一下关于各种滤波的操作比如:双边滤波: #include ...

  8. PostgreSQL单机、同步复制、异步复制性能测试对比

    测试环境: •测试机:PC •内存:8GB •CPU:Intel(R) Core(TM) i5-3450 3.10GHz •硬盘:HDD •数据量:20GB •测试工具:pgbench •Postgr ...

  9. weiphp---------图灵机器人存在的bug。

    1.很多人下载下来weiphp源码以后,配置好了图灵机器人却不能使用.原因是因为他源码里面存在一个小bug 上图红色框框内是他的源码,问题就出在这里. 修改方法: if($result ['code' ...

  10. ACRush 楼天成回忆录

    楼教主回忆录: 利用假期空闲之时,将这几年 GCJ , ACM , TopCoder 参加的一些重要比赛作个回顾.首先是 GCJ2006 的回忆. Google Code Jam 2006 一波三折: ...