iOS 底层 Block 的类型 - AlvinSunny/OC-TheUnderlying GitHub Wiki
block的类型
block 有三种类型,可以通过调用class方法或者isa指针查看具体类型,但最终所有的都是继承自NSBlock。而NSBlock 继承自NSObject !
运行时 --> __NSGlobalBlock__ ( 编译时 --> _NSConcreteGlobalBlock )
运行时 --> __NSMallocBlock__ ( 编译时 --> _NSConcreteMallocBlock )
运行时 --> __NSStackBlock__ ( 编译时 --> _NSConcreteStackBlock )
使用上以运行时的类型为准,编译阶段和运行时不同主要是编译时有些数据可能无法确定,所以苹果在设计上做出的区别。众所周知OC的数据类型是动态的嘛 ,只有在运行时才能确定的!另_NSConcreteGlobalBlock这种类型是通过clang编译器生成的,可能和在运行时的有所不同是正常的。
继承关系举例
__NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
为什么会有不同的block类型,主要的原因就是block存放的位置不同,分别有数据段、堆段、栈段,下面试
MRC环境下的block类型

int a = 110;
NSLog(@"block : %@",[^{} class]); //__NSGlobalBlock__
NSLog(@"block1 : %@",[^{a;} class]); //__NSStackBlock__
NSLog(@"block2 : %@",[[^{a;} copy] class]);//__NSMallocBlock__
注意:
- 在ARC环境对应的情况类型会有变化,因为ARC帮助我们做了内存管理
- 没有访问auto变量:其他变量是可以访问的,比如局部
static变量全局变量
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上;比如以下情况:
1. block作为函数的返回值时;//Mansory 框架中经常用到
2. 将block赋值给强指针时;
3. block作为 Cocoa API中方法名,含有usingBlock的方法参数时;
4. block作为 GCD API的方法参数时;
5. block作为属性声明时;
应用程序中 block 的内存分配

NSStackBlock 类型的 block 是存放在栈段的,栈的内存是由系统负责管理的,当一个方法执行结束后就会被销毁;这样是有风险的;比如该block中捕获了外部的局部变量,在该方法执行结束后再次调用block ,这时获取到的变量值可能就不准确了。如下示例:

这时就需要用到 copy ,对block进行一次copy;这样block就会从栈段移动到堆段(堆:动态分配内存,需要开发者申请,也需要自己管理内存);这样block 中存储的变量就不会被提前释放。

注意
__NSGlobalBlock__类型调用copy是没有意义的,因为这种类型的block不访问任何的auto变量;
NSMallocBlock调用copy还是在堆段的只不过引用计数器会加一;

关于block属性copy声明
@property (copy, nonatomic) void (^block)();
上面的代码在ARC环境下就等价于对一个block在定义后进行一次 copy 操作;
XYHPerson *p = [[XYHPerson alloc] init];
p.block = [^{
} copy];
疑问
下面代码中的FuncPtr指向的方法是放在那里 ?
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; //指向将来需要执行的函数的地址
};
答 :是放在代码段。
函数调用栈
函数调用栈是指在调用某个函数时,在栈区分配一块栈空间给这个函数来使用;当函数调用完毕后这片空间就会被回收。这块数据就变垃圾数据,等待再次分配给其他地方使用。
宏定义和全局变量的区别
宏定义可以理解为一种替换,比如把某个数值替换成 age,在编译时又替换成对应数值;且宏定义不是变量
#define Age 10
全局变量是在方法外部定义的变量,可以在任意地方访问;前提是定义时没有用static关键字修饰。
如何判断某个数据存储在哪里呢 ?
找三个已知的和一个想知道的通过NSLog的方式输出对比可能能够得到答案

运行时数据存储方式有哪些 ?有什么作用
堆、栈、寄存器
堆:Objective-C对象一般申请内存是在堆空间, 并且堆内存释放ARC环境是由 runtime+LLVM 协作完成
栈: 非OC对象、系统默认创建的对象、指针一般放在栈里面(栈内存会被系统自动回收)
寄存器: CPU中的元件单元,是硬件,arm64之后方法中的隐藏参数self 、_cmd就是放在寄存器中的。