block常见的几个问题:
-
1、block是什么
block是一个指向结构体的指针,编译器会将block的内部代码生成对应的函数。 -
2、block为什么用copy修饰
正常创建出来的block的内存是放在栈中(如果block内部没有调用外部变量时存放在全局区),程序员无法去管理,什么时候释放内存也不是程序员可以决定的,栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃。所以一般情况下,就需要使用copy去修饰,会把block copy到堆上,在ARC中,对block代码块内部的对象强引用,在非ARC中对于引用对象进行一次retain操作。 -
3、block与循环引用
在block代码块内部如果使用了当前对象进行调用方法,或者他的的操作,就会对当前对象进行强引用一次,并且如果这个block归当前对象持有,就会导致block和当前对象互相持有引用,而都不能正常释放。(注意:如果block不是当前对象所持有,例如在控制器中使用AFNetWorking请求,AFNetWorking的block并不是当前控制器所持有的,所以这里不用__weak,不会造成循环引用) -
4、_strong的使用
使用_weak来修饰可以避免对控制器或者当前类的循环引用.
但是有的时候,当前对象self已经销毁了,之后再去执行这个block,里面的weakSelf就是个nil了,可能会出现异常,于是最好是如下写法:
__weak typeof(self) weakSelf = self;
self.rightBtnClickHandler = ^() {
__strong typeof(self) strongSelf = weakSelf;
[strongSelf defaultRightBtnClick];
[strongSelf defaultLeftBtnClick];
};
在block内部使用__strong对weakSelf进行一次强引用,self的引用计数+1,此时strongSelf相当于是block内部的一个局部变量,只要block不结束,self这个对象就不会释放,等到block执行完毕,局部变量strongSelf也会自动销毁,完成释放。
block底层初识
接下来看几个常见的面试题
void test1()
{
int value = 10;
void (^block)(void) = ^{
NSLog(@"value = %d", value);
};
value = 20;
block(); // 打印结果:value = 10
}
void test2()
{
__block int value = 10;
void (^block)(void) = ^{
NSLog(@"value = %d", value);
};
value = 20;
block(); // 打印结果:value = 20
}
void test3()
{
static int value = 10;
void (^block)(void) = ^{
NSLog(@"value = %d", value);
};
value = 20;
block(); // 打印结果:value = 20
}
int value = 10;
void test4()
{
void (^block)(void) = ^{
NSLog(@"value = %d", value);
};
value = 20;
block(); // 打印结果:value = 30
}
可以看出,先test1中,我们更改了局部变量value的值,但是block中的value值并没有变化,但是在test2、3、4中,却能成功更改。这是为什么呢?
我们尝试着看下它们的底层代码。
终端执行clang -rewrite-objc main.m
把文件编译成c++代码,得到.cpp文件,打开后估计有接近10万行代码,直接command+⬇️,我们来到文件的最后,再往上面滑一点,可以看到如下:
block_底层代码.png
我们可以看到 void (*block)(void) 是一个没有参数没有返回值的函数指针,既然是一个函数指针,那它就是一个变量,变量里面只能保存函数地址,等式右边接收是下面这个函数的返回值:
__test1_block_impl_0(void *fp, struct __test1_block_desc_0 *desc, int _value, int flags=0) : value(_value) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
这个函数有4个参数,参数 flags= 0,这个参数其实就相当于Swift中指定了一个默认值,不传也有值,可以忽略。所以上面test1中传了3个参数,test2中传了4个参数,其中第三个参数是value,前者是传值,后者是传的value的地址,故后者value的值发生了变化
block_test1.png block_test2.png总结:
- block可以修改全部变量和静态变量,不可以修改局部变量,如果想要修改使用__block,
- block之所以能够修改全局变量、静态变量、__block修饰的局部变量,是因为把指向变量的指针地址copy到block结构体内部,而局部变量是copy的变量值到block内部.
网友评论