Objective-C 中实现 defer(Swift) 原理

419 阅读2分钟

索引

背景

最近在做公司网络优化,对基础网络库进行升级,碰到网络库兼容问题。想到了Swift中的defer关键字。无论你的代码块是通过break还是return,你在defer中代码都会得到执行。

解决方案

  当前封装的网络库并不存在类方法,所有方法通过实例调用。实例对象api发起request请求,在收到response之前需要确保api的生命周期,处理方法如下:

  • 通过VC持有Api的实例对象,其生命周期跟随VC
  • 通过一个全局Dictionary保存,在处理完成response后释放。
  • 通过responseblock捕获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)) = ^{
};