6.三种 Block 本体
到这个阶段,我们用C的结构编译的代码以及源码能看到Block结构体内部的isa指针是指向_NSConcreteStackBlock的,其实这只是其中的一种,分别还有_NSContreteGlobalBlock 和 _NSContreteMallocBlock,可以根据命名的后缀看出来StackBlock是设置在栈上的,GlobalBlock就类似全局变量,设置在程序的数据区域(.data区域),那么最重要的也是我们写OC代码的时候根本不关注的一种类型NSContreteMallocBlock,没错,他就是和对象一样分类内存块(堆)中。
{ NSConcreteGlobalBlock; // 在全局中定义的(数据区域) NSConcreteStackBlock; // 在局部定义的(栈) NSConcreteMallocBlock; // 分配在堆中 }
NSConcreteGlobalBlock 全局定义
上面我们说的都是 NSConcreteStackBlock 在局部定义的(栈),下面我们来看看 NSConcreteGlobalBlock
你可以把它理解为全局变量,反正存储在.data区域的,最直接得写法是这样的
void (^block)(void) = ^{}; int main(){ }
Clang 转换过后的源码指针impl.isa = &_NSContreteGlobalBlock类型的
由于在使用全局变量的地方不能使用局部变量,这么说来就根本不存在对局部变量的捕获。那么这个Block的结构体实例的内容压根不会再进行追加成员变量,所以不会依赖于执行状态,所以整个程序运行只有一个实例。因此将Block使用的结构体实例设置在与全局变量相同的数据区域即可。(把它理解为单纯的全局变量就好了,而且不会有任何值的捕获)
存在的两种案例
- 全局变量的地方,用这种Block语法时,如上面所示
- Block语法中表达式不截获任何局部变量时,这个稍后Demo介绍,也很简单
NSContreteMallocBlock 堆定义
配置在全局的GlobalBlock可以出了作用域还是能继续访问,但是在栈上的StackBlock就废弃了,因此为了出了作用域能继续使用,Blocks提供了把Block和__block这两个东西从栈上复制到堆上的方法来解决这个问题。而_forwarding其实既可以指向自己,也可以指向复制后的自己,也就是说有了这个指针的存在,无论__block变量配置在堆上还是栈上都能够正确的访问__block变量
一种作为返回值返回的情况
typedef void(^block)(void); block func (int a) { return ^{}; }
转换后
block func (int a) { block tmp = ((void (*)())&__func_block_impl_0((void *)__func_block_func_0, &__func_block_desc_0_DATA)); tmp = objc_retainBlock(tmp); return objc_autoreleaseReturnValue(tmp); }
这里先是通过Block语法生成Block,即生成配置在栈上的Block结构体实例,然后赋值给Block类型的变量tmp中,然后执行objc_retainBlock(tmp)这句其实就是_Block_copy(tmp),将栈上的的Block复制到堆上,赋值后将堆上的指针赋值给tmp,然后堆上的所有都是对象,需要注册陶autoreleasepool中进行管理,然后返回其对象
其实这种内部调用方式,MallocBlock就是对象,而且这种写法是否很似曾相识,就是类方法实例化
- (NSArray *) myTestArray { NSArray *array = [[NSArray alloc] initWithObjects: @"a", @"b", @"c", nil nil]; return [array autorelease]; }
简单来说就是第一步copyBlock到堆上,然后和OC对象一样,返回的对象需要进行autorelease防治内存泄露
ARC下面很多都已经自动帮我们Copy成了MallocBlock了,请看一下几种情况
由于Block是默认建立在栈上,所以如果离开方法作用域,Block就会被丢弃,在非ARC情况下,我们要返回一个Block,需要
[Block copy];
在ARC下,以下几种情况, Block会自动被从栈复制到堆:
- 1.被执行copy方法
- 2.作为方法返回值
- 3.将Block赋值给附有__strong修饰符的id类型的类或者Blcok类型成员变量时
- 4.在方法名中含有usingBlock的Cocoa框架方法或者GDC的API中传递的时候
7.来猜一猜
int a = 1; // 这里的^{}初始化的block赋值给block变量,在OC中没有具体写明的情况下应该就是strong类型的,这就是上面第三点的例子 // 打印出来 first <__NSMallocBlock__: 0x60800004d290> void (^block)(void) = ^{ NSLog(@"%d",a); }; NSLog(@"first %@",block); // 没有截获变量的时候就是globalBlock // second<__NSGlobalBlock__: 0x102f80100> NSLog(@"second%@",^{NSLog(@"呵呵");}); // 截获了变量就是stackBlock // third<__NSStackBlock__: 0x7fff5cc7faa0> NSLog(@"third%@",^{NSLog(@"%d",a);}); __block int val = 10; __strong blk strongPointerBlock = ^{NSLog(@"val1 = %d", ++val);}; // strongPointerBlock: <__NSMallocBlock__: 0x600000044e90> NSLog(@"strongPointerBlock: %@", strongPointerBlock); //1 __weak blk weakPointerBlock = ^{NSLog(@"val2 = %d", ++val);}; // weakPointerBlock: <__NSStackBlock__: 0x7fff5282ea70> NSLog(@"weakPointerBlock: %@", weakPointerBlock); //2 // mallocBlock3: <__NSMallocBlock__: 0x608000046930> NSLog(@"mallocBlock3: %@", [weakPointerBlock copy]); //3 // 截获了test <__NSStackBlock__: 0x7fff5282ea48> NSLog(@"test4 %@", ^{NSLog(@"val4 = %d", ++val);}); //4 // test5 <__NSMallocBlock__: 0x60800005c0b0> NSLog(@"test5 %@", [^{NSLog(@"val4 = %d", ++val);} copy]); //5 // stackBlock经过传参 打印 NSLog(@"传参后 %@",[self getBlock]); } - (blk)getBlock { int val = 11; // 上面已经介绍了,这种直接打印传参前的block,应该是__NSStackBlock__ NSLog(@"传参前:%@",^{NSLog(@"%d",val);}); // 那么现在我们直接传出去 return ^{NSLog(@"%d",val);}; }
这里把几种情况都打印了一下看看到底是哪个类型
1.第一个和第二个打印的区别在于,第一个生成的Block默认赋值给了block变量,第二个直接打印,由于OC里面没有修饰符默认就是strong,所以这么看来遵循第三条规则之后,第二条条打印就是_NSGlobalBlock_(没有截获变量),第一条打印就是_NSMallocBlock_(自动复制到堆上了)
2.后面用strong修饰符和weak修饰符分别打印的是malloc的和stack的,但是无论哪种,只要copy就是变成malloc类型了
3.最后一种就是上面介绍的,stackBlock经过传参,自动变成了mallocBlock
8.属性修饰符 copy 修饰 Block
来看一张表,看看三种类型copy之后有什么区别
@property (nonatomic,copy) blk blk;
当我们这么声明属性的时候,其setter方法就是用了copy方法
- (void)setBlk:(blk)blk { if (_blk != blk) { [_blk release]; // MRC _blk = [blk copy]; } }
- 1.超出作用域存在的理由就是生成了MallocBlock对象,即使出栈了还是能继续调用
- 2.forwarding的理由就是无论在堆上还是栈上,我们都能访问Block,而且能保证访问同一个
- 3.GlobalBlock 、MallocBlock和StackBlock的区别以及如何Block如何会被copy到堆上
- 4.特别上作为参数传递时,类似类方法的autoreleasepool注册进去,避免内存泄露
- 5.在正常写代码的时候不需要管理这个,默认百分之99的情况基本都是MallocBlock
- 6.无论什么情况下,copy一下或者强指针引用一下是不会有错的,能保证必然是MallocBlock
- 7.各种迹象表明,他就是一个对象,可以通过copy改变类型的特殊对象
以上文章整理自:https://blog.csdn.net/deft_mkjing/article/details/53143076