Block详解
block的本质
先看block的简单实现
int age = 20;
void (^block)(int) = ^(int a){
NSLog(@"this is a block! -- %d", age);
};
block(10);
转为C++代码
struct __main_block_desc_0 {
size_t reserved;
*********************************
block结构体占用的内存大小
*********************************
size_t Block_size;
};
struct __block_impl {
void *isa; isa指针
int Flags;
int Reserved;
*********************************
block内执行的代码会封装到一个函数中,FuncPtr存放函数的内存地址
*********************************
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
*********************************
block的描述
*********************************
struct __main_block_desc_0* Desc;
*********************************
持有外部变量 age
*********************************
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
*********************************
构造函数(类似OC的init)返回结构体对象
*********************************
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
*********************************
封装了block执行逻辑的函数
*********************************
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r_gswfk35n5938fbdhf6s4xw_c0000gp_T_main_22e326_mi_0, age);
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)};
查看Block的继承关系
*********************************
__NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
*********************************
void (^block)(void) = ^{
NSLog(@"Hello");
};
NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
__NSGlobalBlock__
__NSGlobalBlock
NSBlock
NSObject
结论:
block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象
block的变量捕获(capture)
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
验证:
void (^block)(void);
int weight_ = 10;
static int staticWeight_ = 10;
void test()
{
*********************************
自动变量,离开作用域就销毁, auto默认自带,可以不写
*********************************
auto int age = 10;
static int height = 10;
block = ^{
// age的值捕获进来(capture)
NSLog(@"age is %d, height is %d, weight_ is %d,staticWeight_ is %d", age, height,weight_,staticWeight_);
};
age = 20;
height = 20;
weight_ = 20;
staticWeight_ = 20;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
return 0;
}
}
打印结果
age is 10, height is 20, weight_ is 20,staticWeight_ is 20
转为C++源码如下
void (*block)(void);
int weight_ = 10;
static int staticWeight_ = 10;
*********************************
__test_block_impl_0结构体中并没有捕获全局变量
*********************************
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
int age;
int *height;
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *height = __cself->height; // bound by copy
*********************************
age是值传递
*height 是指针传递,存储的是外部变量的地址
weight_和staticWeight_参数直接调用全局变量,并没有捕获
*********************************
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r_gswfk35n5938fbdhf6s4xw_c0000gp_T_main_a117a3_mi_0, age, (*height),weight_,staticWeight_);
}
static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
test();
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
}
思考:在block里面访问self呢?
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
- (void)test;
@end
#import "Person.h"
@implementation Person
- (void)test
{
void (^block)(void) = ^{
NSLog(@"%@---%@----%@",self, self->_name, [self name]);
};
block();
}
@end
转为C++代码如下
*********************************
block捕获了self
*********************************
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
Person *self;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
Person *self = __cself->self; // bound by copy
*********************************
block通过捕获的self调用self的成员变量和方法
*********************************
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r_gswfk35n5938fbdhf6s4xw_c0000gp_T_Person_606d56_mi_0,self, (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)), ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")));
}
static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __Person__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
void (*dispose)(struct __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};
*********************************
OC转C默认传递两个参数self 和 SEL _cmd
参数就是局部变量,所以block会捕获self
*********************************
static void _I_Person_test(Person * self, SEL _cmd) {
void (*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )
每一种类型的block调用copy后的结果如下所示
验证:
将工程置于MRC下(在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上)
void test()
{
// Global:没有访问auto变量
void (^block1)(void) = ^{
};
NSLog(@"没有访问auto变量block类型---------%@",block1);
NSLog(@"copy后---------%@",[block1 copy]);
// Stack:访问了auto变量
int age = 10;
void (^block2)(void) = ^{
NSLog(@"age---------%d", age);
};
NSLog(@"访问auto变量block类型---------%@", block2);
NSLog(@"copy后-%@", [block2 copy]);
}
调用
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
}
return 0;
}
结果如下:
没有访问auto变量block类型---------<__NSGlobalBlock__: 0x1000020a8>
copy后---------<__NSGlobalBlock__: 0x1000020a8>
访问auto变量block类型---------<__NSStackBlock__: 0x7ffeefbff3e0>
copy后-<__NSMallocBlock__: 0x10051b360>
block的copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
- block作为函数返回值时
typedef void (^Block)(void);
Block myblock()
{
int age = 10;
return ^{
NSLog(@"---------%d",age);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block = myblock();
block();
NSLog(@"%@", [block class]);
}
return 0;
}
---------10
__NSMallocBlock__
- 将block赋值给__strong指针时
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
Block block = ^{
NSLog(@"---------%d", age);
};
NSLog(@"%@", [block class]);
}
return 0;
}
__NSMallocBlock__
- block作为Cocoa API中方法名含有usingBlock的方法参数时
NSArray *arr = @[];
[arr sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
}];
- block作为GCD API的方法参数时
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
block作为属性变量的写法
MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
对象类型的auto变量
-
当block内部访问了对象类型的auto变量时
如果block是在栈上,将不会对auto变量产生强引用
验证:(MRC环境下)
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (assign, nonatomic) int age;
@end
#import "Person.h"
@implementation Person
- (void)dealloc
{
[super dealloc];
NSLog(@"Person - dealloc");
}
@end
调用auto修辞的person对象,并设置断点
#import <Foundation/Foundation.h>
#import "Person.h"
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"---------%d", person.age);
};
NSLog(@"%@",[block class]);
[person release];
}
*******************************
断点处
*******************************
NSLog(@"------");
}
return 0;
}
打印如下
__NSStackBlock__
Person - dealloc
可以看到block未释放时,person对象已经被提前释放,说明block并没有对person产生强引用
- 如果block被拷贝到堆上
- 会调用block内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
验证:(ARC环境下)
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (assign, nonatomic) int age;
@end
#import "Person.h"
@implementation Person
- (void)dealloc
{
// [super dealloc];
NSLog(@"Person - dealloc");
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
// __weak Person *weakPerson = person;
block = ^{
NSLog(@"---------%d", person.age);
};
NSLog(@"%@",[block class]);
}
*******************************
断点处
*******************************
NSLog(@"------");
}
return 0;
}
打印如下
__NSMallocBlock__
可以看到block未释放时,person对象也没有释放
源码解读:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
*********************************
如果auto对象没有用_weak修辞,默认就是__strong
*********************************
Person *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
*********************************
封装了block执行逻辑的函数
*********************************
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Person *__strong person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r_gswfk35n5938fbdhf6s4xw_c0000gp_T_main_07fbbe_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}
*********************************
根据auto对象类型变量的修饰符(__strong、__weak、__unsafe_unretained)
做出相应的操作,形成强引用(retain)或者弱引用
*********************************
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*/);}
*********************************
释放引用的auto对象类型变量
*********************************
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
*********************************
block的描述,一旦访问的是对象类型,就会多了__main_block_copy_0,
和 __main_block_dispose_0函数,对对象类型变量的引用和释放
*********************************
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};
思考,如果用
__weak Person *weakPerson = person;
呢?
结果就是
__NSMallocBlock__
Person - dealloc
- 如果block从堆上移除
- 会调用block内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放引用的auto变量(release)
#import <Foundation/Foundation.h>
#import "Person.h"
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
// __weak Person *weakPerson = person;
block = ^{
NSLog(@"---------%d", person.age);
};
NSLog(@"%@",[block class]);
}
NSLog(@"------");
}
*******************************
断点处
*******************************
return 0;
}
结果如下:
__NSMallocBlock__
------
Person - dealloc
源码解读在上面第2步
__weak问题解决
在使用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
__block修饰符
__block
可以用于解决block
内部无法修改auto
变量值的问题
__block
不能修饰全局变量、静态变量(static
)
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
Block block = ^{
age = 20;
NSLog(@"age is %d", age);
};
block();
}
return 0;
}
转为底层C++代码
***************************
编译器把__block变量age包装成一个对象,转变C++ 就是__Block_byref_age_0结构体
__forwarding指针指向__Block_byref_age_0结构体
***************************
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
***************************
捕获的age的内存地址和外部变量age的内存地址是一样的
***************************
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
***************************
block内部持有的 __Block_byref_age_0 类型的age指针指向上面的结构体
这里是强引用关系
***************************
__Block_byref_age_0 *age; // by ref
...
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
***************************
block内部修改age变量的值会先找到__Block_byref_age_0结构体
->__forwarding指针->age
***************************
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r_gswfk35n5938fbdhf6s4xw_c0000gp_T_main_3520b8_mi_0, (age->__forwarding->age));
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {
(void*)0,
***************************
对应的是__Block_byref_age_0 *__forwarding;
说明*_forwarding接收的是age的内存地址
***************************
(__Block_byref_age_0 *)&age,
0,
sizeof(__Block_byref_age_0),
10
};
...
}
return 0;
}
编译器会将
__block
变量包装成一个对象
__block的内存管理
-
当block在栈上时,并不会对
__block
变量产生强引用 -
当block被copy到堆时
- 会调用block内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会对
__block
变量形成强引用(retain)
- 当block从堆中移除时
- 会调用block内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放引用的
__block
变量(release)
__block的__forwarding指针
对象类型的auto变量、__block变量
-
当block在栈上时,对它们都不会产生强引用
-
当block拷贝到堆上时,都会通过copy函数来处理它们
__block变量(假设变量名叫做a)
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的auto变量(假设变量名叫做p)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
-
当block从堆上移除时,都会通过dispose函数来释放它们
__block变量(假设变量名叫做a)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的auto变量(假设变量名叫做p)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
__block修饰的对象类型
#import <Foundation/Foundation.h>
#import "Person.h"
typedef void (^Block) (void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
Block block = ^{
NSLog(@"%p", person);
};
block();
}
return 0;
}
转为C++底层源码
*************************
__Block修辞的person对象转成C++变成了__Block_byref_person_0结构体
*************************
struct __Block_byref_person_0 {
void *__isa; 指针占用8个字节内存空间
__Block_byref_person_0 *__forwarding; 8
int __flags; 4
int __size; 4
*************************
__Block_byref_person_0结构体中持有person对象
copy和dispose函数是对person对象的引用和释放
*************************
void (*__Block_byref_id_object_copy)(void*, void*); 8
void (*__Block_byref_id_object_dispose)(void*); 8
*************************
__Block_byref_person_0结构体内存地址+40就是person的内存地址
在MRC情况下不会对person强引用
*************************
Person *person;
};
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_person_0 *person; // by ref
...
};
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, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
*************************
对__Block_byref_person_0 *person的内存管理操作
*************************
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变量在栈上时,不会对指向的对象产生强引用
-
当__block变量被copy到堆时
- 会调用
__block
变量内部的copy函数 - copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(
注意:这里仅限于ARC时会retain,MRC时不会retain
)
- 如果__block变量从堆上移除
- 会调用
__block
变量内部的dispose函数 - dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放指向的对象(release)
循环引用问题
#import <Foundation/Foundation.h>
typedef void (^Block) (void);
@interface Person : NSObject
@property (copy, nonatomic) Block block;
- (void)test;
@end
#import "Person.h"
@implementation Person
- (void)dealloc
{
********************
MRC打开 //[super dealloc];
********************
NSLog(@"%s", __func__);
}
- (void)test
{
self.block = ^{
NSLog(@"%@", self);
};
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test];
}
**********************
设置断点
**********************
NSLog(@"*****");
return 0;
}
可以看到在block
函数执行完毕后,dealloc
函数并没有执行,person
对象没有释放
关于block持有self的问题,上面从C++源码角度已经解读就不在说了
解决循环引用问题 - ARC
- 用__weak、__unsafe_unretained解决
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@", weakSelf);
};
__unsafe_unretained typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@", weakSelf);
};
__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
- 用__block解决(必须要调用block)
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
person.block = ^{
NSLog(@"%@", person);
person = nil;
};
person.block();
}
NSLog(@"*****");
return 0;
}
解决循环引用问题 - MRC
__unsafe_unretained typeof(self) weakSelf = self;
self.block = [^{
//__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@", weakSelf);
} copy];
[self release];
__block typeof(self) weakSelf = self;
self.block = [^{
//__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@", weakSelf);
} copy];
[self release];