
作为一个iOS Developer, 我们都知道Block容易造成循环引用, 今天我们就来聊聊block的原理以及 __weak解决循环引用的本质原因。
目标:本篇文章主要弄清楚三个内容:
👈向左侧滑动,显示完整代码👈
1. block是怎么实现的。
2. 使用`__block`,`__weak`解决循环引用的原理。
3. 对于循环引用的block为什么内部要对`__weak`再使用`__strong`引用。
一、block原理
我们先通过一个例子, 看看block的大概实现原理
👈向左侧滑动,显示完整代码👈
#import <Foundation/Foundation.h>
int global_i = 1;
static int static_global_j = 1;
int main(int argc, const char * argv[]) {
static int static_k = 1;
int val = 1;
void (^myBlock)(void) = ^{
global_i++;
static_global_j++;
static_k++;
NSLog(@"in :global_i = %d, static_global_j = %d, static_k = %d, val = %d", global_i, static_global_j, static_k, val);
};
global_i++;
static_global_j++;
static_k++;
val++;
NSLog(@"out:global_i = %d, static_global_j = %d, static_k = %d, val = %d", global_i, static_global_j, static_k, val);
myBlock();
return 0;
}
使用命令 clang-rewrite-objc main.m把OC编译成C/C++
摘录关键代码
👈向左侧滑动,显示完整代码👈
struct __block_impl {
void *isa;// _NSConcreteStackBlock 类型
int Flags;
int Reserved;
void *FuncPtr; // 函数指针
};
// 全局变量
int global_i = 1;
static int static_global_j = 1;
// block编译成一个s结构体, 内部就是block内部的变量, 注意不同作用域的变量
struct __main_block_impl_0 {
struct __block_impl impl;// 存储
struct __main_block_desc_0* Desc;
int *static_k; // 由于是static的所以就算出了改方法也不会消失, 用一个指针指向它即可
int val; // 截获的自动变量
// 可能有人看不懂这个构造函数, 其实就是通过给出的参数给结构体属性赋值这是C++里面的东西, 可以搜索"C++类构造函数初始化列表"了解该语法
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// c编译生成了一个函数, 并且是静态的,参数是带有block全部信息的结构体
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_k = __cself->static_k; // bound by copy
int val = __cself->val; // bound by copy
global_i++;
static_global_j++;
(*static_k)++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_0j_73sz5v7x7k1gvfdxrbd__mz40000gp_T_main1_c16d3a_mi_0, global_i, static_global_j, (*static_k), val);
}
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[]) {
static int static_k = 1;
int val = 1;
// 1.调用__main_block_impl_0构造函数生成了一个__main_block_impl_0结构体变量,内部包含静态方法__main_block_func_0(编译器编译生成), 局部变量static_k val
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));
global_i++;
static_global_j++;
static_k++;
val++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_0j_73sz5v7x7k1gvfdxrbd__mz40000gp_T_main1_c16d3a_mi_1, global_i, static_global_j, static_k, val);
// 2.上边第一步将block信息揽括在了了struct中,我们通过这个struct, 直接调用即可
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0;
}
简单总结一下编译后的代码。

编译器将一个block编译器编译成了1个静态函数和3个结构体。
👈向左侧滑动,显示完整代码👈
1.block信息存储的结构体__main_block_impl_0, 通过它可以获取静态方法地址和block块中用到的变量。
2.__block_impl结构体存储,存储block类型(如_NSConcreteStackBlock),以及函数指针。
3.__main_block_desc_0这个结构体是一个中间结构体,下面内容会讲解。
4.静态方法__main_block_func_0,该方法就是block块中的逻辑代码,其中参数为__main_block_impl_0,可以获取所用到的相关变量。
这里Block编译后的处理, 对于全局,以及全局静态变量的处理方法就是直接访问,但是对于局部变量,以及局部静态变量则是截获到一个结构体中,这里大家可能发现我们没有在block内部对局部变量进行val++操作,因为直接这样做编译时不能通过的,需要添加 __block才可以。 然而添加了 __block后编译后又是什么样的呢。
👈向左侧滑动,显示完整代码👈
int main(int argc, const char * argv[]) {
__block int b = 10;
void (^myBlock)(void) = ^{
b++;
NSLog(@"%d", b);
};
b++;
NSLog(@"%d", b);
myBlock();
return 0;
}
添加了 __block编译后,新增一个用于存储 __block中所用到的局部变量的结构体
👈向左侧滑动,显示完整代码👈
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;
int __flags;
int __size;
int b;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_b_0 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_b_0 *_b, int flags=0) : b(_b->__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_b_0 *b = __cself->b; // bound by ref
(b->__forwarding->b)++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_0j_73sz5v7x7k1gvfdxrbd__mz40000gp_T_main_6b8564_mi_0, (b->__forwarding->b));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->b, 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[]) {
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 10};
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_b_0 *)&b, 570425344));
(b.__forwarding->b)++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_0j_73sz5v7x7k1gvfdxrbd__mz40000gp_T_main_6b8564_mi_1, (b.__forwarding->b));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0;
}
编译后:

对比发现,除了上边的3个结构体和1个静态方法还多了一个用于存储 __block变量的结构体 __Block_byref_b_0; 而且在结构体 __main_block_desc_0中多了两个方法, __main_block_copy_0和 __main_block_dispose_0。 这里多这两个方法的作用是当对block进行copy时(具体请自行去了解block的三种类型,栈,堆,全局),对持有的自动变量进行栈到对空间的自动变量进行拷贝和释放的操作, 并且这两个方法只有当有这个需要的时候编译出的结果才会有。
二、打破循环引用
理清楚了block的实现原理现在我们来看看打破循环引用的原理。 先来讨论 __weak的使用。
我们来看一个例子:
👈向左侧滑动,显示完整代码👈
#import <Foundation/Foundation.h>
typedef void(^Block)(void);
@interface BlockSelf : NSObject
@property(nonatomic, copy) Block block;
@property(nonatomic, strong) NSString *name;
- (void)helloWorld;
@end
#import "BlockSelf.h"
@implementation BlockSelf
- (void)helloWorld {
__weak __typeof__(self) weak_self = self;
[self setBlock:^{
__typeof__(self) strong_self = weak_self;
// 有个人会说我这里直接使用weak_self就可以了,为什么要使用strong_self呢,这个待后边讨论
strong_self.name = @“hello world";
}];
}
@end
由于需要runtime的支持这里命令有所区别。
具体使用clang编译的过程如下:
在Terminal中输入 open~/.bash_profile然后在环境变量中添加如下环境变量SDK_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk exportSDK_PATH
最后在Terminal中输入输入 source~/.bash_profile 使修改生效
OK, 现在我们编译源文件 xcrun--show-sdk-path>SDK_PATH;clang-x objective-c-isysroot $SDK_PATH-rewrite-objc-fobjc-arc-fblocks-mios-version-min=8.0.0-fobjc-runtime=ios-8.0.0-O0BlockSelf.m
编译后:
👈向左侧滑动,显示完整代码👈
// 函数信息结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// bloc信息结构体
struct __BlockSelf__helloWorld_block_impl_0 {
struct __block_impl impl;
struct __BlockSelf__helloWorld_block_desc_0* Desc;
BlockSelf *__weak weak_self; // 我们制定类型为__weak的,编译后就为该类型,但是如果你不指定__weak, 那么编译器会默认添加为__strong这也是导致循环引用的根本原因
__BlockSelf__helloWorld_block_impl_0(void *fp, struct __BlockSelf__helloWorld_block_desc_0 *desc, BlockSelf *__weak _weak_self, int flags=0) : weak_self(_weak_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 静态函数
static void __BlockSelf__helloWorld_block_func_0(struct __BlockSelf__helloWorld_block_impl_0 *__cself) {
BlockSelf *__weak weak_self = __cself->weak_self; // bound by copy 对应着block结构体中的变量
__attribute__((objc_ownership(strong))) BlockSelf *strong_self = weak_self; // 对应代码__strong BlockSelf *strong_self = weak_self;
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)strong_self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_0j_73sz5v7x7k1gvfdxrbd__mz40000gp_T_BlockSelf_c84e00_mi_0);
}
static void __BlockSelf__helloWorld_block_copy_0(struct __BlockSelf__helloWorld_block_impl_0*dst, struct __BlockSelf__helloWorld_block_impl_0*src) {_Block_object_assign((void*)&dst->weak_self, (void*)src->weak_self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __BlockSelf__helloWorld_block_dispose_0(struct __BlockSelf__helloWorld_block_impl_0*src) {_Block_object_dispose((void*)src->weak_self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
// desc
static struct __BlockSelf__helloWorld_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __BlockSelf__helloWorld_block_impl_0*, struct __BlockSelf__helloWorld_block_impl_0*);
void (*dispose)(struct __BlockSelf__helloWorld_block_impl_0*);
} __BlockSelf__helloWorld_block_desc_0_DATA = { 0, sizeof(struct __BlockSelf__helloWorld_block_impl_0), __BlockSelf__helloWorld_block_copy_0, __BlockSelf__helloWorld_block_dispose_0};
// helloworld函数体
static void _I_BlockSelf_helloWorld(BlockSelf * self, SEL _cmd) {
__attribute__((objc_ownership(weak))) BlockSelf *weak_self = self;// 对应__weak BlockSelf *weak_self = self;
((void (*)(id, SEL, Block))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__BlockSelf__helloWorld_block_impl_0((void *)__BlockSelf__helloWorld_block_func_0, &__BlockSelf__helloWorld_block_desc_0_DATA, weak_self, 570425344)));
}
编译后类图

我们可以看到编译后依然是3个结构体1个静态方法。
我们来看block的主结构体信息:

这里重点关注编译后的变量信息 BlockSelf*__weak weak_self;, 这里为什么是__weak呢, 那是因为我们再代码中添加了 __weak修饰, 如果没有添加默认为 __strong的,如下边代码(大家可以通过修改代码自己查看,这里是我修改后截取的代码):

通过对比,我们可以得出结论, block主结构体中生成的变量类型是和我们代码一一对应的。
然后我们再来看这个静态方法:

在方法内部又将对象转换为了strong类型,防止对象释放掉。
三、为什么要使用__strong
这里我们就好解释了为什么block内部需要使用 __strong修饰 __weak变量了, 看这样一段代码
👈向左侧滑动,显示完整代码👈
BlockSelf *obj = [BlockSelf new];
obj.name = @"hei";
__weak BlockSelf *weak_obj = obj;
[obj setBlock:^{
__strong BlockSelf *strong_obj = weak_obj;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// NSLog(@"%@",weak_obj.name);
NSLog(@"%@",strong_obj.name);
});
}];
obj.block();
这里会造成循环引用, 如果使用 weak_obj那么打印出的信息为null, 但是如果使用 strong_obj那么会打印hei
结束:
现在再来聊聊block 在上部分我们看到了使用block修饰后会生成一个结构体, 但是对于OC对象该结构体默认是强持有这个对象的, 所以在arc模式下只使用block是无法解决循环引用的问题的,还是需要添加weak。
顺便提一下, 现在有个比较流行的写法 @weakify(self)@strongify(self)两个比较让人疑惑的地方 1.@ 符号, 其实就是就是宏定义了一个啥也没做的 @autoreleasepool, 只是去掉了@符号, 可以理解为纯粹是为了装逼才这样写。 2.为什么在block内部可以直接使用self, 而不像我们写的 weak_self之类的变量,这是因为在block内 @strongify(…)的宏定义中定义了一个名叫self的strong类型变量,而不是我们认为的当前对象的self。ReactiveCocoa对于这两个的宏定义由于用到了可变参数, 所以比较复杂,这里可以看看YYKit里面的定义, 由于只支持单个参数, 所以很容易理解。
这里摘取其中debug下的宏定义,将object替换为self,就一目了然了。其中##是拼接的意思。
👈向左侧滑动,显示完整代码👈
#define weakify(object) autoreleasepool{} __weak __typeof__(object) weak##_##object = object;
#define strongify(object) autoreleasepool{} __typeof__(object) object = weak##_##object;
今日推荐

