大家好,我是OB!今天来聊聊大家的老熟人Block!
block
block 本质就是NSObject对象,把方法包装成了block块
来看看block的真面目
void(^OBblock)(void) = ^{
NSLog(@"-------");
};
OBblock();
/*
* 编译后
*/
void(*OBblock)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA
);
OBblock->FuncPtr(OBblock);
编译后
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//block 最终变成了 __main_block_impl_0类型的结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//构造函数,返回 __main_block_impl_0 类型的实例
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block块中的实现,放在这个函数中
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_fo_T_main_0072ab_mi_0);
}
值捕获
//全局变量
int height_ = 20;
static int weight_ = 20;
void test() {
auto int num = 20;
static int age = 20;
void(^OBblock)(void) = ^{
NSLog(@"num:%d age:%d %d %d",num,age,height_,weight_);
};
num = 10;
age = 10;
OBblock();
}
再看看结构体发生变化没
void(*OBblock)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
num, //值捕获,20已经被赋值到结构体中了
&age //指针捕获
);
//全局变量height_,weight_没有捕获
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int num;
int *age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int *_age, int flags=0) : num(_num), age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
多了
num
*age
构造函数也多了一个: num(_num)
age(_age)
(将_nun的值自动赋值给num变量),但是 全局变量height_
,weight_
没有捕获却没有捕获全局变量,
调用函数实现:
num
直接取值__cself->num
,static变量:__cself->age
指针传递,获取age地址取值,全局变量height_,weight_
直接访问
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int num = __cself->num; // bound by copy
int *age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__..._mi_0,num,(*age),height_,weight_);
}
捕获与否取决去block的函数执行块,执行时,能不能访问改变量。
变量类型 | block捕获 | 访问方式 | 能否修改 |
---|---|---|---|
局部变量:auto | 捕获 | 值传递 | 添加__block 修饰 |
局部变量:static | 捕获 | 指针传递 | 可直接修改 |
全局变量:static/auto | 不捕获 | 直接访问 | 可直接修改 |
block类型
block 本质是一个结构体,换成OC那就是block本质是NSObject;
void getType(id obj) {
if (obj == NULL) { return; }
Class currentClass = [obj class];
NSLog(@"%@",currentClass);
getType([(NSObject*)currentClass superclass]);
}
可以看到结果:block 也是继承 NSObject
Test_05_block[56306:5305484] __NSGlobalBlock__
Test_05_block[56306:5305484] __NSGlobalBlock
Test_05_block[56306:5305484] NSBlock
Test_05_block[56306:5305484] NSObject
进一步探索发现:block的三种类型
block访问的变量类型 | block类型 | copy操作 |
---|---|---|
没有访问局部auto变量 | __NSGlobalBlock__ | 一直在数据区什么也不做,反正也没有访问变量,一般不考虑 |
访问局部auto变量 | __NSStackBlock__ | 从栈区copy到了堆区,StackBlock 变成了MallocBlock |
NSStackBlock调用了copy | __NSMallocBlock__ | 引用计数加一 |
注意:
NSStackBlock
调用了copy
变成__NSMallocBlock__
其实就是对象的生命周期或者是变量的生命周期不统一,需要放到一个能长久访问的地方,防止程序访问时访问不到值。所以ARC
才会自动给NSStackBlock
copy
一下,变成__NSMallocBlock__
循环引用
使用block会造成block捕获对象,会造成对象的引用计数+1,这时就会可能造成循环引用
解决循环引用方法 | 操作 | 安全性 |
---|---|---|
__weak | 指针指向的对象销毁时,自动置为nil | 安全 |
__unsafe_unretained | 指针指向的对象销毁时,还指向那个地址 | 不安全 |
__block | 利用在block中可以修改对象的特性,将obj=nil | 安全 |
不使用 __weak
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Animal *animal; //对象引用计数+1
...
};
使用 __weak
后
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Animal *__weak animal; //变成弱引用
...
};
block和delegate相比
优点
- 回调的block代码块定义在委托对象函数内部,使代码更为紧凑,使用方便
- 被委托对象不再需要实现具体某个protocol,代码更为简洁
缺点
- delegate运行成本低,block成本很高。
block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者block置nil后才消除;delegate只是保存了一个对象指针,直接回调,没有额外消耗 - 如果在block里面使用了self,容易导致循环引用问题,要用weak
- delegate 更适用于多个回调方法,block 则适用于少量回调