iOS进阶 - Block底层原理
一 block的本质
1 block本质上也是一个oc对象,它内部也有一个isa指针
2 block是封装了函数调用以及函数调用环境的oc对象
3 block的底层结构
Block源码转换查看
block在实际编译时无法转换成我们能够理解的源代码,但可以通过clang(LLVM编译器)转换成可读的源代码,步骤如下:
1打开终端,输入cd 把要转换的文件拖到终端,然后回车进入要转换文件的目录;
2输入命令clang -rewrite-objc main.m -o main.cpp
二 block的变量捕获 capture
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
变量类型 捕获到block内部 访问方式
局部变量 auto 会捕获 值传递
static 会捕获 指针传递
全局变量
不用捕获
直接访问
三 block类型
block有三种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
__NSGlobalBlock__
__NSStackBlock__
__NSMallocBlock__
如何判断具体属于什么类型的block呢?
block类型 环境
NSGlobalBlock 没有访问auto变量
NSStackBlock 访问了auto变量
NSMallocBlock NSStackBlock 调用了copy
每一种类型的block调用copy后的
block类型 存储区域 调用copy后的复制效果
NSStackBlock 栈 从栈复制到堆
NSGlobalBlock 程序的数据区域data 什么也不做
NSMallocBlock 堆 引用计数增加
全局block调用copy后仍然是全局globalBlock
注意:
这是因为编译器默认在ARC环境下,应该切换到MRC环境下,看一下真正的block类型
将编译器改为去除ARC,在Build Settings -> automatic Reference Counting 中将ARC改为MRC
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,如:
block作为函数返回值时 如:Masonry
block赋值给__strong指针时
block作为Cocoa API中方法名有usingBlock的方法参数时 如[array enumeratorObjectsUsingBlock]
block作为GCD API的方法参数时
四 对象类型的auto变量
总结:
当block内部访问了对象类型的auto变量时
1 如果block在栈上,将不会堆auto变量产生强引用
2 如果block被拷贝到堆上
1)会调用block内部的copy函数
2)copy函数内部会调用Block_object_assign函数
3)Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,l类似于retain(形成强引用或者弱引用)
3 如果block从堆上移除
1)会调用block内部的dispose函数
2)dispose函数内部会调用Block_object_dispose函数
3)_Block_object_dispose函数会自动释放引用的auto变量。类似于release
函数 调用时机
copy函数 栈上的block复制到堆上时
dispose函数 堆上的block被废弃时
__weak问题解决
使用clang转换oc为c++代码时,可能会遇到以下问题
Cannot create __weak reference in file using manual reference
解决:支持ARC、指定运行时系统版本,如:
xcrun -sdk iphooneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0 main.m
五 __block修饰符
1 __block的底层原理
__block可以用于解决block内部无法修auto变量的问题
__block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象
__forwarding指针指向自己
age – __forwarding – age
block内部有个__Block_byref_age_0
类型的指针age,指向结构体__Block_byref_age_0
结构体内部的forwarding指向自己,拿到结构体内部的age
2 __block的内存管理
当block在栈上时,并不会对__block变量产生强引用
当block被copy到堆上时
1会调用block内部的copy函数
2 copy函数内部会调用__Block_object_assign函数
3 _Block_object_assign函数会对__block变量形成强引用(retain)
⚠️注意:区分于auto对象类型,对象类型copy时要根据对应的属性修饰符
当block从堆中移除时
1 会调用block内部的dispose函数
2 dispose函数内部会调用_Bloock_object_dispose函数
3 _Block_object_dispose函数会自动释放引用的_block变量(release)
3 对象类型的auto变量、__block变量
当block在栈上时,对他们都不会产生强引用
当block拷贝到堆上时,都会通过copy函数来处理它们
1 __block变量(假设变量名叫做age)
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/BLOCK_FIELD_IS_BYREF/);
2 对象类型的auto变量(假设变量名叫做obj)
_Block_object_assign((void*)&dst->obj,(void*)src->obj,3/BLOCK_FIELD_IS_OBJECT/);
当block从堆上移除时,都会通过dispose函数来释放它们
1 __block变量(假设变量名叫做a)
_Block_object_dispose((void*)src->age, 8/BLOCK_FIELD_IS_BYREF/);
2 对象类型的auto变量(假设变量名叫做p)
_Block_object_dispose((void*)src->obj, 3/BLOCK_FIELD_IS_OBJECT/);
对象 BLOCK_FIELD_IS_OBJECT
__block变量 BLOCK_FIELD_IS_BYREF
区别:
在堆上时,Block_object_assign
__block变量都是强引用,对象变量要根据其外部定义时的强弱来决定
4 被__block修饰的对象类型
当__block 变量在栈上时,不会对指向的对象产生强引用
当__block变量被copy到堆上时
1 会调用__block变量内部的copy函数
2 copy函数内部会调用_Block_object_assign函数
3 _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)作出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)
如果__block变量从堆上移除
1 会调用__block变量内部的dispose函数
2 dispose函数内部会调用_Block_object_dispose函数
3 _Block_object_dispose函数会自动释放指向的对象(release)
__block MYPerson *p = [MYPerson alloc] init];
区别于上面3中,添加__block修饰后,block内部的person指针,先指向__block包装的结构体,然后结构体中的person指针再指向person对象
MYPerson *p = [[MYPerson alloc] init];
__block __weak MYPerson *weakPerson = p;
MYBlock block = ^{
NSLog(@"---- %@",weakPerson);
};
+40:是__Block_byref_weakPerson_0的地址加40到weakPerson这个指针的地址
六 循环引用
简单举例:以下情况会造成循环引用
person.block = ^{
//循环引用
NSLog(@“age is %d”, person.age);
};
1 解决循环引用问题 - ARC
用__weak __unsafe__unretained 解决
__weak: 不会产生强引用 指向的对象销毁时,会自动让指针置为nil
__unsafe_unretained: 不会产生强引用,不安全,指向的对象销毁时,指针存储的地址不变,不会自动置为nil
用 __block 解决(必须调用block)
2 解决循环引用问题 - MRC
用 _unsafe__unretained 解决 (MRC不支持)
用 __block 解决(必须调用block)
七 相关问题
block的原理是怎样的?本质是什么?
答:block是封装了了函数调用和函数调用环境的OC对象,内部也有一个isa指针
__block的作用是什么?有什么使用注意点?
答:__bloock可以将变量包装成一个对象,解决block内部无法修改auto变量值的问题,通过block内部的__block_byref_变量_0 指针去访问这个变量的结构体对象,达到修改auto变量值的效果
block的属性修饰符词为什么是copy?使用block有哪些注意点?
答:没有copy的时候,block是在栈区的,释放时机不可控,进行copy操作,拷贝到堆上后,可以对其进行内存管理;
使用注意:循环引用问题
解决:__weak __unsafe_unretained __block
__weak typeof(self) weakSelf = self;
注意:在bloock内部,使用weakSelf,有时如延时函数情况下,self在使用时已经释放了,为了保证在block内部任务执行过程中self的存在,需要用__strong
__strong typeof(weakSelf) strongSelf = weakSelf;
其他:在ARC下,strong和copy效果一样,MRC下,copy会拷贝到堆上,strong不会
block在修改NSMutableArray,需不需要添加__block?
答:不需要,只是addObject
注意:如果mutArr = nil,是不行的