block
简介Block块是封装工做单元的对象,是能够在任什么时候间执行的代码段。其本质上是可移植的匿名函数,能够做为方法和函数的参数传入,能够从方法和函数中返回。—(翻译自官方文档)markdown
块是对C语言的一种扩展,它并未做为标准的ANSI C所定义的部分,而是有苹果公司添加到语言中的。块看起来更像是函数,能够给块传递参数,块也能够具备返回值。函数
block
类型__NSGlobalBlock__
全局block,存储在全局区block
,block
没有入参,没有block
内部代码块只是简单的打印没有引用外界变量,则此时的block
类型是__NSGlobalBlock__
__NSStackBlock__
栈区block__NSStackBlock__
,外界变量处理以后底层会对block
进行拷贝从栈区拷贝到堆区。在ARC下,编译器作了不少的优化,每每看不到本质,上面的代码输出结果找不到,由于编译器对__NSStackBlock__
自动进行了copy操做。改成MRC
就能够看到不论是否对变量进行处理都会打印__NSStackBlock__
固然也能够用改成MRC方法: Build Settings 里面的Automatic Reference Counting改成NO。源码分析
__weak
修饰block
,不作强引用同样不会copy
因此此时打印的block
仍是栈区block
__NSMallocBlock__
堆区blockblock
底层拷贝以后变成堆区block
block
本质__main_block_impl_0
函数地址赋值给了block
此时咱们再看__main_block_impl_0
的结构 block
本质其实就是一个结构体,结构体中又存在着isa
,也能够说block
本质就是一个OC对象,一个封装了函数调用以及函数调用环境的OC对象FuncPtr
,并将block
做为入参传递block
如何捕获变量上文分析本质的时候写了一个简单的block没有任何入参,也没有使用外界变量,再写一个使用外界变量的block
,看看底层block
如何使用这些外界变量的,在外界定义一个变量a,而后在代码块中打印a,查看编译后的代码:
优化
block
结构体中多了一个同名的参数,初始化的时候将外界的变量赋值给这个同名变量,其中局部变量会生成一个变量进行值拷贝,全局变量不捕获变量直接使用外界变量,静态变量是是有一个同名变量指针,是指针拷贝__block
原理block
代码块中直接修改外界没有使用__block
修饰的变量时回报以下错误 此时咱们在使用
__block
修饰发现没有报错而且修改为功 经过clang查看编译后的代码
发现编译后
a
此时取得是地址不仅仅是简单的数值而且被转换成了__Block_byref_a_0
类型 经过编译后的代码发现
__Block_byref_a_0
是一个结构体,而后block
的结构体同时也多了一个__Block_byref_a_0
类型的指针因此底层a
是指针复制也就是和外界的a
变量指向了同一片内存地址,因此此时使用__block
修饰的变量可以修改为功ui
block
真正的类型Block_layout
block
的时候首先会走到objc_retainBlock
方法中去如图objc_retainBlock
,发现objc_retainBlock
方法里面会跳转到_Block_copy
方法中去,此时断点继续往下跟发现第一次会走进libobjc.A.dylib
库中的_Block_copy
方法,方法内部呢又跳转了一个_Block_copy
方法,一样的继续往下跟发现走进了libsystem_blocks.dylib
库中的_Block_copy
方法,此时咱们再冲源码中找block
的真正类型block
的真正类型是Block_layout
Block_layout
源码探索flags
标识说明
BLOCK_DEALLOCATING
,释放标记,-般经常使用BLOCK_NEEDS_FREE
作位与操做,一同传入Flags
,告知该block
可释放。BLOCK_REFCOUNT_MASK
,存储引用计数的值;是一个可选用参数BLOCK_NEEDS_FREE
,低16是否有效的标志,程序根据它来决定是否增长或是减小引用计数位的值;BLOCK_HAS_COPY_DISPOSE
,是否拥有拷贝辅助函数(a copy helper function);BLOCK_IS_GC
,是否拥有block
析构函数;BLOCK_IS_GLOBAL
,标志是不是全局block;BLOCK_HAS_SIGNATURE
,与BLOCK_USE_STRET
相对,判断当前block
是否拥有一个签名。用于 runtime
时动态调用。descriptor
说明:Block_descriptor_1
是必选的Block_descriptor_2
和 Block_descriptor_3
都是可选的Block_descriptor_2
和Block_descriptor_3
的构造函数 Block_descriptor_1
的内存地址平移得来block
三层拷贝分析注意:能出发三层拷贝的状况是,在block中捕获用__block修饰的对象的时候出发spa
_Block_copy
block
从栈区拷贝到堆区 block
是否须要释放,须要则不拷贝block
是则不拷贝aBlock
拷贝到result
result
_Block_byref_copy
__block
修饰的时候拷贝成Block_byref
结构体 从下文中的_Block_object_assign
源码分析知道若是是__block
修饰的对象会交给_Block_byref_copy
去处理此时咱们再看_Block_byref_copy
的源码 __block
修饰的变量在block
中可以直接修改对应的值,我看再看编译后的代码__block
修饰的对象转换成__Block_byref_person_0
类型再看__Block_byref_person_0
结构体 __Block_byref_id_object_copy
和__Block_byref_id_object_dispose
,暂时不知道多的这两个函数是怎么用的,这时候咱们再回到源码先看Block_byref
结构体 Block_byref
结构中是没有这两个函数的,可是Block_byref_2
恰好存在着两个函数分别是copy
和dispose
,再看_Block_byref_copy
函数的源码发现若是存在copy
和dispose
方法的时候会有调用copy
方法copy
的实现 _Block_object_assign
方法,此时传入的就是普通的对象交给系统arc去处理,而后作一个指针拷贝,至此这一层拷贝就找到了_Block_object_assign
__main_block_copy_0
和__main_block_dispose_0
方法里面的实现分别调用的是_Block_object_assign
和__main_block_dispose_0
,冲源码中也能够知道这两个方法分别对应着Block_descriptor_2
中的copy
和dispose
此时咱们再看_Block_object_assign
的源码实现 _Block_copy
去处理_Block_byref_copy
去处理该方法的源码分析可看面内容block
循环引用block
,而后还有个属性name
若是在block
中使用name
,那么在block
中就会只有当前这个对象(上文中也分析过了,此状况,block
内部结构体会添加一个一样的对象作持有当前对象,底层传入普通对象会进行指针拷贝而且引用计数加一)这就形成了相互持有循环引用。block
中须要使用self
中的属性直接使用__weak
就能够了,这样两个指针都指向同一片内存地址可是使用__weak
修饰不会形成引用计数加一。block
中又嵌套block
的状况 __weak typeof(self) weakSelf = self;
self.tdBlock = ^(void){
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
self.tdBlock();
复制代码
这个状况外面同样的使用__weak
去修饰,可是在第一个block
里面在使用__strong
去修饰。应为__strong
修饰的变量在代码块中是个局部变量,因此block
代码执行完成以后会自动释放,因此不会形成循环引用,为何要在block
中在使用__strong
修饰呢?主要是应为里面的block
是一个延时操做若是不是用__strong
修饰那么执行完析构函数weakSelf
就变成nil
了,因此在嵌套的block
,中拿到的weakSelf
,就是nil,此时在使用__strong
是为了延长weakSelf
声明周期,让其在嵌套的block
执行完成以后再销毁__block
修饰是利用了在block
内部能够修改变量的值的属性,一样的在定义block
的时候self
的引用计数会加一,可是在使用完成以后能够吧变量置为nil
,那么self
的引用计数又会减一,因此不会形成循环引用。(注意:这种方法block必需要调用若是不调用变量没法置空同样会形成循环引用)self
就会被做为临时变量压栈进来,因此就不会形成持有,也就不会形成循环引用(函数参数是存在栈区的,是由编译器自动分配和释放的)OC
是只能单继承的语言,可是它是基于运行时的机制,因此能够经过NSProxy
来实现 伪多继承,填补了多继承的空白NSProxy
和 NSObject
是同级的一个类,也能够说是一个虚拟类,只是实现了NSObject
的协议NSProxy
实际上是一个消息重定向封装的一个抽象类,相似一个代理人,中间件,能够经过继承它,并重写下面两个方法来实现消息转发到另外一个实例- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel