一、block的定义:
block是封装了函数调用和调用环境的OC对象。
证明:
void test() {
// __NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
void (^block)(void) = ^{
NSLog(@"Hello");
};
NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
}
输出:最终superclass指向NSObject,如下所示。
__NSGlobalBlock__
__NSGlobalBlock
NSBlock
NSObject
二、block的分类:
类型 | 条件 |
---|---|
NSGlobalBlock | 没有访问auto变量 |
NSStackBlock | 访问了auto变量(在MRC环境下,ARC系统会视情况自动添加copy) |
NSMallocBlock | __NSStackBlock__调用了copy |
示例:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 18;
//没有访问auto变量(局部变量)
void(^globalBlock)(void) = ^{
NSLog(@"Lynn Zhang");
};
//访问auto变量(局部变量),在ARC环境下会自动执行copy ==> 堆区block
void(^stackBlock)(void) = ^{
NSLog(@"---%d", age);
};
NSLog(@"\nblock1: %@ \nblock2: %@ \nblock3: %@", [globalBlock class], [stackBlock class], [^{
NSLog(@"---%d", age);
} class]);
}
return 0;
}
每一种类型的block调用copy的结果:
block的类 | 副本源存储域 | copy效果 |
---|---|---|
_NSConcreteGlobalBlock | 数据区域 | 什么也不做 |
_NSConcreteStackBlock | 栈 | 栈区复制到堆区 |
_NSConcreteMallocBlock | 堆 | 引用计数加一 |
在ARC环境下,编译器会根据情况将栈区上的block复制到堆区:
- block作为返回值时;
- block被__strong修饰时,如
void(^block)(void) = ^{......}
; - block作为GCD API的方法参数时;
- block作为系统API的方法,含有usingBlock的方法参数时。
三、block的变量捕获:
原因:为了保证block内部能够正常的访问外部变量,需要有变量捕获机制。
变量类型 | 修饰 | 捕获到block内部 | 访问方式 |
---|---|---|---|
局部变量 | auto | 是 | 值传递 |
局部变量 | static | 是 | 指针传递 |
全局变量 | - | 否 | 直接访问 |
示例:
//源码
void (^helloBlock)(void);
static int no;
void test() {
int age = 10;
static int height = 10;
no = 10080601;
helloBlock = ^{
// age的值捕获进来
NSLog(@"age is %d, height is %d and no is %d", age, height, no);
};
age = 20;
height = 20;
no = 100806;
helloBlock();
}
//打印结果:
//age is 10, height is 20 and no is 100806
//底层实现
void test() {
int age = 10;
static int height = 10;
no = 10080601;
//可以看到,age是通过值传递到block内部,height是通过指针传进来的,no是通过直接访问取值
helloBlock = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height));
age = 20;
height = 20;
no = 100806;
((void (*)(__block_impl *))((__block_impl *)helloBlock)->FuncPtr)((__block_impl *)helloBlock);
}
四、__block修饰符
- __block可以用于解决block内部无法修改auto变量值的问题;
- __block不能修饰全局变量、静态变量;
编译器会将__block变量包装成一个对象。
age用 auto
、static
、__block
对应的底层实现如下所示:
/**
int age = 10;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
}
*/
/**
static int age = 10;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *age;
};
*/
//__block
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
struct __Block_byref_age_0 *age;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
MZBlock block = ^{
NSLog(@"age = %d", age);
};
block();
}
return 0;
}
由此可见:当用 auto
修饰时,直接是把变量 age
传入到block内;static
修饰时,则传入的是一个 *age
的指针;而用 __block
修饰时,则是被包装成一个OC对象。
拓展
类对象也在数据段里。
证明:
int age = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
NSLog(@"数据段:age %p", &age);
NSLog(@"数据段:class %p", [MJPerson class]);
NSLog(@"堆:obj %p", [[NSObject alloc] init]);
NSLog(@"栈:a %p", &a);
}
return 0;
}
打印输出如下,可以看出[MJPerson class]的内存地址和&age的很接近。
数据段:age 0x100002468
数据段:class 0x100002418
堆:obj 0x1007051a0
栈:a 0x7ffeefbff3cc