- Block又称为代码块,匿名函数,函数指针,下面来详细介绍Block的相关内容;
Block的声明定义
【第一种:在property属性中定义block】
- 定义的是具有特定参数与返回值类型的block变量,属于定义block变量;
@interface ViewController ()
//定义了一个block变量testOne,类型为void(^)(void)
@property(nonatomic,copy)void(^testOne)(void);
//定义了一个block变量testTwo,类型为void(^)(int)
@property(nonatomic,copy)void(^testTwo)(int a);
//定义了一个block变量testThree,类型为NSString *(^)(int,int)
@property(nonatomic,copy)NSString *(^testThree)(int a,int b);
@end
【第二种:使用typedef定义block类型】
- 定义的是具有特定参数与返回值类型的block,属于定义block类型;
//定义了一个YYBlock类型
typedef void(^YYBlock)(void);
//定义了一个XXBlock类型
typedef void(^XXBlock)(int a);
//定义了一个ZZBlock类型
typedef NSString *(^ZZBlock)(int a,int b);
【第三种:在函数内部定义局部block变量并初始化其代码块的实现】
- 定义的是block变量且初始化了代码块;
- (void)viewDidLoad {
[super viewDidLoad];
//定义了一个block变量add,类型为void(^)(void)
void(^add)(void) = ^{
NSLog(@"add");
};
//定义了一个block变量minus,类型为void(^)(int)
void(^minus)(int) = ^(int a){
NSLog(@"%d",a);
};
//定义了一个block变量multiply,类型为int(^)(int,int)
int(^multiply)(int,int) = ^(int a,int b){
int c = a * b;
NSLog(@"%d",c);
return c;
};
//block代码块的调用
add();
minus(10);
multiply(10,5);
}
- 等号左边是block变量名+block类型;
- 等号右边是block代码块初始化实现;
- 等号左边在定义block类型时,形参的参数名可以省略,只保留形参的类型,从这里可以看出block与函数指针十分相似;
【第四种:block作为函数方法的参数】
- 这里引用
AFNetworking
中发送网络请求的函数定义;
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
-
success
与failure
是block变量名,其作为函数参数;success
的block类型为
(void (^)(NSURLSessionDataTask *task, id responseObject))
- 外界业务层调用此网络层函数,业务层会初始化block变量success的代码块,网络层在发送网络请求获取网络数据之后,会调用success代码块,并传入task与responseObject两个参数;
- block作为函数参数时,block中的参数名通常要写上,便于理解参数的含义;
Block类型
-
block通常有三种类型,分别如下:
- 【第一种:
__NSGlobalBlock__
】:全局区block,存储在全局区; - 【第二种:
__NSMallocBlock__
】:堆区block,因为block既是函数,也是对象; - 【第三种:
__NSStackBlock__
】:栈区block,存储在栈区;
- 【第一种:
新建
C语言工程
,配置成MRC环境:设置target --> Build Setting --> 搜索gar --> Objective-C Automatic Reference Counting - NO
,LLDB调试如下:
- 在MRC环境下的总结如下:
- 全局block变量 访问外界变量 NSGlobalBlock
- 全局block变量 未访问外界变量 NSGlobalBlock
- 局部block变量 未访问外界变量 NSGlobalBlock
- 局部block变量 访问外界局部变量 NSStackBlock
- 局部block变量 访问外界全局变量 NSGlobalBlock
- 将C语言工程设置成ARC环境,LLDB调试如下:
-
在ARC环境下的总结如下:
- 全局block变量 访问外界变量 NSGlobalBlock
- 全局block变量 未访问外界变量 NSGlobalBlock
- 局部block变量 未访问外界变量 NSGlobalBlock
- 局部block变量 访问外界局部变量 NSMallocBlock
- 局部block变量 访问外界全局变量 NSGlobalBlock
比较MRC与ARC两种环境,可以看出
当局部 block变量在访问外界局部变量时
,block变量会从栈区拷贝(copy)到堆区
即__NSStackBlock__ --> __NSMallocBlock__
;我们知道一个引用在默认情况下是强引用即strong,若在ARC环境下用弱引用weak修饰block变量时,其访问外界局部变量,依然是栈区block,并没有拷贝到堆区;
-
最终总结针对ARC环境,局部block变量(最常见):
- 创建的block为空实现时,默认存储在全局区,即全局区block;
- 如果block访问外界局部变量时:
- 如果此时的block是强引用(默认强引用),则block存储在堆区,即堆区block;
- 如果此时的block通过__weak修饰即弱引用,则block存储在栈区,即栈区block;
现看一案例代码,在MRC情况下:
#import <Foundation/Foundation.h>
void(^block)(void);
void test(){
int age = 10;
block = ^{
NSLog(@"age = %d",age);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
}
return 0;
}
- 在test函数中定义全局的block变量,且block访问了外界局部变量a,由于是在MRC环境下,所以此block是在栈区分配内存;
- 当test函数执行完,block就会被回收,当下面再调用block时,会出现数据错乱的问题;
- 若在ARC环境下,系统默认会将访问了外界局部变量的block从栈区拷贝到堆区,那么再调用block时,就不会出现数据错乱的问题;
Block的copy操作
- 不同类型block的copy操作如下:
- 在ARC环境下,系统会对MRC环境下的栈区block做默认的copy操作;
- 在ARC环境下,用强指针指向栈区block,会将其copy到堆区;
- 在ARC环境下,栈区block作为返回值时,会将其copy到堆区;
- 在ARC环境下,栈区block作为方法名含有
usingBlock
的方法参数时,会将其copy到堆区; - 在ARC环境下,栈区block作为GCD函数的参数时,会将其copy到堆区;
Block循环引用
- 废话不多说先上案例如下所示:
第一个例子:控制器NextVC持有block,对block强引用,block内部出现self,即对NextVC强引用,NextVC与block彼此之间强引用,形成强引用环即循环引用,两者都不能释放,造成内存泄漏;
第二个例子:animation的block对self(NextVC)强引用,只是单方面的强引用,没有形成循环引用,不会造成内存泄漏;
-
解决循环引用的常见方案有以下几种方式:
- 【方式一】使用弱引用__weak修饰符;
- 【方式二】__block修饰对象(需要注意的是在block内部需要置空对象,而且block必须调用);
- 【方式三】传递对象self作为block的参数,提供给block内部使用;
- 【方式四】使用NSProxy;
下面来详细介绍着几种方式:
方式一:使用弱引用__weak修饰符
- 当block内部为嵌套block时,直接使用__weak修饰符即可;
- self与weakSelf指向的是同一块内存,即两者是同一个对象;
- block对weakSelf不会强引用,从而打破了强引用环,不会造成内存泄漏;
- 当block内部嵌套block,需要同时使用__weak 和 __strong;
- self.block内部一定不能出现self,出现就会对self强引用,造成循环引用;
- strongSelf是一个临时变量,在self.block的作用域内,即内部block执行完就释放strongSelf;
方式二:__block修饰变量
- 注意的是这里的block必须调用,如果不调用block,vc就不会置空,那么依旧是循环引用,self和block都不会被释放;
方式三:传递对象self作为block的参数
- 将对象self作为block参数,提供给block内部使用,不会有引用计数问题;
方式四:使用NSProxy(这里只做简单介绍)
- 首先来介绍一下NSProxy类;
- NSProxy 和 NSObject是同级的一个类,也可以说是一个虚拟类,只是实现了NSObject的协议;
- NSProxy 其实是一个消息重定向封装的一个抽象类,类似一个代理人,中间件,可以通过继承它,并重写下面两个方法来实现消息转发到另一个实例;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
NSProxy的使用场景
实现多继承功能;
解决了NSTimer&CADisplayLink创建时对self强引用问题,参考YYKit的YYWeakProxy;
下面通过代码实例来实现上述的两种场景:
定义三个类
YYProxy
,YYStudent
,YYTeacher
代码实现如下:
#import <Foundation/Foundation.h>
@interface YYProxy : NSProxy
- (id)transformObjc:(NSObject *)objc;
+ (instancetype)proxyWithObjc:(id)objc;
@end
#import "YYProxy.h"
@interface YYProxy ()
@property(nonatomic,weak,readonly)NSObject *objc;
@end
@implementation YYProxy
+ (instancetype)proxyWithObjc:(id)objc{
return [[self alloc] transformObjc:objc];
}
- (id)transformObjc:(NSObject *)objc{
_objc = objc;
return self;
}
//1.查询该方法的方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
NSMethodSignature *signature;
if (self.objc) {
signature = [self.objc methodSignatureForSelector:sel];
}else{
signature = [super methodSignatureForSelector:sel];
}
return signature;
}
//2.有了方法签名之后就会调用方法实现
- (void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = [invocation selector];
if ([self.objc respondsToSelector:sel]) {
[invocation invokeWithTarget:self.objc];
}
}
- (BOOL)respondsToSelector:(SEL)aSelector{
return [self.objc respondsToSelector:aSelector];
}
@end
#import <Foundation/Foundation.h>
@interface YYStudent : NSObject
- (void)study;
@end
#import "YYStudent.h"
@implementation YYStudent
- (void)study{
NSLog(@"%s",__func__);
}
@end
#import <Foundation/Foundation.h>
@interface YYTeacher : NSObject
- (void)edcuate;
@end
#import "YYTeacher.h"
@implementation YYTeacher
- (void)edcuate{
NSLog(@"%s",__func__);
}
@end
- 通过YYProxy实现多继承功能;
YYProxy类获取了YYStudent与YYTeacher类中的功能方法,主要是通过消息的慢速转发实现的,原理在 iOS底层系列14 -- 消息流程的动态方法决议与转发这篇文章中有详细阐述,YYProxy由于没有目标方法实现,将其转发给YYStudent与YYTeacher类;
通过YYProxy解决定时器中self的强引用问题;
- 将定时器的target指定为YYProxy对象,然后YYProxy对象再将定时器的回调消息函数转发给当前控制器;
- 在当前控制器销毁时,一定要销毁定时器,从而释放YYProxy对象;
Block导致循环引用的总结
- 循环应用的解决方式从根本上来说就两种,以self -> block -> self为例:
-
打断self 对 block的强引用
,block属性修饰符使用weak,但是这样会导致block还未创建完就释放了,所以从这里打断强引用行不通; -
打断block对self的强引用
,主要就是self的作用域和block作用域的通讯,通讯有代理、传值、通知、传参等几种方式,用于解决循环,常见的解决方式如下:- weak-strong-修饰符;
- __block(block内对象置空,且调用block);
- 将对象self作为block的参数;
- 通过NSProxy的子类代替self;
Block的底层结构
- 在C语言工程的main.文件中,定义一个block变量,且调用block;
- cd 到指定文件夹 输入
clang -rewrite-objc main.m -o main.cpp
即将main.m文件编译成main.cpp文件,底层C++中的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;
}
};
//block中代码块 封装在当前函数中
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//bound by copy
int a = __cself->a;
printf("YY a = %d", 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 main(int argc, const char * argv[]) {
/* @autoreleasepool */
{
__AtAutoreleasePool __autoreleasepool;
int a = 10;
//block的定义
void(*TestTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
//block的调用
((void (*)(__block_impl *))((__block_impl *)TestTwo)->FuncPtr)((__block_impl *)TestTwo);
}
return 0;
}
block中定义的代码块在底层是封装在
__main_block_func_0
C语言函数中;可以看到block本质是
__main_block_impl_0
结构体;定义block时,底层是调用
__main_block_impl_0
结构体的同名函数__main_block_impl_0()
,第一个参数传入是__main_block_func_0
函数指针,也就是block中代码块实现,然后将参数的函数指针赋值给成员变量impl
的FuncPtr
成员;block的调用,最终也是通过
__main_block_impl_0
结构体的成员impl
的FuncPtr
函数指针来调用block代码块;由于
impl
是__main_block_impl_0
结构体的第一个成员,那么impl
的内存地址就是__main_block_impl_0
结构体的内存地址,所以在调用block时,可以将__main_block_impl_0
结构体强转成impl
然后调用FuncPtr
函数指针指向的函数,也就是block中的代码块;block捕获外界局部变量时,从底层我们看到在
__main_block_impl_0
结构体中有一个int类型成员变量a,其就是用来接收捕获的外界变量值的,在__main_block_func_0
函数中完成接受赋值操作,也就是说block在捕获外界变量时,在其底层结构体内部会自动生成同名成员变量来保存;上述捕获的外界局部变量,在block底层结构体中会自动生成一个同名成员变量保存其值,
属于值传递
,也就是说block内部捕获的变量与外界的变量是两块不同的内存地址
,所以在外界更改变量的值是不会影响到block内部捕获到的变量的值的;
其次在block内部是不能更改捕获变量的值的,因为内外变量的内存地址不同;
下面尝试捕获全局变量,静态变量,看看底层结构体如何操作:
block访问全局变量,其底层结构体并
没有自动生成同名的成员变量
,表明block不会捕获全局变量,至于block代码块中出现全局变量的赋值,是因为全局变量可以在当前文件中如何地方都可访问到;block访问静态局部变量时,其底层结构体会自动生成一个同名指针的成员,属于指针传递,所以在block内部可以更改捕获的外界变量的值;
-
总结:
-
block能捕获外界局部变量
,block底层结构体会自动生成同名的成员变量来接收外界局部变量的值,但在block内部不能修改外界局部变量的值,因为是值传递
,内外内存地址不同; -
block能捕获外界静态局部变量
,其底层结构体会自动生成同名的成员变量来接收,且在block内部可以修改外界局部变量的值,因为是地址的传递
,内外内存地址相同; -
block不能捕获外界全局变量
,block底层结构体不会自动生成同名的成员变量来接收,但在block内部可以修改全局变量的值,因为全局变量是全局
的可以直接访问;
-
由上面的内容我们知道,在block内部是不能修改局部变量的,但是可以修改静态局部变量,那么如何修改局部变量呢?接下来就要引出__block修饰符;通过__block修饰符我们可以在block内部修改局部变量;
__block修饰符的底层实现
- 先上代码如下所示:
- 编译之后的底层代码:
struct __Block_byref_m_0 {
void *__isa;
__Block_byref_m_0 *__forwarding;
int __flags;
int __size;
int m;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_m_0 *m; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_m_0 *_m, int flags=0) : m(_m->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_m_0 *m = __cself->m;
(m->__forwarding->m) = 20;
printf("YY m = %d",(m->__forwarding->m));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->m, (void*)src->m, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->m, 8/*BLOCK_FIELD_IS_BYREF*/);}
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 main(int argc, const char * argv[]) {
{ __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_m_0 m = {(void*)0,(__Block_byref_m_0 *)&m, 0, sizeof(__Block_byref_m_0), 10};
void(*TestTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_m_0 *)&m, 570425344));
((void (*)(__block_impl *))((__block_impl *)TestTwo)->FuncPtr)((__block_impl *)TestTwo);
}
return 0;
}
- __block修饰的外界局部变量,在底层结构中会生成
__Block_byref_m_0
结构体,此结构体会保存局部变量的值和指针
,且其内部还有一个__forwarding
指针指向它自己; - 在block内部捕获的不是__block变量的值,而是捕获__block变量的地址,通过地址访问内存修改其值;
- 打印出__block修饰的局部变量m与内部捕获的变量m的地址,如下所示:
- 可以看出__block修饰的局部变量存储在栈区,而内部捕获到的局部变量存储在堆区;
- block访问__block修饰局部变量,其底层结构体之间的关系如下:
- __block修饰的局部变量,在底层会被包装成一个
__Block_byref_age_0
结构体,此结构体有一个__forwarding
成员是指向自己的,其分析如下:
- 当block还在栈区时,__forwarding指向的是栈区的__block变量的底层结构体;
- 当block拷贝到堆区时,栈区的__forwarding指向堆区的__block变量的底层结构体,堆区的__forwarding也是指向__block变量的底层结构体,保证通过__forwarding指针访问的一定是堆区的__block变量;
block底层源码实现
- 在定义block处加上断点,然后进入汇编页面看到如下:
- 在创建block时会调用
objc_retainBlock
函数; - 在工程中配置
符号断点 objc_retainBlock
,LLDB调试如下:
- 再在工程中配置
符号断点 _Block_copy
,LLDB调试如下:
- 可以看到底层执行的是
libsystem_blocks.dylib
中的_Block_copy
函数; - 到苹果开源网站下载 libclosure-78 源码,全局搜索
_Block_copy
,结果如下所示:
- 看到
Block_layout
结构体,它才是block的真正类型,Block_layout
结构如下:
// Block 结构体
struct Block_layout {
//指向表明block类型的类
void *isa;//8字节
//用来作标识符的,类似于isa中的位域,按bit位表示一些block的附加信息
volatile int32_t flags; // contains ref count 4字节
//保留信息,可以理解预留位置,用于存储block内部变量信息
int32_t reserved;//4字节
//函数指针,指向具体的block实现的调用地址
BlockInvokeFunction invoke;
//block的附加信息
struct Block_descriptor_1 *descriptor;
// imported variables
};
- isa:表明block是一种class类;
- flags:标识符,按bit位表示一些block的附加信息,类似于isa中的位域,其中flags的种类有以下几种,主要重点关注BLOCK_HAS_COPY_DISPOSE 和 BLOCK_HAS_SIGNATURE, BLOCK_HAS_COPY_DISPOSE 决定是否有 Block_descriptor_2,BLOCK_HAS_SIGNATURE 决定是否有 Block_descriptor_3;
- 第1 位 - BLOCK_DEALLOCATING,释放标记,-般常用 BLOCK_NEEDS_FREE 做 位与 操作,一同传入 Flags , 告知该 block 可释放。
- 第16位 - BLOCK_REFCOUNT_MASK,存储引用计数的值,是一个可选用参数;
- 第24位 - BLOCK_NEEDS_FREE,低16是否有效的标志,程序根据它来决定是否增加或是减少引用计数位的 值;
- 第25位 - BLOCK_HAS_COPY_DISPOSE,是否拥有拷贝辅助函数(a copy helper function);
- 第26位 - BLOCK_IS_GC,是否拥有 block 析构函数;
- 第27位,标志是否有垃圾回收;//OS X
- 第28位 - BLOCK_IS_GLOBAL,标志是否是全局block;
- 第30位 - BLOCK_HAS_SIGNATURE,与 BLOCK_USE_STRET 相对,判断当前 block 是否拥有一个签名。用于 runtime 时动态调用。
// flags 标识
// Values for Block_layout->flags to describe block objects
enum {
//释放标记,一般常用于BLOCK_BYREF_NEEDS_FREE做位与运算,一同传入flags,告知该block可释放
BLOCK_DEALLOCATING = (0x0001), // runtime
//存储引用引用计数的 值,是一个可选用参数
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
//低16位是否有效的标志,程序根据它来决定是否增加或者减少引用计数位的值
BLOCK_NEEDS_FREE = (1 << 24), // runtime
//是否拥有拷贝辅助函数,(a copy helper function)决定block_description_2
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
//是否拥有block C++析构函数
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
//标志是否有垃圾回收,OSX
BLOCK_IS_GC = (1 << 27), // runtime
//标志是否是全局block
BLOCK_IS_GLOBAL = (1 << 28), // compiler
//与BLOCK_HAS_SIGNATURE相对,判断是否当前block拥有一个签名,用于runtime时动态调用
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
//是否有签名
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
//使用有拓展,决定block_description_3
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
- reserved:保留信息,可以理解预留位置,猜测是用于存储block内部变量信息;
- invoke:是一个函数指针,指向block的执行代码;
- descriptor:block的附加信息,比如保留变量数、block的大小、进行copy或dispose的辅助函数指针,有三类如下所示:
- Block_descriptor_1是必选的;
- Block_descriptor_2 和 Block_descriptor_3都是可选的;
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;//保留信息
uintptr_t size;//block大小
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;//拷贝函数指针
BlockDisposeFunction dispose;
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;//签名
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT 布局
};
- 以上关于descriptor的可以从其构造函数中体现,其中Block_descriptor_2和Block_descriptor_3都是通过Block_descriptor_1的地址,经过内存平移得到的;
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;//默认打印
}
#endif
// CJL注释:Block 的描述 : copy 和 dispose 函数
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;//descriptor_1的地址
desc += sizeof(struct Block_descriptor_1);//通过内存平移获取
return (struct Block_descriptor_2 *)desc;
}
//Block 的描述 : 签名相关
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
- 下面来探索block在创建的时候,其存储域的变化过程:
- 初始化创建一个局部block变量add,内部未访问外界局部变量,在创建处打下断点:
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.button];
//定义了一个block变量add,类型为void(^)(void)
__block int m = 10;
void(^add)(void) = ^{
};
add();
}
- 进入汇编界面,执行到
objc_retainBlock
断点停住,LLDB调试如下:
- 看到此时的block类型为全局block,即
__NSGlobalBlock__
; - 若block内部捕获外界局部变量时,代码如下:
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.button];
//定义了一个block变量add,类型为void(^)(void)
__block int m = 10;
void(^add)(void) = ^{
m = 20;
NSLog(@" m = %d",m);
};
add();
}
- 进入汇编界面,执行到
objc_retainBlock
断点停住,LLDB调试如下:
- 看到此时的block类型为栈区block,即
__NSStackBlock__
; - 添加一个符号断点
_Block_copy
,当执行到此函数汇编指令的ret指令时,停住断点,LLDB调试结果如下:
可以看到底层源码在调用
_Block_copy
函数之后,block类型变成了堆区block,即__NSMallocBlock__
,完成了block从栈区到堆区的拷贝
;_Block_copy的函数源码解析:
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
//这里是核心重点 block的拷贝操作: 栈Block -> 堆Block
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;//强转为Block_layout类型对象,防止对外界造成影响
if (aBlock->flags & BLOCK_NEEDS_FREE) {//是否需要释放
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {//如果是全局block,直接返回
return aBlock;
}
else {//为栈block 或者 堆block,由于堆区需要申请内存,所以只可能是栈区
// Its a stack block. Make a copy. 它是一个栈区block,执行拷贝
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);//申请空间并接收
if (!result) return NULL;
//通过memmove内存拷贝,将 aBlock 拷贝至result
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;//可以直接调起invoke
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed 告知可释放
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;//设置block对象类型为堆区block
return result;
}
}
首先判断block是否需要free,需要free就直接释放;
然后判断block是否为全局block,如果是则不需要copy,直接返回;
-
最后专门时针对栈区block的逻辑操作,将栈区block拷贝到堆区;
- 通过malloc申请内存空间用于接收block;
- 通过memmove将block拷贝至新申请的内存中;
- 设置block对象的类型为堆区block,即result->isa = _NSConcreteMallocBlock
_Block_object_assign
函数是在底层编译代码中,当block从栈区拷贝到堆区时,外部变量的拷贝(栈区-->堆区)调用的方法就是它
;外部变量的类型如下:
// Block 捕获的外界变量的种类
// Runtime support functions used by compiler when generating copy/dispose helpers
// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
// see function implementation for a more complete description of these fields and combinations
//普通对象,即没有其他的引用类型
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
//block类型作为变量
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
//经过__block修饰的变量
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
//weak 弱引用变量
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
//返回的调用对象 - 处理block_byref内部对象内存会加的一个额外标记,配合flags一起使用
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
其中最常用的是
BLOCK_FIELD_IS_BYREF
与BLOCK_FIELD_IS_OBJECT
;_Block_object_assign
源码如下:
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
default:
break;
}
}
针对普通对象(BLOCK_FIELD_IS_OBJECT),执行
_Block_retain_object()
,引用计数+1;针对block变量(BLOCK_FIELD_IS_BLOCK),执行
_Block_copy()
即拷贝;针对__block修饰的变量(BLOCK_FIELD_IS_BYREF),执行
_Block_byref_copy()
;_Block_byref_copy的源码实现如下:
static struct Block_byref *_Block_byref_copy(const void *arg) {
//强转为Block_byref结构体类型,保存一份
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack 申请内存
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
//block内部持有的Block_byref 和 外界的Block_byref 所持有的对象是同一个,这也是为什么__block修饰的变量具有修改能力
//copy 和 scr 的地址指针达到了完美的同一份拷贝,目前只有持有能力
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
//如果有copy能力
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
//Block_byref_2是结构体,__block修饰的可能是对象,对象通过byref_keep保存,在合适的时机进行调用
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
//等价于 __Block_byref_id_object_copy
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
首先将传入的对象,强转成
Block_byref
结构体类型对象;-
判断传入的对象是否已经拷贝到堆区:
- 已经拷贝到堆区,处理直接返回;
- 未拷贝到堆区,首先申请内存,然后进行拷贝,再将copy(堆区)与src(栈区)的forwarding指针都指向堆区的内存地址;
代码调试:
int main(int argc, const char * argv[])
{
@autoreleasepool{
__block NSString *a = [NSString stringWithFormat:@"li"];
void(^TestBlock)(void) = ^{
NSLog(@" a = %@",a);
};
NSLog(@" TestBlock = %@",TestBlock);
}
return 0;
}
- clang编译成C++语言如下:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSString *a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_c5_l8bnxw0d2w92f4439t_r8qjc0000gn_T_main_5b35fd_mii_1,(a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
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 main(int argc, const char * argv[])
{
/* @autoreleasepool */{ __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 33554432, sizeof(__Block_byref_a_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_c5_l8bnxw0d2w92f4439t_r8qjc0000gn_T_main_5b35fd_mii_0)};
void(*TestBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_c5_l8bnxw0d2w92f4439t_r8qjc0000gn_T_main_5b35fd_mii_2,TestBlock);
}
return 0;
}
看到__block修饰OC对象时,__Block_byref结构体会多生成出两个函数分别为
__Block_byref_id_object_copy
和__Block_byref_id_object_dispose
;调试底层源码libclosure,首先底层会执行
_Block_copy
函数,实现将栈区block拷贝到堆区;
- 当执行到
_Block_call_copy_helper
函数时,接下来会调用_Block_object_assign
函数,传入的参数是__block修饰的局部变量被封装成Block_byref结构体对象;
- 接下来进入
_Block_byref_copy
函数内部,实现将栈区的Block_byref结构体对象拷贝到堆区;
- 上面已经提到如果__block修饰的是OC对象,在__Block_byref结构体会多生成出两个函数分别为
__Block_byref_id_object_copy
和__Block_byref_id_object_dispose
;
//block自身拷贝(_Block_copy)
// __block bref结构体拷贝(_Block_object_assign) //_Block_object_assign中对外部变量(存储在bref)拷贝一份到内存
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);
}
- if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) 这个判断条件就与是否生成COPY_DISPOSE两个函数有关,如果生成了就会执行这两个函数的相关逻辑;
- 从COPY_DISPOSE两个函数实现我们看到,dst是__Block_byref结构体,根据结构体成员布局(dst + 40)就是外界局部变量a;所以COPY_DISPOSE这两个函数执行的是外界局部变量a的拷贝与释放;
总结:
__block修饰OC对象存在三层copy:
- block对象的copy,调用
_Block_copy
函数实现将栈区block拷贝到堆区; - 外界局部变量生成C++结构体
Block_byref
对象的copy,调用_Block_object_assign
-->_Block_byref_copy
实现将Block_byref
结构体对象从栈区拷贝到堆区; - 只针对OC对象类,即在结构体
Block_byref
中生成COPY_DISPOSE函数,将外界局部变量从栈区拷贝到堆区;
_Block_object_dispose
- 同一般的retain和release一样,_Block_object_assign其本质主要是retain,所以对应的还有一个release,即_Block_object_dispose方法,其源码实现如下,也是通过区分block种类,进行不同释放操作;
// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents 当Blocks或Block_byrefs持有对象时,其销毁助手例程将调用此入口点以帮助处置内容
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF://__block修饰的变量,即bref类型的
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK://block类型的变量
_Block_release(object) ;
break;
case BLOCK_FIELD_IS_OBJECT://普通对象
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
- 进入_Block_byref_release源码,主要就是对象、变量的释放销毁;
static void _Block_byref_release(const void *arg) {
//对象强转为Block_byref类型结构体
struct Block_byref *byref = (struct Block_byref *)arg;
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
byref = byref->forwarding;//取消指针引用
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
if (latching_decr_int_should_deallocate(&byref->flags)) {
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {//是否有拷贝辅助函数
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
(*byref2->byref_destroy)(byref);//销毁拷贝对象
}
free(byref);//释放
}
}
}
Block的内存管理
- 栈区block,访问外界局部变量,不会对局部变量产生强引用;
- 堆区block,会调用block内部的copy函数,copy函数内部会调用
_Block_object_assign
函数,此函数会根据局部变量的修饰符(__strong,__weak,__unsafe_unretained)做出相应的操作, - 堆区block从堆区移除时,会调用block内部的dispose函数,dispose函数内部会调用
_Block_object_dispose
函数,其会自动释放引用的局部变量,相当于release局部变量;
- 栈区block拷贝到堆区时,__block修饰的变量也会从栈区复制到堆区,并且堆区block会对__block变量进行引用;