概述
闭包,可以捕获代码。block对象常量块外的属性 非标准c组成部分。
源码部分不粘了 - 主要证明两点 1 是个对象, 2 内部有一个结构体用来存自动变量。
自己的一份流水账笔记,囊括了基本block的全部行为。
三种block
NSConcreteGlobalBlock
如果引用的不是外部变量那么都是这种,即使引用的是全局、静态。
这种block 不需要保存变量,一种是内部的,一种是全局的,都是可以在block的生命周期内拿到的。
block 没引用需要保存的变量,就不会开辟栈空间, 它在全局区。
最开始考虑他是一段代码会不会直接存在text段,但他是个对象所以还应该是全局区。
NSConcreteStackBlock
变成这种类型,只有一个理由,那么就是block的结构体中保存了自动变量(全局、静态都不需要保存,而在block块内变量更是和block 一个生命周期 所以更不需要保存)。
内部有一个结构体,保存了自动变量,block依旧在栈上。
NSConcreteMallocBlock
当copy行为产生,block就会是这个类型的,而且会对所有内部持有的对象,引用计数+1。
这种block是对NSConcreteStackBlock的升级,把NSConcreteStackBlock这种block的结构体保存到了堆区。
如果这种block没有被持有,所有block中的对象也都不会增加引用计数。
在arc下
当一个block被外部强引用的指针引用 并且内部有一个对外部变量引用的条件下,他会产生一个copy行为,这个行为使它变成NSConcreteMallocBlock
只要有强引用的block 都会产生copy 行为。
但是当外部引用的指针是一个弱指针,那么它还是NSConcreteStackBlock 类型
如果block中没有引用外部变量,那么就会直接是个NSConcreteGlobalBlock 而不论外部引用的指针是不是强引用
在mrc下
由于手写内存管理,release retain, 所以强引用并没有参与指针的copy行为。
在mrc下 只有当block 引用了自动变量,并且对该block使用了 Block_copy() 这个block 才是NSConcreteMallocBlock 类型
如果引用了自动变量而没有copy行为,那么是NSConcreteStackBlock类型
所谓持有
arc下持有block是用一个强指针引用block,mrc下持有block是对block 使用 block_copy。 除此之外,block不会存在堆区
block stackBlock; {
NSInteger val = 10;
stackBlock = ^{ NSLog(@"%zd", val); };
}
stackBlock();
没崩溃,
分析 : {}不是一个函数调用,虽然执行了, 不会弹栈。
__block 的行为
我们一般说block对外部变量的使用 是一个捕获的行为。
是说值保存在block自己的一个结构体中,所以不能对这个值进行修改操作。
当使用__block的时候,其实是对外部对象进行了一个指针引用,也就是在block内部是对指针进行一个保存,对这个指针的一切操作都会影响外部(类似引用)。
mrc 下的 __block 行为
如果block 中引用了外部变量,那么即使该变量是被 __block 修饰的 该block 也是 NSConcreteStackBlock 类型,如果再被 Block_copy 修饰,那么它将会是NSConcreteMallocBlock 类型
arc 下的 __block 行为
block 的存储位置,基本取决于指针的强弱,而不取决于 引用的外部自动变量是否是 __block
一个现象
两个不同堆内存的block 可以共用 __block 修饰的自动变量
//测试两个block 之间 对 __block 修饰的自动变量的引用关系
//mrc 下,即使两个变量对不同的block copy到不同的堆内存,这两个block 对相同的通过 __block 引用的变量的访问地址都是一致的 , 这怎么实现的??? 难道是一张__block变量的表吗? 是不是通过__block 修饰后这个变量就不存在栈区了,而是全局区, 也不再栈区? 证明不是,即使通过__block 修饰,他也是在栈区。
mrc下 __block 修饰的自动变量在block中的内存管理方式。
NSObject *objc = [[NSObject alloc] init];
block copyObjc = Block_copy(^{
NSLog(@"%@", objc);
});
也即是执行完了 第三行,block内部结构体会对objc这个变量的值进行一个复制。
那么当block 被持有时,block对内部的访问到的对象也是有一个持有(retain) 的操作了。
那么也就是说在mrc下,这个复制的行为会导致这个对象的引用计数+1,
而当objc 是 __block 修饰的时候,引用计数就不会 +1 ,因为内部结构体存的是指针的引用。
arc 行为 arc 行为非常简单
当持有block的指针是一个强指针时 block 内访问到的所有外部对象都有强指针引用,即使block引用的外部变量是 被__block 指针引用修饰。
当持有block的指针是一个弱引用是 block 内访问到的所有外部对象都没有被强引用,甚至这个block都不是一个 mallocBlock,而是一个stackBlock
_NSConcreteGlobalBlock 全局的静态 block,不会访问任何外部变量。
_NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
_NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0 时会被销毁。
Block_copy 在mrc下有一个特殊行为
这个函数是用来把 block 放到堆区的,那么也就是说,参数应该是存在栈内存上的一个block
如果参数是一个存在堆或者全局区的block 那么 这个Block_copy 函数的行为会使存在堆内存上的block的引用计数 + 1 。
如果block本身是 _NSConcreteGlobalBlock ,那么使用了 Block_copy 之后 她依旧在 _NSConcreteGlobalBlock
属性用copy 的行为
mrc
copy 直接对 block 使用 Block_copy 行为 , 也是系统推荐的 方式。
retain block 依旧保持原来类型, 引用计数不会增加
assign 依旧保持原来类型,引用计数不会增加
strong (mrc 下 可以用 strong 了, 是不是也就说 如果都是用 strong 那么mrc 和 arc 没区别了? 应该不是,因为arc mrc 是编译选项,编译时期只不过做了 添加 release retain 的操作,如果不 打开 arc, 那么就要自己添加了 retain release 了), 和copy 行为一样了,把block 放到堆内存了 , 并且block 类型也变成 malloc block 了。
arc 下
copy 和 strong 行为一致
因为block 是不可变对象,所以都只是增加了一个强引用而已,arc 下 block 只要有强引用并且内部引用了自动变量,他就在堆区。
weak 依旧保持block 原样, 没有copy行为。
一些细节
如果block在函数外部,那么存储在全局区。
包括block变量头 和 block对象常亮块。
多次执行block 也同样是一个block对象常量块,不会多次生成存储区域。
block在递归中的行为,是多次递归生成存储区域,因为栈内存没有被弹栈,而且之前的区域一直被占用所以会开辟多个存储区域。
官方文档 查 working 关键字,一般会是一些原理。
uiApplication 强引用着 Appdelegate 强引用着 window 强引用着 根控制器
block 使用场景
1.保存代码
// 1.在一个方法中定义,在另外一个方法调用
// 2.在一个类中定义,在另外一个类中调用
2.作为方法参数
- (void)Cacultor:(NSInteger(^)(NSInteger result))block;
注意点
arc 下,block会对内部使用到的所有强指针变量都强引用。
一般的处理是在外部把 需要引用到的对象,赋给一个弱引用再进行引用。
__weak typeof(self) weakSelf = self;
_block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf);
}); };
_block();
ARC:管理block
只要block引用外部局部变量,block放在堆里面
block使用strong.最好不要使用copy
为什么arc下block就放到堆区了呢? 因为有强引用。
如果block没有强引用, 那么依旧是 保存在全局区,如果引用到局部变量,依然保存在栈区。
MRC 下 block管理原则
使用copy来管理。
如果使用了retain ,那么block体(也就是对象) 还是存在栈区(如果内部使用到了需要保存的局部变量,否则block体存在全局区)。
因为block使用是一块代码,全局区。
但是当 使用到了 copy ,那么会把 block体 拷贝到堆区。
总结
block的管理原则 只有一个
当内部有需要保存的变量,那么block在栈区开辟空间保存临时变量。
如果没有需要保存的变量,那么保存在代码区。
当有强引用 或是 copy的时候,block 会存储在堆区。
一些写法
__weak ViewController *weakSelf = self;
self.retainBlock = ^ {
ViewController *strongSelf = weakSelf;
NSLog(@"%@", strongSelf);
};
只有当引用的外部对象是弱引用的情况下,block是 NSConcreteStackBlock,block 存在栈区,所以没有循环引用。