一、__block 的解析
-
接上一篇《iOS Block底层解析一》,在block中我们要修改局部变量(自动局部变量,自动局部类对象)前面都加__block修饰 自动是auto的意思,就是我们写代码苹果自动给我们生成的 比如:int a = 10;其实是 auto int a = 10; 下面就直接说局部变量吧,这么麻烦的叫法膈应人
-
为啥局部变量在block里面不能直接修改,从上一篇的分析我们知道我们传进去的局部变量,相当于函数的参数,你在函数里面改参数,外面的局部变量会变吗?例如:
void test8(int a) {
a++;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
int b = 10;
test8(b);
NSLog(@"%d",b);
}
return 0;
}
打印结果:
2020-04-13 21:12:24.922403+0800 blockTest[3434:163752] 10
- 类对象的属性修改要不要加__block呢? 其实不用的,跟NSMutableArray等 增、删、改、查是一样的,我们修改的都是她里面的东西,其实是因为block最后拿到的还是person地址里面的name值 例如:
// 对象类型
void test6() {
Person *p = [Person new];
p.name = @"hello block!";
void(^block)(void) = ^{
p.name = @"fuck block!";
NSLog(@"--- %@",p.name);
};
block();
}
打印结果:
2020-04-13 21:18:06.198269+0800 blockTest[3486:167284] --- fuck block!
void test8() {
NSMutableArray * arr = [NSMutableArray new];
void(^block)(void) = ^{
[arr addObject:@2];
NSLog(@"--- %@",arr);
};
block();
}
打印结果:
2020-04-13 21:25:44.568467+0800 blockTest[3515:170553] --- (
2
)
-
但是我们改变他们本身就会报错,例如:
图片.png
图片.png
-
一定要知会的一点就是有__block修饰的捕获变量和没有__block修饰的捕获变量要分开,内存管理上不要混淆,这样思路才更清晰。这也是跟上一篇分开解析的原因
-
其实我们要加__block就两种情况,局部变量和局部类对象,其他的都可以直接访问在block中修改,废话有点多 开搞 开搞
// __block作用
void test7() {
__block int age = 10;
__block NSObject *objc = [[NSObject alloc] init];
void(^block)(void) = ^ {
objc = nil;
age = 20;
};
block();
}
- 老套路clang一波
把转换的去掉简化代码,摆好步骤:
// 第一步
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
test7();
}
return 0;
}
// 第二步
void test7() {
// __block int age = 10;
__Block_byref_age_0 age = {
0,
&age,
0,
sizeof(__Block_byref_age_0),
10
};
// __block NSObject *objc = [[NSObject alloc] init];
__Block_byref_objc_1 objc = {
0,
&objc,
33554432,
sizeof(__Block_byref_objc_1),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
objc_msgSend((id)(objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))
};
// 调用
void(*block)(void) = &__test7_block_impl_0(__test7_block_func_0, &__test7_block_desc_0_DATA, (__Block_byref_objc_1 *)&objc, (__Block_byref_age_0 *)&age, 570425344));
block->FuncPtr(block);
}
// 第三步
struct __Block_byref_age_0 {
void *__isa; // isa指针
__Block_byref_age_0 *__forwarding; // 指向自己的指针
int __flags; // 不清楚是什么鬼 猜测是标识符什么的
int __size; // 当前结构体的大小
int age; // 捕获值
};
// 第三步
struct __Block_byref_objc_1 {
void *__isa; // isa指针
__Block_byref_objc_1 *__forwarding; // 指向自己的指针
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*); // 执行copy操作
void (*__Block_byref_id_object_dispose)(void*); // 执行dispose操作
NSObject *objc;// 捕获 NSObject类型指针
};
// 第四步
static void __test7_block_func_0(struct __test7_block_impl_0 *__cself) {
// 拿到 __Block_byref_objc_1里面 *objc指针
__Block_byref_objc_1 *objc = __cself->objc; // bound by ref
// 拿到__Block_byref_age_0里面 *age指针
__Block_byref_age_0 *age = __cself->age; // bound by ref
//objc->__forwarding指针指向 __Block_byref_objc_1 拿到里面的的objc 完成赋值
(objc->__forwarding->objc) = __null;
//age->__forwarding指针指向 __Block_byref_age_0 拿到里面的的age 完成赋值
(age->__forwarding->age) = 20;
}
// 第五步
struct __test7_block_impl_0 {
struct __block_impl impl;
struct __test7_block_desc_0* Desc;
__Block_byref_objc_1 *objc; //__Block_byref_objc_1 类型结果体
__Block_byref_age_0 *age; //__Block_byref_age_0类型结果体
__test7_block_impl_0(void *fp, struct __test7_block_desc_0 *desc, __Block_byref_objc_1 *_objc, __Block_byref_age_0 *_age, int flags=0) : objc(_objc->__forwarding), age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 看得眼花,那我们走一个,其实前面搞好了,这里就一目了然:
1.第一步一样,第二步多了两个东西,其实是一个鬼东西,差不多 __Block_byref_age_0和__Block_byref_objc_1两个结构体
2.第三就是对这两个结构体的解析,是不是就是一个类,包装成类,然后再保存在block结构体里面
3.第四步就是赋值个过程了__forwarding指针为啥要这样搞,明明已经拿到*objc的指针了,又(objc->__forwarding->objc) 这样拿objc,这他么不是吓搞吗?我们想一下,前面说的内存管理,很多时候block会copy到堆上的,然后苹果是这样设计的 如图:
图片.png
图片.png
这样搞的好处就是:
- block在栈上的时候 block在栈上的 __forwarding指针指向自己
- block在堆上的时候 block在栈上的 __forwarding指针指向堆内存 block在堆上的_forwarding指针指向堆内存
- 所以你是指向栈还是堆 最后都能找到这个变量
二、__weak 修饰、 __strong修饰、block的循环引用问题
- 其实到上面block已经搞完了,这个步骤都是根据之前的解析的都可以推导出来,还是要自己去多推导 还是说说吧
- __weak 修饰的时候可以解除block的循环引用问题,首先你要明白怎么样才会造成循环引用问题:
- 两个是否相互强引用 2.两个以上是否形成强引用闭环
-
那么我们可以得出结论 block只有在堆上才会形成强引用,就是执行copy操作的时候 对就是结合上一篇文章说道的内存管理总结:
图片.png
图片.png -
有些同学就问了 GCD里面的block调用self会吗 UIview animation的block调用self会吗 self要用__weak 修饰吗?嗯 是不会的 因为你self 跟GCD UIview 没有强引用block 没有形成相互强引用,还有很多第三方库的block里面用self 也是一样的,首先你要判断的是 self有没有强引用第三方的block
-
有同学会问 NSTimer的block里面会是强引用呢? 因为timer是self的属性strong的啊,然后你在人家的block里面调用肯定就形成相互强引用啦
-
最后关于循环引用的问题就是前面说的两个
- 两个是否相互强引用 2.两个以上是否形成强引用闭环
-
还有种特殊的场景比较少见,用__weak 修饰的对象,什么情况下会在block里面再用__strong修饰,就是在block里面执行多线程时候,为啥这么说,因为__weak 修饰的对象block结束的时候block里面的对象就释放了,可是你后面的线程还要使用block里面的对象所以就会有空指针的问题(weak/__weak 修饰的对象释放后为nil)
-
关于block的底层解析就到这里了,也是对自己学习block的一个总结吧,以后遇到block的难题基本都是这样一套推倒,还有一些深入的东西和细节估计说得不到位,希望各位大老爷指出
网友评论