@weakify 与 @strongify 实现原理
为了解决 Block 造成的循环引用,iOS 开发过程中常常使用 @weakify 与 @strongify 来解决这个问题。下面就来看下 @weakify 与 @strongify 的实现原理。
准备知识
@weakify 和 @strongify 的实现原理就是宏展开,阅读上面的准备知识可以更好的理解下面宏展开的过程。
@weakify、@strongify 替换后的结果
我们首先看 @weakify 与 @strongify 替换后的结果,假如我们有如下代码:
@interface ViewController () @property (nonatomic, copy) void (^testBlock1)(void);
@property (nonatomic, copy) void (^testBlock2)(void); @end @implementation ViewController - (void)viewDidLoad {
@weakify(self);
self.testBlock1 = ^{
@strongify(self);
NSLog(@"%@", self);
};
} @end
因为 @weakify 与 @strongify 本质上是宏实现,在 Xcode 中选择菜单 Product -> Perform Action -> Preprocess "xx.m"(xx.m 就是文件名),就会看到最终替换的结果:
- (void)viewDidLoad {
@autoreleasepool {} __attribute__((objc_ownership(weak))) __typeof__(self) self_weak_ = (self); // @weakify 替换结果
self.testBlock1 = ^{
@autoreleasepool {} __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_; // @strongify 替换结果, block里面可以重定义self
;
NSLog(@"%@", self);
};
}
可以看到,最终替换的结果其实很简单。最开始的 @ 符号被 autoreleasepool 给"吃掉",__attribute__((objc_ownership(weak))) 与 __attribute__((objc_ownership(strong))) 相当于 __weak 与 __strong,__typeof__ 等价于 typeof(关于两者的关系,参看
宏定义中的重复副作用)。那么整个替换结果就相当于:
@weakify(self); -> __weak typeof(self) self_weak_ = self; @strongify(self); -> __strong typeof(self) self = self_weak_;
为什么 Bolock 里面可以重新定义 self
上面替换结果需要注意的第一个地方是,Block 里面可以重新定义 self。为了弄清楚原理,首先查看 Clang 文档对于 self 的解释:
The
selfparameter variable of an non-init Objective-C method is considered externally-retained by the implementation...
可以看到,self 其实不是一个关键字,而只是一个参数变量。在 OC 方法中,self 作为第一个隐藏参数传递给相关的方法。假设有如下代码:
@implementation X
- (void)test {
__weak typeof(self) weakSelf = self;
self.testBlock1 = ^{
__strong typeof(self) self = weakSelf;
};
}
@end
使用 clang -rewrite-objc(由于这里使用了 __weak 关键字,需要运行时支持,同时要开启 ARC,完整的命令为 clang -framework foundation -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-11.7)查看 c++ 代码:
static void _I_X_test(X * self, SEL _cmd) { // test方法
__attribute__((objc_ownership(weak))) typeof(self) weakSelf = self;
((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setTestBlock1:"), ((void (*)())&__X__test_block_impl_0((void *)__X__test_block_func_0, &__X__test_block_desc_0_DATA, weakSelf, 570425344)));
}
虽然在 OC 里面 test 方法没有参数,但实际上有2个参数传给了它,第一个就是 self,另一个是方法名字符串 _cmd。因为这个原因,我们无法在 OC 方法里面重新定义 self,这样会报错。
接下来看 Block 里面的实现,同样查看 c++ 代码:
struct __X__test_block_impl_0 { // Block的实现
struct __block_impl impl;
struct __X__test_block_desc_0* Desc;
X *const __weak weakSelf; // Block 持有了 weakSelf
__X__test_block_impl_0(void *fp, struct __X__test_block_desc_0 *desc, X *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __X__test_block_func_0(struct __X__test_block_impl_0 *__cself) { // Block方法的实现
X *const __weak weakSelf = __cself->weakSelf; // bound by copy
__attribute__((objc_ownership(strong))) typeof(self) self = weakSelf;
}
从 Block 的实现可以看到,Block 持有了 weakSelf。同时在 Block 的方法实现 __X__test_block_func_0 中只接收了一个参数,这个参数就是 Block,而不是像 OC 方法一样有 self 参数。正是因为这个原因,才可以在 Block 方法里面重新定义 self 变量。
为什么 typeof(self) 不会引起 Block 强引用
替换结果需要注意的第二个地方是,替换后 Block 里面仍然有 self 的出现,这个 self 会造成强引用吗?答案是不会。typeof(self) 可以看成是一个类型,也就是说替换后的结果实际上可以看成:
@weakify(self); -> __weak ViewController * self_weak_ = self; @strongify(self); -> __strong ViewController *self = self_weak_;
同时,上面类 X 的 c++ 源码也可以看到,Block 确实没有对 self 造成强引用。
@weakify 的实现原理
我们接下来一层一层将 weakify 的宏展开,看下它的实现原理。
#define weakify(...) \
rac_keywordify \
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
可以看到 weakify 宏接收一个可变参数(可变参数宏参看可变参数宏)。
rac_keywordify 定义如下:
#if DEBUG
#define rac_keywordify autoreleasepool {}
#else
#define rac_keywordify try {} @catch (...) {}
#endif
可以看到 rac_keywordify 在 DEBUG 模式下是 autoreleasepool {},而在 release 模式下是 try..catch语句。这两个都可以"吃掉" @weakify 前面的 '@' 符号,之所以要进行区别,是因为如果在 DEBUG 模式下使用 try...catch,若果碰到 Block 有返回值,会抑制 Xcode 对返回值检测的警告。比如:
@weakify(self);
self.block = ^int {
@strongify(self);
NSLog(@"123");
}
上面代码 Block 应该返回一个 int 类型,但是实际上没有返回。因为 try..catch 的存在,Xcode 不会产生报错或者警告,这对 DEBUG 模式下不友好。而在 release 模式下,空的 try...catch 会被编译器优化移除,而空的自动释放池不会被优化,对性能会有影响。由于这个原因,在 DEBUG 模式下使用了 autoreleasepool {},而 release 模式下使用 try...catch。
对于 @weakify(self),经过第一层宏展开(宏参数展开原理参看宏参数(Arguments)的扩展),结果如下:
@autoreleasepool {} metamacro_foreach_cxt(rac_weakify_,, __weak, self)
接下来对宏 metamacro_foreach_cxt 进行展开,首先看它的定义:
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
这个宏定义里面有一个宏 metamacro_argcount,它用来计算可变参数的个数,原理后面有讲,这里它扩展之后值为1。
metamacro_foreach_cxt 扩展后的结果为:
metamacro_concat(metamacro_foreach_cxt, 1)(rac_weakify_, ,__weak, self)
接下来对宏 metamacro_concat 进行扩展,它的定义为:
#define metamacro_concat(A, B) \
metamacro_concat_(A, B) #define metamacro_concat_(A, B) A ## B
可以看到 metamacro_concat 就是一个字符串的连接操作,整个宏的扩展结果为:
metamacro_foreach_cxt1(rac_weakify_, ,__weak, self)
接下来看宏 metamacro_foreach_cxt1 的定义,定义为:
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
可以看到对于只有1个参数的情形,SEP 参数被忽略。整个宏的扩展结果为:
rac_weakify_(0, __weak, self)
宏 rac_weakify_ 的定义如下:
#define rac_weakify_(INDEX, CONTEXT, VAR) \
CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
宏 metamacro_concat 依然是字符串连接操作,最后扩展出来的结果就是:
__weak typeof(self) self_weak_ = (self)
@strongify 的实现原理
同样将宏 strongify 一层一层展开,首先看它的定义:
#define strongify(...) \
rac_keywordify \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
_Pragma("clang diagnostic pop")
定义里面 rac_keywordify 宏和 weakify 一样,_Pragma 是 C99 引入,等价于 #pragma。将整个宏展开之后结果为:
@autoreleasepool {} metamacro_foreach(rac_strongify_, ,self)
接下来展开宏 metamacro_foreach,它的定义为:
#define metamacro_foreach(MACRO, SEP, ...) \
metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)
展开结果为:
metamacro_foreach_cxt(metamacro_foreach_iter, , rac_strongify_, self)
宏 metamacro_foreach_cxt 展开的流程和 weakify 中讲解的一样,这里直接给出展开结果:
metamacro_foreach_cxt1(metamacro_foreach_iter, ,rac_strongify_, self)
宏 metamacro_foreach_cxt1 展开的流程也和 weakify 中讲解的一样,展开结果为:
metamacro_foreach_iter(0, rac_strongify_, self)
宏 metamacro_foreach_iter 定义为:
#define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG)
展开结果为:
rac_strongify_(0, self)
宏 rac_strongify_ 定义为:
#define rac_strongify_(INDEX, VAR) \
__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
展开结果为:
__strong __typeof__(self) self = self_weak_;
计算可变参数个数的宏 metamacro_argcount
宏 metamacro_argcount 用来计算可变参数的个数,首先看它的定义:
#define metamacro_argcount(...) \
metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
如果我们向 metamacro_argcount 宏传递了3个参数 arg1,arg2,arg3(也就是@weakify(arg1, arg2, arg3)),那么扩展后的结果为:
metamacro_at(20, arg1, arg2, arg3, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
宏 metamacro_at 的定义如下:
#define metamacro_at(N, ...) \
metamacro_concat(metamacro_at, N)(__VA_ARGS__)
扩展后的结果为:
metamacro_concat(metamacro_at, 20)(arg1, arg2, arg3, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
宏 metacro_concat 就是字符串连接操作,扩展后的结果为:
metamacro_at20(arg1, arg2, arg3, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
宏 metamacro_at20 定义为:
#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)
这里比较重要,我们先将传递给 metamacro_at20 的实参与形参做一个对照:
arg1 arg2 arg3 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 // 实参
_0 _1 _2 _3 _4 _5 _6 _7 _8 _9 _10 _11 _12 _13 _14 _15 _16 _17 _18 _19 // 形参
通过对照发现,实参总共传递了23个,而形参总共有20个,因此实参比形参多的部分都是可变参数。也就是说,__VA_ARGS__的值为3, 2, 1,所以扩展后的结果为:
metamacro_head(3, 2, 1)
宏 metamacro_head 定义为:
#define metamacro_head(...) \
metamacro_head_(__VA_ARGS__, 0) #define metamacro_head_(FIRST, ...) FIRST
扩展之后,FIRST 就是3,刚好是传入的可变参数的个数。
通过分析发现,计算的原理其实就是利用宏 metamacro_at20 实参与形参的个数差。如果传递给宏 metamacro_at20 的实参个数是4个,那么实参与形参的对比为:
arg1 arg2 arg3 arg4 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 // 实参
_0 _1 _2 _3 _4 _5 _6 _7 _8 _9 _10 _11 _12 _13 _14 _15 _16 _17 _18 _19 // 形参
这样扩展之后的结果就是4。
如果传递的参数个数超过了20个,那就计算不出来了:
// 传入20个参数
arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14 arg15 arg16 arg17 arg18 arg19 arg20 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 // 实参
_0 _1 _2 _3 _4 _5 _6 _7 _8 _9 _10 _11 _12 _13 _14 _15 _16 _17 _18 _19 // 形参 // 传入21个参数
arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14 arg15 arg16 arg17 arg18 arg19 arg20 arg21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 // 实参
_0 _1 _2 _3 _4 _5 _6 _7 _8 _9 _10 _11 _12 _13 _14 _15 _16 _17 _18 _19 // 形参
从上面可以看到,当传入20个参数,扩展之后结果是20;当传入21个时,扩展的结果是 arg21,就不对了。
多参数扩展
假如传递2个参数,也就是 @weakify(arg1, arg2) 与 @strongify(arg1, arg2),整个扩展过程中,差别就是宏 metamacro_foreach_cxt 的扩展。当参数个数为2时,metamacro_foreach_cxt 的扩展结果为宏 metamacro_foreach_cxt2。看一下宏 metamacro_foreach_cxt2 的定义:
#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \
SEP \
MACRO(1, CONTEXT, _1)
可以看到宏 metamacro_foreach_cxt2 首先调用 metamacro_foreach_cxt1,SEP 参数为空, 然后扩展 rac_weakify_(1, __weak, arg2)它的扩展结果为:
__weak __typeof__(arg1) arg1_weak_ = (arg1);
__weak __typeof__(arg2) arg2_weak_ = (arg2);
相应的 @strongify(arg1, arg2) 的扩展结果为:
__strong __typeof__(arg1) arg1 = arg1_weak_;
__strong __typeof__(arg2) arg2 = arg2_weak_;
由于 @weakify 与 @strongify 最多可以传递20个参数,所以宏 metamacro_foreach_cxt 有20个定义,从 metamacro_foreach_cxt1 到 metamacro_foreach_cxt20。每一个定义都是下一个 调用上一个 。比如宏 metamacro_foreach_cxt8 就是调用 metamacro_foreach_cxt7 实现:
#define metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
SEP \
MACRO(7, CONTEXT, _7)
嵌套 Block 的情形
下面来看一下如果 Block 有嵌套,那么对于内层的 Block,还需要 @weakify 与 @strongify 对吗?
首先来看下,如果内层 Block 也使用 @weakify 与 @strongify 的情形:
- (void)viewDidLoad {
@weakify(self);
self.testBlock1 = ^{
@strongify(self);
NSLog(@"%@", self);
@weakify(self);
self.testBlock2 = ^{
@strongify(self);
NSLog(@"%@", self);
};
};
}
使用 Xcode 查看宏展开的结果:
- (void)viewDidLoad {
@autoreleasepool {} __attribute__((objc_ownership(weak))) __typeof__(self) self_weak_ = (self);;
self.testBlock1 = ^{
@autoreleasepool {} __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_;
NSLog(@"%@", self);
@autoreleasepool {} __attribute__((objc_ownership(weak))) __typeof__(self) self_weak_ = (self);;
self.testBlock2 = ^{
@autoreleasepool {} __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_;
NSLog(@"%@", self);
};
};
}
可以看到对于嵌套 Block,内层 Block 是不需要 @weakify的,只需要写 @strongify就可以。因为内层 Block 的@weakify 只是重新定义了 self_weak_。
下面是内层 Block 不写 @weakify 的情形:
- (void)viewDidLoad {
@weakify(self);
self.testBlock1 = ^{
@strongify(self);
NSLog(@"%@", self);
self.testBlock2 = ^{
@strongify(self);
NSLog(@"%@", self);
};
};
}
用 Xcode 展开后的结果为:
- (void)viewDidLoad {
@autoreleasepool {} __attribute__((objc_ownership(weak))) __typeof__(self) self_weak_ = (self);;
self.testBlock1 = ^{
@autoreleasepool {} __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_;
NSLog(@"%@", self);
self.testBlock2 = ^{
@autoreleasepool {} __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_;
NSLog(@"%@", self);
};
};
}
p.p1 { margin: 0; font: 18px "Courier New"; color: rgba(0, 0, 0, 1) }
span.s1 { font-variant-ligatures: no-common-ligatures }
@weakify 与 @strongify 实现原理的更多相关文章
- 深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用(下)
深入研究Block捕获外部变量和__block实现原理 EOCNetworkFetcher.h typedef void (^EOCNetworkFetcherCompletionHandler)(N ...
- 深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用(上)
深入研究Block捕获外部变量和__block实现原理 前言 在上篇中,仔细分析了一下Block的实现原理以及__block捕获外部变量的原理.然而实际使用Block过程中,还是会遇到一些问题,比如R ...
- [HMLY]10.深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用
前言 在上篇中,仔细分析了一下Block的实现原理以及__block捕获外部变量的原理.然而实际使用Block过程中,还是会遇到一些问题,比如Retain Circle的问题. 目录 1.Retain ...
- 请谨慎使用 @weakify 和 @strongify
来源:酷酷的哀殿 链接:http://www.jianshu.com/p/d8035216b257 前言 相信大部分见过 @weakify 和 @strongify 的开发者都会喜欢上这两个宏.但是很 ...
- 剖析@weakify 和 @strongify
前言 使用RAC的时候我们常会看到这两个宏@weakify(self).@strongify(self),用来防止使用block时出现引用闭环. 今天看YYKit的时候,看到里面也写了类似的宏,还是来 ...
- [HMLY]13.请谨慎使用 @weakify 和 @strongify
前言 相信大部分见过 @weakify 和 @strongify 的开发者都会喜欢上这两个宏.但是很多人只知道它的强大威力,却没有意识到在特定环境下的危险性. 本文将通过代码测试的方式告诉读者,如何正 ...
- 剖析RAC中的@weakify、@strongify
0.很长的前言 1.问题 2.RAC是怎么解决的 2.weakify.strongify的定义 预备知识 一层层展开weakify 3.RAC装逼宏 metamacro_argcount 的定义 me ...
- @weakify, @strongify ObjC的Block中使用weakSelf/strongSelf @weakify/@strongify
首先要说说什么时候使用weakSelf和strongSelf. 下面引用一篇博客<到底什么时候才需要在ObjC的Block中使用weakSelf/strongSelf>的内容: Objec ...
- ObjC的Block中使用weakSelf/strongSelf @weakify/@strongify
首先要说说什么时候使用weakSelf和strongSelf. 下面引用一篇博客<到底什么时候才需要在ObjC的Block中使用weakSelf/strongSelf>的内容: Objec ...
随机推荐
- Kubernetes实战之部署ELK Stack收集平台日志
主要内容 1 ELK概念 2 K8S需要收集哪些日志 3 ELK Stack日志方案 4 容器中的日志怎么收集 5 K8S平台中应用日志收集 准备环境 一套正常运行的k8s集群,kubeadm安装部署 ...
- FileInputStream 类与 FileReader 类的区别
FileInputStream 类与 FileReader 类的区别: 两个类的构造函数的形式和参数都是相同的,参数为 File 对象或者表示路径的 String ,它们到底有何区别呢? FileIn ...
- Docker的资源控制管理
Docker的资源控制管理 1.CPU控制 2.对内存使用进行限制 3.对磁盘I/O配额控制的限制 1.CPU控制: cgroups,是一个非常强大的linux内核工具,他不仅可以限制被namespa ...
- 简述redis集群的实现原理
在哨兵sentinel机制中,可以解决redis高可用问题,即当master故障后可以自动将slave提升为master,从而可以保证redis服务的正常使用,但是无法解决redis单机写入的瓶颈问题 ...
- 如何通过opensea-js获取OpenSea的数据
OpenSea作为NFT最大的交易平台,随着NFT的火热之后,热度也是出现翻天覆地的变化.作为开发人员肯定好奇有没有可以与opensea交互的包来开发相关的工具或者快速获取opensea的数据.别急, ...
- Flask中请求数据的优雅传递
当一个请求到来时,浏览器会携带很多信息发送发送服务端.在Django中,每一个处理函数都要传入一个request的参数,该参数携带所有请求的信息,也就是服务端程序封装的environ(不明白该参数可以 ...
- Dubbo扩展点应用之六服务动态降级
服务降级,当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务有策略的降低服务级别以释放服务器资源保证核心任务的政策运行. 为什么要使用服务降级呢?这是为了防止分布式服务发送雪崩效应,也就是蝴蝶 ...
- NFA转化为DFA
NFA(不确定的有穷自动机)转化为DFA(确定的有穷自动机) NFA转换DFA,通常是将带空串的NFA(即:ε-NFA)先转化为不带空串的NFA(即:NFA),然后再转化为DFA. 提示:ε是空串的意 ...
- 在不受支持的 Mac 上安装 macOS Monterey 12(OpenCore Patcher)
一.介绍 本文通用于 macOS Big Sur 和 macOS Monterey,也可以视作笔者 早期文章 的升级版. 这一章节将介绍 macOS Monterey 的系统要求和不受支持的 Mac ...
- Linux系统LVM逻辑卷创建过程以及自动化脚本
转至:https://cloud.tencent.com/developer/article/1068328 Linux系统LVM逻辑卷创建过程以及自动化脚本 2018-03-21阅读 6300 ...