主要讲解 Block内修改外部变量, 内存管理, 以及循环引用;
Block部分一
Block部分二
Block部分三
Block知识点总结
以下内容的测试主要针对ARC
环境; MRC
下直接贴出测试结果, 不再贴出测试代码, 具体请自行测试MRC
环境;
1. __block的用法(基础类型)
当block
内部需要修改外部变量时()
全局变量
, 全局静态变量
可以直接进行修改, 因为block
内部不对他们进行捕获;
静态局部变量
也可以直接在block
内部修改, 因为�block
捕获了它的地址;
auto变量
则不能直接在block
内部修改; 需要用__block
修饰才行;
先从底层结构代码看下为什么
auto变量
为什么不能直接在block
内部修改;首先我们知道
int c = 3
整个变量的作用域就是viewDidLoad
这个方法, 出了这个方法后就不能再访问;底层代码如下:
///viewDidLoad的底层代码如下
static void _I_ViewController3_viewDidLoad(ViewController3 * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController3"))}, sel_registerName("viewDidLoad"));
int c = 3;
static int d = 4;
void(*Case3Block)(void) = ((void (*)())&__ViewController3__viewDidLoad_block_impl_0((void *)__ViewController3__viewDidLoad_block_func_0, &__ViewController3__viewDidLoad_block_desc_0_DATA, c, &d));
((void (*)(__block_impl *))((__block_impl *)Case3Block)->FuncPtr)((__block_impl *)Case3Block);
}
而block
内部执行的代码是封装在这个函数中, 从明面即可得知, 这个函数中并不能访问另一个函数的auto变量
, 这个函数内部里面的变量c
; 是block
底层的结构体重新创建的变量 int c = __cself->c;
static void __ViewController3__viewDidLoad_block_func_0(struct __ViewController3__viewDidLoad_block_impl_0 *__cself) {
int c = __cself->c; // bound by copy
int *d = __cself->d; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController3_2637aa_mi_0, a,b,c,(*d));
}
为什么用__block修饰后的auto变量就可以在block内部修改了呢?
下面代码, 通过__block
修饰后就可以在block
内部修改;
@implementation ViewController3
- (void)viewDidLoad {
[super viewDidLoad];
__block int XXXX = 3;
void(^Case3Block)(void) = ^{
XXXX = 300;
};
NSLog(@"XXXX = %d", XXXX);
Case3Block();
NSLog(@"XXXX = %d", XXXX);
}
@end
2020-06-27 15:27:21.694208+0800 BlockMore2[31764:1243071] XXXX = 3
2020-06-27 15:27:21.694316+0800 BlockMore2[31764:1243071] XXXX = 300
底层的结构为
///viewDidLoad的底层
static void _I_ViewController3_viewDidLoad(ViewController3 * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController3"))}, sel_registerName("viewDidLoad"));
///变量XXXX被封装成了一个对象
__attribute__((__blocks__(byref))) __Block_byref_XXXX_0 XXXX = {(void*)0,(__Block_byref_XXXX_0 *)&XXXX, 0, sizeof(__Block_byref_XXXX_0), 3};
///block的底层
void(*Case3Block)(void) = ((void (*)())&__ViewController3__viewDidLoad_block_impl_0((void *)__ViewController3__viewDidLoad_block_func_0, &__ViewController3__viewDidLoad_block_desc_0_DATA, (__Block_byref_XXXX_0 *)&XXXX, 570425344));
///NSlog删掉了
///block的调用
((void (*)(__block_impl *))((__block_impl *)Case3Block)->FuncPtr)((__block_impl *)Case3Block);
///NSlog删掉了
}
===>
///block的底层结构
struct __ViewController3__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController3__viewDidLoad_block_desc_0* Desc;
///将XXXX变量封装成一个对象
__Block_byref_XXXX_0 *XXXX; // by ref
__ViewController3__viewDidLoad_block_impl_0(void *fp, struct __ViewController3__viewDidLoad_block_desc_0 *desc, __Block_byref_XXXX_0 *_XXXX, int flags=0) : XXXX(_XXXX->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
===>
///变量XXXX的封装对象, 底层也是一个结构体
struct __Block_byref_XXXX_0 {
///isa指针
void *__isa;
///指向自身的一个指针
__Block_byref_XXXX_0 *__forwarding;
///flags: 他的作用就是传入不同的值进行不同的操作;
int __flags;
///结构体的size
int __size;
///变量XXXX
int XXXX;
};
===>
///封了block内部执行代码块的函数
static void __ViewController3__viewDidLoad_block_func_0(struct __ViewController3__viewDidLoad_block_impl_0 *__cself) {
__Block_byref_XXXX_0 *XXXX = __cself->XXXX; // bound by ref
///通过__forwarding指针访问XXXX变量
(XXXX->__forwarding->XXXX) = 300;
}
===>
注意:
///block的描述信息的函数发生了变化, 多了两个函数
static struct __ViewController3__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
///ARC下栈block会被拷贝到堆上, 这个过程会降封装的变量也进行拷贝;
void (*copy)(struct __ViewController3__viewDidLoad_block_impl_0*, struct __ViewController3__viewDidLoad_block_impl_0*);
///当block执行完毕从堆上移出时,会调用dispose函数对所持有的对象进行类似release操作;
void (*dispose)(struct __ViewController3__viewDidLoad_block_impl_0*);
} __ViewController3__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController3__viewDidLoad_block_impl_0), __ViewController3__viewDidLoad_block_copy_0, __ViewController3__viewDidLoad_block_dispose_0};
===>
///copy函数
static void __ViewController3__viewDidLoad_block_copy_0(struct __ViewController3__viewDidLoad_block_impl_0*dst, struct __ViewController3__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->XXXX, (void*)src->XXXX, 8/*BLOCK_FIELD_IS_BYREF*/);}
===>
///dispose函数
static void __ViewController3__viewDidLoad_block_dispose_0(struct __ViewController3__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->XXXX, 8/*BLOCK_FIELD_IS_BYREF*/);}
总结:当用__block
修饰的auto变量
(基础类型)使用block
的过程为:
- 首先被
__block
修饰的变量在底层会被封装成__Block_byref_XXXX_0
的对象变量; - 当
block
被拷贝大堆上时会调用内部的copy
函数,copy
函数会通过_Block_object_assign
函数对封装的对象进行强引用; - 当
block
执行完/从堆上移出时, 会调用block
内部的dispose
函数, 其内部调用_Block_object_dispose
函数对结构体持有的对象进行类似release
的释放操作;
2. __block的用法(对象类型)
首先弄清楚下面两个操作的不同之处;为什么第一个会报错第二个不报错呢;
因为那里的
arr
操作是访问它的方法或者属性, 并不是修改它; 如果arr = nil
也是会报错的;首先看下方代码
- (void)viewDidLoad {
[super viewDidLoad];
__block TestObject *obj = [[TestObject alloc] init];
void(^Case4Block)(void) = ^ {
obj = nil;
};
NSLog(@"obc = %@", obj);
Case4Block();
NSLog(@"obc = %@", obj);
}
2020-06-27 16:29:03.310124+0800 BlockMore2[32512:1277607] obc = <TestObject: 0x600002e5e920>
2020-06-27 16:29:03.310238+0800 BlockMore2[32512:1277607] obc = (null)
通过指令后获得底层代码
///__Block_byref_obj_0封装对象结构
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
///对obj强引用
TestObject *__strong obj;
};
///block结构
struct __ViewController4__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController4__viewDidLoad_block_desc_0* Desc;
///obj对象被封装成__Block_byref_obj_0对象, block对其强引用
__Block_byref_obj_0 *obj; // by ref
__ViewController4__viewDidLoad_block_impl_0(void *fp, struct __ViewController4__viewDidLoad_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
弱引用代码示例:
///弱引用
TestObject *obj = [[TestObject alloc] init];
__block __weak TestObject *weakObj = obj;
void(^Case4Block)(void) = ^ {
NSLog(@"weakObj = %@", weakObj);
};
NSLog(@"obc = %@", obj);
Case4Block();
底层代码实现为
///将obj封装成__Block_byref_weakObj_0对象
struct __Block_byref_weakObj_0 {
void *__isa;
__Block_byref_weakObj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
///对obj对象弱引用
TestObject *__weak weakObj;
};
///block底层结构
struct __ViewController4__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController4__viewDidLoad_block_desc_0* Desc;
///对obj封装成的__Block_byref_weakObj_0强引用;
__Block_byref_weakObj_0 *weakObj; // by ref
__ViewController4__viewDidLoad_block_impl_0(void *fp, struct __ViewController4__viewDidLoad_block_desc_0 *desc, __Block_byref_weakObj_0 *_weakObj, int flags=0) : weakObj(_weakObj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
总结:当用__block
修饰的对象类型时使用block
整个过程为:
- 首先被
__block
修饰的变量在底层会被封装成__Block_byref_XXXX_0
的对象变量; - 当
block
被拷贝大堆上时会调用内部的copy
函数,copy
函数调用_Block_object_assign
函数, 会对对象的修饰符__strong
,__weak
,unsafe_unretained
做出相应的操作(强引用或者弱引用); - 当
block
执行完/从堆上移出时, 会调用block
内部的dispose
函数, 其内部调用_Block_object_dispose
函数对结构体持有的对象进行类似release
的释放操作;
总结: __block在MRC和ARC下的不同
- MRC下:
自动变量
和对象
都是被浅拷贝, 不会强引用(不会增加引用计数); 因为MRC
环境下,block
放在栈区, 所以不会对对象强引用, 如果对block
进行拷贝到堆区操作则是强引用;- ARC下:
block
访问局部变量后会自动进行拷贝操作到堆区, 所以会自动变量
和对象
封装后的__Block_byref_XXX
对象强引用(引用计数会增加);__Block_byref_XXX
对变量根据实际的修饰符进行强/弱引用;
3. 循环引用问题
首先看下方代码, 我们都知道产生了循环引用, 但是为什么会循环引用, 通过底层代码查看下原因
#import "ViewController4.h"
typedef void(^Block)(void);
@interface ViewController4 ()
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) Block Block1;
@property (nonatomic, copy) Block Block2;
@end
@implementation ViewController4
- (void)viewDidLoad {
[super viewDidLoad];
///产生循环引用
self.Block1 = ^{
NSLog(@"name = %@", self.name);
};
self.Block1();
///weak 修饰不产生循环引用
__weak typeof(self) weakSelf = self;
self.Block2 = ^{
NSLog(@"name = %@", weakSelf.name);
};
self.Block2();
}
@end
底层代码:
#Block0的底层结构
struct __ViewController4__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController4__viewDidLoad_block_desc_0* Desc;
///对 self 进行强引用
ViewController4 *const __strong self;
__ViewController4__viewDidLoad_block_impl_0(void *fp, struct __ViewController4__viewDidLoad_block_desc_0 *desc, ViewController4 *const __strong _self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
#Block1的底层结构
struct __ViewController4__viewDidLoad_block_impl_1 {
struct __block_impl impl;
struct __ViewController4__viewDidLoad_block_desc_1* Desc;
///对 self 进行弱引用
ViewController4 *const __weak weakSelf;
__ViewController4__viewDidLoad_block_impl_1(void *fp, struct __ViewController4__viewDidLoad_block_desc_1 *desc, ViewController4 *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
引用block
结构中对self
指针有强引用, 而self
又对block
有强引用, 所以造成循环引用;
ARC解决循环引用方案:
- 用
__weak
修饰, 这样block
底层对self的引用变成了XXX *__weak weakSelf;
推荐使用; - 用
__unsafe_unretained
修饰, 具体效果跟__weak
类似, 但是需要注意__unsafe_unretained
不会将使用完的对象置为nil
; 不推荐使用;
MRC解决循环引用方案:
- 用
__block
修饰, 将对象封装为__Block_byref_XX_0
形式,bock
对__Block_byref_XX_0
是强引用, 但是__Block_byref_XX_0
对其结构体内的变量不是强引用; - 用
__unsafe_unretained
修饰, 具体效果跟__weak
类似, 但是需要注意__unsafe_unretained
不会将使用完的对象置为nil
;
参考文章和下载链接
文中测试代码
iOS clang指令报错问题总结
Apple 一些源码的下载地址
auto关键字是什么
C++中结构体的构造函数
全局变量、静态全局变量、静态局部变量和普通局部变量的区别
网友评论