block是什么,什么结构?
- block本质上也是一个OC对象,它内部也有一个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
那么block到底什么样子的呢?
下面这段含有block的代码,转换成底层时:
int age = 10;
void(^block)(int ,int) = ^(int a, int b){
NSLog(@"this is block,a = %d,b = %d",a,b);
NSLog(@"this is block,age = %d",age);
};
age = 20;
block(3,5);
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
int age = 10;
void(*block)(int ,int) = ((void (*)(int, int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, age));
age = 20;
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 3, 5);
}
删除其中的类型转换,就能看到
block的类型是:__ViewController__viewDidLoad_block_impl_0;
而block的调用:block->FuncPtr(block , 3, 5)
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
int age;
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
}
用图来说明就是:
block的变量捕获
提前补充一点不常为人知的:
通常情况下,局部变量自带auto,但大都隐藏不写; 比如 int age = 20; 等同于 auto int age = 20;
所以block捕获的变量分以下情况:
-
auto 修饰的变量,只是变量的值被block捕获而已,外界再修改变量,block内部无法同步;
-
static修饰的变量,该变量的内存地址被block捕获,外界修改变量时,block内部访问的是同一地址,所以也会被修改
-
全局变量:写在方法外面的变量;block不用捕获,可直接拿到地址访问
如果是auto修饰的局部变量:
如果是static修饰的局部变量:
auto int a = 10;
static int b = 11;
void(^block)(void) = ^{
NSLog(@"hello, a = %d, b = %d", a,b);
};
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
int a;
int *b;
};
b就是被static修饰后的,被block捕获后,在block里面是int *b;,说明捕获的是指针地址
如果是全局变量:
static int c = 10;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
void(^block)(void) = ^{
NSLog(@"hello, c = %d", c);
};
block();
}
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
};
结构体里根本没有变量c或它的指针,说明全局变量不会被捕获。
多说一点,我们平时使用的self是如何捕获的呢?
对于self来说,block会将self捕获到block内部
以Person为例:
@implementation Person
- (void)personTest
{
void (^block)(void) = ^{
NSLog(@"%s %p",__func__,self);
};
block();
}
@end
struct __Person__personTest_block_impl_0 {
struct __block_impl impl;
struct __Person__personTest_block_desc_0* Desc;
Person *self;
};
可以看出,block会捕获self
原因:self是局部变量; 每个方法都有默认的两个参数,
- (void)test{
NSLog(@"test");
}
等同于
- (void)test(Person *self ,SEL _cmd) {
NSLog(@"test");
}
转成C++时:
void test(Person *self , SEL _cmd){
NSLog(@"test");
}
block的类型
上面说到,block本质上就是OC对象,是因为有isa指针,那么他继承于谁呢?
void (^block)(int ,int ) = ^(int a, int b){
NSLog(@"this is a block !");
};
NSLog(@"%@",[block class]);
NSLog(@"%@",[[block class] superclass]);
NSLog(@"%@",[[[block class] superclass] superclass]);
NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
打印:
__NSMallocBlock__
__NSMallocBlock
NSBlock
NSObject
说明:
__NSMallocBlock__ : __NSMallocBlock : NSBlock : NSObject
block是对象,isa是继承与NSObject
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
内存分布如下:
程序区域:主要存放函数的二进制代码等(代码段)
数据区域:全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后有系统释放
堆区域: 由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收 ,比如在iOS 中 alloc 都是存放在堆中
栈区域:由编译器自动分配并释放,存放函数的参数值,局部变量等。
栈空间分静态分配和动态分配两种。静态分配是编译器完成的,比如自动变量(auto)的分配。动态分配由alloca函数完成
那如何区分block的类型呢?
在MRC环境下:(ARC环境编译器会做一些其他操作)
第一种:
void(^block1)(void) = ^{
NSLog(@"没有访问变量");
};
block1();
NSLog(@"%@",[block1 class]);
打印:
没有访问变量
__NSGlobalBlock__
第二种:
auto int a = 10;
void(^block2)(void) = ^{
NSLog(@"访问auto变量, a = %d,", a);
};
block2();
NSLog(@"%@",[block2 class]);
打印:
访问auto变量, a = 10,
__NSStackBlock__
第三种:
NSLog(@"%@",[[block2 copy] class]);
打印:__NSMallocBlock__
GlobalBlock 、StatckBlock 的销毁管理由系统处理;
MallocBlock的销毁由开发人员处理;
每一种类型的block调用copy后的结果如下所示:
因为在堆上的block才是开发人员管理的,而且现在基本都是在ARC环境中开发,编译器会自动做了一部分工作(主要是将栈上的block拷贝到堆上),那就看看block中的copy.
block中的copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上, 比如以下情况:
1、block作为函数返回值时
2、将block赋值给__strong指针时
3、block作为Cocoa API中方法名含有usingBlock的方法参数时
4、block作为GCD API的方法参数时
MRC下block属性的建议写法:
@property (copy, nonatomic) void (^block)(void);
ARC下block属性的建议写法:
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
对象类型的auto变量
1、当block内部访问了对象类型的auto变量时,如果block是在栈上,将不会对auto变量产生强引用
(因为当block在栈上的时候,他自己都不能保证自己什么时候被释放,所以block也就不会对自动变量进行强引用了)
2、如果block被拷贝到堆上,会调用block内部的copy函数,
copy函数内部会调用_Block_object_assign函数,
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
3、如果block从堆上移除,会调用block内部的dispose函数,
dispose函数内部会调用_Block_object_dispose函数,
_Block_object_dispose函数会自动释放引用的auto变量(release)
4、在多个block相互嵌套的时候,auto属性的释放取决于最后的那个强引用什么时候释放
typedef void (^Block)(void);
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Block block;
{
Person *p = [[Person alloc]init];
block = ^{
NSLog(@"%s p= %@",__func__,p);
};
[p release];
}
block();
NSLog(@"%s ",__func__);
}
在MRC下,p释放后,才会去执行block中的打印,因为p被释放了,所以会出现崩溃。
在ARC下,打印如下:
-[ViewController viewDidLoad]_block_invoke p= <Person: 0x600002cdce
-[ViewController viewDidLoad]
-[Person dealloc]
转成C++后,如下:
//block的底层结构
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
Person *p;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, Person *_p, int flags=0) : p(_p) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block中的 block_desc_0的结构
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
}
//copy函数
static void __ViewController__viewDidLoad_block_copy_0(
struct __ViewController__viewDidLoad_block_impl_0*dst,
struct __ViewController__viewDidLoad_block_impl_0*src) {
_Block_object_assign((void*)&dst->p,
(void*)src->p,
3/*BLOCK_FIELD_IS_OBJECT*/);
}
//dispose函数
static void __ViewController__viewDidLoad_block_dispose_0(
struct __ViewController__viewDidLoad_block_impl_0*src) {
_Block_object_dispose((void*)src->p,
3/*BLOCK_FIELD_IS_OBJECT*/);
}
补充:
我们运行 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m出错了,
我们需要支持ARC,指定运行时系统版本,xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
block的修饰符
__block
- __block可以用于解决block内部无法修改auto变量值的问题
- __block不能修饰全局变量、静态变量(static)
- 编译器会将__block变量包装成一个对象
举例解释:
typedef void (^Block)(void);
__block int age = 10;
Block block = ^{
NSLog(@"%s age = %d",__func__,age);
};
block();
如果删除age的修饰符__block,直接在block中修改age,Xcode会直接报错,Variable is not assignable (missing __block type specifier); block内部不能直接修改age的值
我们把代码转化为C++代码,然后在age使用__Block前后,对Block结构体进行分析:
在__block所起到的作用就是只要观察到该变量被 block 所持有之后,age其实变成了OC对象,里面含有isa指针
如果在block中修改age,就会通过__main_block_impl_0找到其内部的__Block_byref_age_0 *age,然后找到结构体__Block_byref_age_0,然后对结构体中的age做出修改
通过NSLog就可以看出:
NSLog(@"%s age = %d",__func__,age);
NSLog(
(NSString*)&__NSConstantStringImpl__var_folders____qf7vv781595b7dhbc7f1cj4c0000gn_T_ViewController_5492a8_mi_2,
__func__,
(age->__forwarding->age)
);
最后是age->__forwarding->age。
block由栈上拷贝到堆上,结构体__Block_byref_age_0中的forwarding指针指向大致如图:
__block的内存管理
1、当block在栈上时,并不会对__block变量产生强引用
2、当block被copy到堆时, 会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会对__block变量形成强引用(retain)
3、当block从堆中移除时,会调用block内部的dispose函数,
dispose函数内部会调用_Block_object_dispose函数,
_Block_object_dispose函数会自动释放引用的__block变量(release)
总结
1、在ARC环境下,Block被引用的时候,会被Copy一次,由栈区copy到了堆
2、在Block被copy的时候,Block内部被引用的变量也同样被copy一份到了堆上面
3、被__Block修饰的变量,在被Block引用的时候,会变成结构体也就是OC对象,里面的__forwarding也会由栈copy到堆上面
4、栈上__block变量结构体中__forwarding的指针指向堆上面__block变量结构体,堆上__block变量结构体中__forwarding指针指向自己
5、当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的__block变量(release)
block的循环引用
在auto变量为OC对象的时候,在没有修饰符修饰的时候Block内部会强引用OC对象,而对象如果也持有Block的时候就会造成相互引用,也就是循环引用的问题。
@interface Person : NSObject
@property(nonatomic ,copy)void(^block)(void);
- (void)personTest;
@end
@implementation Person
- (void)personTest
{
self.block = ^{
NSLog(@"%s %@",__func__,self);
};
}
@end
Person *person = [[Person alloc]init];
[person personTest];//产生循环引用
当在别处调用Person的personTest时,就会产生循环引用:person拥有属性block,而在block内部也引用着self,所以产生了循环引用。
大致如图:
解决这种问题:
第一种方式:(ARC下)
__weak typeof(self) weakSelf = self;
self.block = ^{
printf("%p",weakSelf);
};
第二种方式:(MRC)
__unsafe __unretained id weakSelf = self;
self.block = ^{
NSLog(@"%p",weakSelf);
};
转成底层C++就是弱引用了
struct __Person__personTest_block_impl_0 {
struct __block_impl impl;
struct __Person__personTest_block_desc_0* Desc;
id weakSelf;
};
补充一下:__unsafe __unretained用在MRC下,MRC下没有__weak
还有一种解决方式:用__block解决(必须要调用block)
__block id weakSelf = self;
self.block = ^{
printf("%p",weakSelf);
weakSelf = nil;
};
self.block();
struct __Person__personTest_block_impl_0 {
struct __block_impl impl;
struct __Person__personTest_block_desc_0* Desc;
__Block_byref_weakSelf_0 *weakSelf; // by ref
};
struct __Block_byref_weakSelf_0 {
void *__isa;
__Block_byref_weakSelf_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
id weakSelf; //指向外面的person对象
};
这种方式解决是因为有中间的__Block_byref_weakSelf_0
person对象拥有block,block内部拥有__Block_byref_weakSelf_0这个结构体,__Block_byref_weakSelf_0内部又有一个指针id weakSelf来指向person对象,因此就形成了一个循环引用的环。
大致如下图:
解决循环引用就是打破这个环,调用block之后,将weakSelf = nil;就是将__Block_byref_weakSelf_0内部的指
针id weakSelf置为nil,就能打破这个环了,就能解除循环引用问题。
大致如下图:
以上若有错误,欢迎指正。转载请注明出处。