block定义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就是这俩玩意了
我们来举个栗子看看

使用clang指令
clang -rewrite-objc main.m
得到一个cpp文件
打开之,你就会看到什么是block了

你定义完block之后,其实是创建了一个函数,在创建结构体的时候把函数的指针一起传给了block,所以之后可以拿出来调用。
再看看值捕获的问题


定义block的时候,变量a的值就传递到了block结构体中,仅仅是值传递,所以在block中修改a是不会影响到外面的a变量的。


并不是直接传递a的值了,而是把a的地址传过去了,所以在block内部便可以修改到外面的变量了。
根据isa指针,block一共有3种类型的block
_NSConcreteGlobalBlock 全局静态
_NSConcreteStackBlock 保存在栈中,出函数作用域就销毁
_NSConcreteMallocBlock 保存在堆中,retainCount == 0销毁
而ARC和MRC中,还略有不同:
在 ARC 中,捕获了外部变量的 block 的类会是 _NSConcreteMallocBlock 或者 _NSConcreteStackBlock,如果 block 被赋值给了某个变量,在这个过程中会执行 __Block__copy 将原有的 _NSConcreteStackBlock 变成 _NSConcreteMallocBlock ;但是如果 block没有赋值给某个变量,那他的类型就是 _NSConcreteStackBlock ;没有捕获外部变量的 block 的类会是 _NSConcreteGlobalBlock 。即不在堆上,也不在栈上,它类似 C 语言函数一样会在代码段中。
在非 MRC 中,捕获了外部变量的 block 的类会是 _NSConcreteStackBlock ,放在栈上,没有捕获外部变量的 block 时与 ARC 环境下情况相同。
补充:
在MRC下NSStackBlock需要进行copy才会被拷贝到堆区,否则调用block不在原来的方法堆栈时,会出现被释放的情况,其强引用的对象也可能会因为无引用而被释放;
随后,在ARC中,苹果对NSStackBlock进行了自动处理,当将NSStackBlock给另一个对象赋值时,会自动将block从栈区拷贝到堆区,这样就避免了上面的问题;但是将block当作参数传递给其他方法时,并不会自动将block从栈区拷贝到堆区,而是只有在方法中异步引用了block时,才会自动将block从栈区拷贝到堆区。
比如 [self addBlock:block]; //block是NSStackBlock
-(void)addBlock:(void(^)(void))block{
NSLog(@"%@",block);
}
这时并不会自动将block从栈区拷贝到堆区,因为没有被异步引用时,block一直跟方法调用在同一个调用堆栈里,并不会出现被释放掉的情况;
但是按下面这种写法,
-(void)addBlock:(void(^)(void))block{
NSLog(@"%@",block);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(2 * NSEC_PER_SEC)),
dispatch_get_global_queue(0, 0), ^{
block(nil,nil);
});
}
这时会自动将block从栈区拷贝到堆区,因为block被异步引用了,有可能会出现跟方法调用时不处于同一个调用堆栈,有可能会被释放掉;
苹果这么处理也是充分考虑到了性能问题,在需要进行拷贝时才进行拷贝,减少不必要的资源浪费;
链接:https://www.zhihu.com/question/30779258/answer/49492783
链接:https://www.jianshu.com/p/08010a3ee59e