索引
背景
最近在做公司网络优化,对基础网络库进行升级,碰到网络库兼容问题。想到了
Swift中的defer关键字。无论你的代码块是通过break还是return,你在defer中代码都会得到执行。
解决方案
当前封装的网络库并不存在类方法,所有方法通过实例调用。实例对象api发起request请求,在收到response之前需要确保api的生命周期,处理方法如下:
- 通过
VC持有Api的实例对象,其生命周期跟随VC。 - 通过一个全局
Dictionary保存,在处理完成response后释放。 - 通过
response的block捕获Api的实例对象,在block处理完成后释放。
方案选择
从代码优雅度以及后期方便调整来看,我选着了最后一种处理方式。为了避免内存泄漏,最简单有效的方式就是使用一个类似Swift中的defer关键字来处理。
__block Api *api = [[Api alloc] init ....]
[api startRequestWithCompletion:^(BPNResponse *response) {
if (response.error != nil) { return; }
....
api = nil
}];
# ----- 以上这种处理方案,就会导致内存泄漏,而且处理比较复杂 ----
# 采用 Swift 中的 defer 改为以下方案来解决,避免内存泄漏
[api startRequestWithCompletion:^(BPNResponse *response) {
oc_defer(^{ api = nil; });
if (response.error != nil) { return; }
....
}];
实现代码
#ifndef oc_defer_h
#define oc_defer_h
// some helper declarations
#define _oc_macro_concat(a, b) a##b
#define oc_macro_concat(a, b) _oc_macro_concat(a, b)
typedef void(^oc_defer_block_t)(void);
NS_INLINE void nob_deferFunc(__strong oc_defer_block_t *blockRef) {
oc_defer_block_t actualBlock = *blockRef;
actualBlock();
}
// the core macro
#define oc_defer(deferBlock) \
__strong oc_defer_block_t oc_macro_concat(__oc_stack_defer_block_, __LINE__) __attribute__((cleanup(nob_deferFunc), unused)) = deferBlock
#endif /* nob_defer_h */
实现原理
-
__attribute__机制:
GNU编译器提供__attribute__机制,该机制可以给变量、函数、类型添加一些编译属性,优化代码在编译时的行为。
__attribute__((cleanup(nob_deferFunc)))释意:
__attribute__((cleanup(nob_deferFunc)))这个属性,可以告诉编译器,在变量超出作用域时执行nob_deferFunc清理函数,该清理函数只接受一个参数,就是参数所修饰的变量的指针。这是编译时的行为,编译器在分析代码后,在变量作用域的末尾新增nob_deferFunc调用,并最终编译成二进制序列。
defer调用:
每次
defer宏调用,实际上是在该作用域定义了一个__oc_stack_defer_block_LINE局部变量,即defer中的block代码块;并对这个block变量添加了编译属性,告诉编译器在该变量超出作用域时,将__oc_stack_defer_block_LINE变量的地址传递给nob_deferFunc清理函数执行。
oc_macro_concat宏作用
将
__oc_stack_defer_block_与当前代码所在行号进行拼接,该block 定义在 100 行,那么这个变量名将变为__oc_stack_defer_block_100,其完整代码如下:
__strong oc_defer_block_t __oc_stack_defer_block_100 \
__attribute__((cleanup(nob_deferFunc), unsigned)) = ^{
};