@weakify, @strongify 的实现思路

这篇文章介绍一下 “libextobj” 中 @weakify, @srongify 的实现思路。

为了方便测试,写了个测试用例,整体测试思路借鉴于 “libextobjc” 的一个测试用例。

先在 @autoreleasepool{} 声明几个对象,然后用 captureBlock capture 住,那么在结束这个 autoreleasepool 之后,测试对象是否存在。

开发者一般都知道,为了使 captureBlock 不 capture 住变量,会使用 __weak 来修饰这个变量,然后再 captureBlock 内部在用 __strong 修饰这个变量,不过经常这样写就显得繁琐。

下面介绍一下 “libextobj” 是如何巧妙简化这些步骤的。

这是测试用例的代码:

- (void)testWeakStrong {
void (^verifyMemoryManagement)(void);
void (^captureBlock)(void);
@autoreleasepool {
NSString* cycleRefObj = [@"cycleRef" mutableCopy];
NSString* nCycle = [@"ncycle" mutableCopy];
NSString* lnc = [@"libextobj no cycle" mutableCopy];
__weak typeof(nCycle) wnc = nCycle;
@weakify(lnc)
captureBlock = ^{
NSLog(@"%@", cycleRefObj);
__strong typeof(nCycle) snc = wnc;
NSLog(@"%@", snc);
@strongify(lnc) // redeclare lnc
NSLog(@"strongify %@", lnc);
};
captureBlock();
__weak typeof(cycleRefObj) wc = cycleRefObj;
verifyMemoryManagement = ^{
XCTAssertNotNil(wc);
XCTAssertNil(wnc);
@strongify(lnc);
XCTAssertNil(lnc);
};
}
verifyMemoryManagement();
}

nCycle 对象是手写 weak-strong-dancing 的变量,可以看到,这样写非常繁琐。
lnc 对象是用 “libextobj” 来处理 weak-strong-dancing 问题。
cycleRefObj 对象则没有处理被 block capture 的问题。

@weakify 宏展开

// 省略了非关键宏展开
#define ext_weakify_(INDEX, CONTEXT, VAR) \
CONTEXT __typeof__(VAR) VAR ## _weak_ = (VAR);

也就是说如果输入 @weakify(a),那么展开后就是:

// 这里的 `CONTEXT` 是由上一个宏传进来的 `__weak`
__weak __typeof__(a) a_weak_ = (a);

造成的结果就是会声明一个 __weak 修饰的变量 a_weak_

@strongify 宏展开

// 省略了非关键宏展开
__strong __typeof__(VAR) VAR = VAR ## _weak_;

那么 @strongify(a) 的展开结果是:

__strong __typeof__(a) a = a_weak_;

可以看到,它会引用上一个 __weak 修饰后的变量 a_weak_,并且在 block 作用域中重新声明变量 a,这里就不用担心 a 又被 capture 进来的问题了,因为它是新的局部变量。

宏拯救世界,因为@weakify@strongify 支持多参数,所以可以一次性设置,@weakify(a, b, c)