iOS Block本质笔记
2021-10-30 本文已影响0人
山杨
OC中定义block
void(^myBlock)(NSString *) = ^(NSString *param){
};
block访问外部参数
int age = 10;
static NSString *name = @"Andy";
NSString *address = @"India";
void(^myBlock)(NSString *) = ^(NSString *param){
name = @"Alandy";
NSLog(@"name %@, age %@, address %@", name, @(age), address);
};
OC转C++分析
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSString **name;
int age;
NSString *address;
__main_block_impl_0(
void *fp,
struct __main_block_desc_0 *desc,
NSString **_name, int _age, NSString *_address, int flags=0) : name(_name), age(_age), address(_address) {
// name(_name)的意思是把传入的_name赋值给外面的成员name
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
static void
__main_block_func_0(struct __main_block_impl_0 *__cself, NSString *param) {
NSString **name = __cself->name; // bound by copy
int age = __cself->age; // bound by copy
NSString *address = __cself->address; // bound by copy
(*name) = (NSString *)&__NSConstantStringImpl__var_main_2;
NSLog(
&__NSConstantStringImpl__var_main_3, (*name),
objc_msgSend(objc_getClass("NSNumber"),sel_registerName("numberWithInt:"),age),
address);
}
static void
__main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src) {
_Block_object_assign((void*)&dst->name, (void*)src->name, 3);
_Block_object_assign((void*)&dst->address, (void*)src->address, 3);
}
static void
__main_block_dispose_0(struct __main_block_impl_0 *src) {
_Block_object_dispose((void*)src->name, 3);
_Block_object_dispose((void*)src->address, 3);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int age = 10;
static NSString *name = (NSString *)&__NSConstantStringImpl__var_main_0;
NSString *address = (NSString *)&__NSConstantStringImpl__var_main_1;
void(*myBlock)(NSString *) =
((void (*)(NSString *))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &name, age, address, 570425344));
-----------
简化一下 myBlock
void(*myBlock)(NSString *) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &name, age, address, 570425344);
-
block的变量捕获机制
为了保证block能够正常访问外部的变量,block有两个变量捕获机制
int a = 1;等价于 auto int a = 1;
自动变量,离开作用域就销毁
static修饰的变量一直存在内存中不会被释放,在block内部可以直接访问,被捕获到block内部的static变量会变成*或**类型,例如:block中捕获了static修饰的name
struct __main_block_impl_0 {
...
NSString **name;
...
};
-
block的类型
- block的本质是一个OC对象,内部有一个isa指针
-
block是封装了函数调用以及函数环境的OC对象
验证一下:
void(^myBlock)(NSString *) = ^(NSString *param){};
[myBlock class] 的返回值是 __NSGlobalBlock__
Class superCls = [myBlock superclass];
superCls 的值是 NSBlock
[superCls superclass] 的返回值是 NSObject
[[superCls superclass] superclass] 的返回值为 null
那么block的类型就是__NSGlobalBlock__了吗?不止如此
换一种block的用法:
int height = 173;
void(^myBlock)(int) = ^(int param){
NSLog(@"height %d", height);
};
再通过NSLog打印出的结果是__NSMallocBlock__
再换一种block的用法:
NSLog(@"%@", [^{NSLog(@"%d", height);} class]);
这种直接方式打印出的结果是__NSStackBlock__
通过clang编译出来的block结果与打印出来的结果不同,以NSLog运行时打印的结果为准,原因与llvm的编译器有关系
通过这样的方式得到的block有3种类型分别是
注意:
(在ARC模式下,由于编译器增加了一些东西,导致在block内部访问了auto变量的时候,block变成了__NSMallocBlock__,在MRC下是__NSStackBlock__)
- 在ARC模式下把block作为属性使用时用copy和strong修饰都可以
@property (nonatomic, copy) void (^block)(void); or @property (nonatomic, strong) void (^block)(void);
- ARC模式下系统带有block的方法都是默认进行了copy操作的例如
-[NSArray enumerateObjectsUsingBlock:[block copy]];
有使用到__weak等修饰词的代码,OC文件转C++的时候需要额外添加一些内容
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m
- 只要是在栈上的block(__NSStackBlock__)都不会对外部的对象进行强引用
- 如果block被拷贝到堆上
- 会调用block中的__main_block_copy_0函数
- __main_block_copy_0函数又会调用_Block_object_assign函数
- _Block_object_assign中会根据外部的auto变量使用的修饰符(
__weak
、__strong
、unsafe_unretained
)做出相应的操作(强引用、弱引用)- 如果block从堆上移除
- 会调用block内部的__main_block_dispose_0函数
- __main_block_dispose_0会调用_Block_object_dispose函数去释放block中引用的auto变量,类似release
-
用__block修饰变量
被__block修饰过得变量结构会发生变化
__block int height = 173;
本质是:
__attribute__((__blocks__(byref))) __Block_byref_height_0 height = {(void*)0,(__Block_byref_height_0 *)&height, 0, sizeof(__Block_byref_height_0), 173};
简化一下:
__Block_byref_height_0 height =
{
0,
&height,
0,
sizeof(__Block_byref_height_0),
173
};
struct __Block_byref_height_0 {
void *__isa;
__Block_byref_height_0 *__forwarding;
int __flags;
int __size;
int height;
};
---------------- 分隔线 ----------------
__block NSObject *obj = [NSObject new];
本质是:
__attribute__((__blocks__(byref))) __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))};
简化一下:
__Block_byref_obj_1 obj =
{
0,
&obj,
33554432,
sizeof(__Block_byref_obj_1),
__Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131,
objc_msgSend(objc_getClass("NSObject"), sel_registerName("new"))
};
struct __Block_byref_obj_1 {
void *__isa;
__Block_byref_obj_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
^{
obj = nil;
height = 175;
}
这段block中的代码本质是:
在__main_block_func_0中的
(obj->__forwarding->obj) = __null;
(height->__forwarding->height) = 175;
有个一直没解决的问题:代码中使用
__block
修饰NSString *
之后,转换C++文件会报错,不知道怎么处理!!!求指教
- 创建__block修饰的NSArray对象
__block NSArray *arr = @[@"1111"];
block中修改arr的值 ^{ arr = @[@"22222"]; };
转换成C++
__Block_byref_arr_2 arr = { 0, &arr, 33554432, sizeof(__Block_byref_arr_2), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, objc_msgSend( objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), __NSContainer_literal(1U, &__NSConstantStringImpl__var_main_0).arr, 1U) };
- __main_block_func_0
(arr->__forwarding->arr) = objc_msgSend( objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), __NSContainer_literal(1U, &__NSConstantStringImpl__var_main_1).arr, 1U );
综上看来被__block修饰的变量会把变量包装成一个结构体来使用
__block int height = 173;
struct __Block_byref_height_0 {
void *__isa;
__Block_byref_height_0 *__forwarding;
int __flags;
int __size;
int height;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_height_0 *height; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_height_0 *_height, int flags=0) : height(_height->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 基本数据类型的变量如果没有被__block修饰过,不会产生copy和dispose函数
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
// block中执行的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_height_0 *height = __cself->height;
(height->__forwarding->height) = 175;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign(&dst->height, src->height, 8);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose(src->height, 8);
}
被__block修饰的height
变量的实际地址和__Block_byref_height_0
中的height
相同。
-
__block的内存管理
- 当block在栈上(__NSStackBlock__),并不会对__block变量产生强引用
- 当block被copy到堆时(__NSMallocBlock__)
- 会调用block内部的__main_block_copy_0函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会对__block修饰的变量形成强引用(retain)或弱引用(
仅限于ARC模式下会retain,MRC模式不会retain
)
- 当block从堆上移除
- 会调用block内部的__main_block_dispose_0函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放引用的__block变量(release)
-
block的循环引用问题
ARC模式下
- 用__weak、__unsafe_unretained解决
__weak typeof(self) weakSelf = self; __unsafe_unretained typeof(self) unSelf = self; self.block = ^{ weakSelf.name = @"__weak"; unSelf.name = @"__unsafe_unretained"; };
- 用__block解决(必须要执行block才能释放)
__block YSObject *bSelf = self; self.block = ^{ bSelf.name = @"__block"; bSelf = nil; }; self.block();
MRC模式下
- 用__unsafe_unretained解决(MRC下没有弱引用)
__unsafe_unretained typeof(self) unSelf = self; self.block = ^{ unSelf.name = @"__unsafe_unretained"; };
- 用__block解决
__block YSObject *bSelf = self; self.block = ^{ bSelf.name = @"__block"; };
- 使用__strong确保对象没有被释放
__weak typeof(self) weakSelf = self; self.block = ^{ // 确保weakSelf有效,使用weakSelf无法直接访问_name __strong strongSelf = weakSelf; strongSelf->_name = @"__weak"; };
-
block的常见面试题
- block的本质
- __block的原理
- 如何处理循环引用问题,ARC和MRC下的处理方式有哪些不同,为什么?