1. block的本质
int a = 10;
void (^block)(void) = ^{
NSLog(@"block ------%d",a);
NSLog(@"block ------%d",a);
NSLog(@"block ------%d",a);
};
block();
以上代码编译后代码:
int a = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
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;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1l_pky1p4kn4p9dcgr7cqzz7n080000gn_T_main_a762a2_mi_0,a);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1l_pky1p4kn4p9dcgr7cqzz7n080000gn_T_main_a762a2_mi_1,a);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1l_pky1p4kn4p9dcgr7cqzz7n080000gn_T_main_a762a2_mi_2,a);
}
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)};
简化后:去掉强引用
int a = 10;
void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));
block->FuncPtr(block);
可见block就是一个指针地址(带*),本质上是__main_block_impl_0结构体,传减去一个方法__main_block_func_0(block的函数体)和__main_block_desc_0_DATA(描述信息包括block的长度等)的地址。
__main_block_func_0会赋值给__block_impl的FuncPtr。
最后通过block拿到FuncPtr (因为结构体的地址和结构体的第一个元素的地址是一样的,所以可以通过强转拿到),调用的时候把这个block传进去,拿到它捕获的变量等。
- block本质上也是一个OC对象,它内部也有个isa指针。
- block是封装了函数调用以及函数调用环境的OC对象
- block的底层结构如图所示。
block变量捕获
static int a = 10;
NSLog(@"外面——————————%p",&a);
void (^block)(void) = ^{
NSLog(@"blcok a = %d",a);
a = 30;
NSLog(@"block-----%p",&a);
};
a = 20;
block();
NSLog(@"外面 a = %d",a);
打印结果:
2020-06-23 11:54:58.613340+0800 block[92464:2645876] 外面——————————0x100001208
2020-06-23 11:54:58.613949+0800 block[92464:2645876] blcok a = 20
2020-06-23 11:54:58.614046+0800 block[92464:2645876] block-----0x100001208
2020-06-23 11:54:58.614151+0800 block[92464:2645876] 外面 a = 30
可以看出,static修饰的时候是 指针传递。
额外:
void(^block)(void) = ^{
_name;
}
访问_name.其实是捕获了self.因为函数有两个隐式参数:self和_cmd. 再通过self->_name访问_name.
3. block的类型
- block有三种类型,可以通过调用class方法或者isa指针查看具体类型,最终都继承自NSBlock(继承NSObject).
- NSGlobalBlock (_NSConcreteGlobalBlock) (继承__NSGlobalBlock)
- NSStackBlock (_NSConcreteStackBlock ) (继承__NSStackBlock)
- NSMallocBlock (_NSConcreteMallocBlock) (继承__NSMallocBlock)
在MRC环境下:
- 小知识:类对象是放在数据段的。
4. block的copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
- block作为函数返回值时
- 将block赋值给__strong指针时 (ARC环境默认就是强引用)
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
MyPerson *person = [[MyPerson alloc]init];
person.age = 10;
MyBlock block = ^{
NSLog(@"%d",person.age);
};
ARC默认是强引用,所以这个block在推上。
MRC block属性建议写法:@property (copy, nonatomic) void (^block)(void);
ARC block属性建议写法:
- @property (strong, nonatomic) void (^block)(void);
- @property (copy, nonatomic) void (^block)(void);
4.对象类型的auto变量
-
当block内部访问了对象类型的auto变量时
如果block是在栈上,将不会对auto变量产生强引用 -
如果block被拷贝到堆上
- 会调用block内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
- 如果block从堆上移除
- 会调用block内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数 _Block_object_dispose函数会自动释放引用的auto变量(release)
源码:
因为__weak和__strong,是运行时产生的。所以在使用clang转换OC为C++代码时,可能会遇到以下问题
cannot create __weak reference in file using manual reference
解决方案:支持ARC、指定运行时系统版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
代码:
MyBlock block;
{
MyPerson *person = [[MyPerson alloc]init];
person.age = 10;
__weak MyPerson *weakPerson = person;
block = ^{
NSLog(@"%d",weakPerson.age);
};
}
NSLog(@"---------------------");
源码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MyPerson *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MyPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
MyPerson *__weak weakPerson = __cself->weakPerson; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1l_pky1p4kn4p9dcgr7cqzz7n080000gn_T_main_d22b95_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)weakPerson, sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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};
注意:这里是对person捕获,而不是对age.
代码:
MyBlock block;
{
MyPerson *person = [[MyPerson alloc]init];
person.age = 10;
block = ^{
NSLog(@"%d",person.age);
};
}
NSLog(@"---------------------");
源码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MyPerson *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MyPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
MyPerson *__strong person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1l_pky1p4kn4p9dcgr7cqzz7n080000gn_T_main_db4cbb_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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};
测试:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
MJPerson *p = [[MJPerson alloc] init];
__weak MJPerson *weakP = p;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1-------%@", p);
NSLog(@"+++++++++++++");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2-------%@", weakP);
});
});
NSLog(@"touchesBegan:withEvent:");
}
打印结果:
2020-06-23 22:43:57.526211+0800 Interview03-测试[49740:3955822]
touchesBegan:withEvent:
2020-06-23 22:43:58.525566+0800 Interview03-测试[49740:3955822] 1-------<MJPerson: 0x600002cfc340>
2020-06-23 22:43:58.525788+0800 Interview03-测试[49740:3955822] +++++++++++++
2020-06-23 22:43:58.526006+0800 Interview03-测试[49740:3955822] MJPerson - dealloc
2020-06-23 22:44:00.528077+0800 Interview03-测试[49740:3955822] 2-------(null)
可以看出MyPerson,1秒之后就释放了。因为第一个block对他强引用,这个block运行完之后就释放了,对MyPerson执行了release。
5. __block
改变外部变量的值:
int age = 10;
MJBlock block1 = ^{
// age = 20;
NSLog(@"age is %d", age);
};
static int a = 15;
MJBlock block2 = ^{
a = 30;
NSLog(@"a is %d", a);
};
NSObject *o = [[NSObject alloc]init];
MJBlock block3 = ^{
// o = [[NSObject alloc]init];
NSLog(@"o is %@",o);
};
block1();
block2();
block3();
会发现,注释的两句会报错。因为auto变量(不管是基本类型还是对象),捕获的值传递,在一个函数内是不能访问到另一个函数的变量的。源码中__main_block_func_1是在main函数外定义的。
static修饰的时候,不会报错。因为是指针传递。
static void __main_block_func_1(struct __main_block_impl_1 *__cself) {
int *a = __cself->a; // bound by copy
(*a) = 30;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1l_pky1p4kn4p9dcgr7cqzz7n080000gn_T_main_00301d_mi_1, (*a));
}
怎样在里面改auto变量呢?通过__block. 注意:
NSMutableArray *array = [[NSMutableArray alloc]init];
MJBlock block1 = ^{
[array addObject:@"1"];
NSLog(@"array count is %ld",array.count);
};
block1();
这种情况,编译不会报错。因为这是使用它,而不是改变它。