深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用(下)

时间:2024-04-15 15:07:15

深入研究Block捕获外部变量和__block实现原理

EOCNetworkFetcher.h

typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);

@interface EOCNetworkFetcher : NSObject

@property (nonatomic, strong, readonly) NSURL *url;

- (id)initWithURL:(NSURL *)url;

- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion;

@end

EOCNetworkFetcher.m

@interface EOCNetworkFetcher ()

@property (nonatomic, strong, readwrite) NSURL *url;

@property (nonatomic, copy) EOCNetworkFetcherCompletionHandler completionHandler;

@property (nonatomic, strong) NSData *downloadData;

@end

@implementation EOCNetworkFetcher

- (id)initWithURL:(NSURL *)url {

if(self = [super init]) {

_url = url;

}

return self;

}

- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion {

self.completionHandler = completion;

//开始网络请求

dispatch_async(dispatch_get_global_queue(0, 0), ^{

_downloadData = [[NSData alloc] initWithContentsOfURL:_url];

dispatch_async(dispatch_get_main_queue(), ^{

//网络请求完成

[self p_requestCompleted];

});

});

}

- (void)p_requestCompleted {

if(_completionHandler) {

_completionHandler(_downloadData);

}

}

@end

EOCClass.m

@implementation EOCClass {

EOCNetworkFetcher *_networkFetcher;

NSData *_fetchedData;

}

- (void)downloadData {

NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];

_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];

[_networkFetcher startWithCompletionHandler:^(NSData *data) {

_fetchedData = data;

}];

}

@end

在这个例子中,存在3者之间形成环

1、completion handler的block因为要设置_fetchedData实例变量的值,所以它必须捕获self变量,也就是说handler块保留了EOCClass实例;

2、EOCClass实例通过strong实例变量保留了EOCNetworkFetcher,最后EOCNetworkFetcher实例对象也会保留了handler的block。

书上说的3种方法来打破循环。

方法一:手动释放EOCNetworkFetcher使用之后持有的_networkFetcher,这样可以打破循环引用

- (void)downloadData {

NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];

_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];

[_networkFetcher startWithCompletionHandler:^(NSData *data) {

_fetchedData = data;

_networkFetcher = nil;//加上此行,打破循环引用

}];

}

方法二:直接释放block。因为在使用完对象之后需要人为手动释放,如果忘记释放就会造成循环引用了。如果使用完completion handler之后直接释放block即可。打破循环引用

- (void)p_requestCompleted {

if(_completionHandler) {

_completionHandler(_downloadData);

}

self.completionHandler = nil;//加上此行,打破循环引用

}

方法三:使用weakSelf、strongSelf

- (void)downloadData {

__weak __typeof(self) weakSelf = self;

NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];

_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];

[_networkFetcher startWithCompletionHandler:^(NSData *data) {

__typeof(&*weakSelf) strongSelf = weakSelf;

if (strongSelf) {

strongSelf.fetchedData = data;

}

}];

}

四.@weakify、@strongify实现原理

上面讲完了weakSelf、strongSelf之后,接下来再讲讲@weakify、@strongify,这两个关键字是RAC中避免Block循环引用而开发的2个宏,这2个宏的实现过程很牛,值得我们学习。

@weakify、@strongify的作用和weakSelf、strongSelf对应的一样。这里我们具体看看大神是怎么实现这2个宏的。

直接从源码看起来。

#define weakify(...) \

rac_keywordify \

metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)

#define strongify(...) \

rac_keywordify \

_Pragma("clang diagnostic push") \

_Pragma("clang diagnostic ignored \"-Wshadow\"") \

metamacro_foreach(rac_strongify_,, __VA_ARGS__) \

_Pragma("clang diagnostic pop")

看到这种宏定义,咋一看什么都不知道。那就只能一层层的往下看。

1. weakify

先从weakify(…)开始。

#if DEBUG

#define rac_keywordify autoreleasepool {}

#else

#define rac_keywordify try {} <a href='http://www.jobbole.com/members/wx895846013'>@catch</a> (...) {}

#endif

这里在debug模式下使用@autoreleasepool是为了维持编译器的分析能力,而使用@try/@catch 是为了防止插入一些不必要的autoreleasepool。rac_keywordify 实际上就是autoreleasepool {}

的宏替换。因为有了autoreleasepool {}的宏替换,所以weakify要加上@,形成@autoreleasepool {}。

#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \

metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)

__VA_ARGS__:总体来说就是将左边宏中 … 的内容原样抄写在右边 __VA_ARGS__ 所在的位置。它是一个可变参数的宏,是新的C99规范中新增的,目前似乎只有gcc支持(VC从VC2005开始支持)。

那么我们使用@weakify(self)传入进去。__VA_ARGS__相当于self。此时我们可以把最新开始的weakify套下来。于是就变成了这样:

rac_weakify_,, __weak, __VA_ARGS__整体替换MACRO, SEP, CONTEXT, …

这里需要注意的是,源码中就是给的两个”,”逗号是连着的,所以我们也要等效替换参数,相当于SEP是空值。

替换完成之后就是下面这个样子:

autoreleasepool {}

metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self))(rac_weakify_, , __weak, self)

现在我们需要弄懂的就是metamacro_concat 和 metamacro_argcount是干什么用的。

继续看看metamacro_concat 的实现

#define metamacro_concat(A, B) \

metamacro_concat_(A, B)

#define metamacro_concat_(A, B) A ## B

## 是宏连接符。举个例子:

假设宏定义为#define XNAME(n) x##n,代码为:XNAME(4),则在预编译时,宏发现XNAME(4)与XNAME(n)匹配,则令 n 为 4,然后将右边的n的内容也变为4,然后将整个XNAME(4)替换为 x##n,亦即 x4,故 最终结果为 XNAME(4) 变为 x4。所以A##B就是AB。

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)

#define metamacro_at(N, ...) \

metamacro_concat(metamacro_at, N)(__VA_ARGS__)

metamacro_concat是上面讲过的连接符,那么metamacro_at, N = metamacro_atN,由于N = 20,于是metamacro_atN = metamacro_at20。

深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用(下)

metamacro_at20的作用就是截取前20个参数,剩下的参数传入metamacro_head。

#define metamacro_head(...) \

metamacro_head_(__VA_ARGS__, 0)

#define metamacro_head_(FIRST, ...) FIRST

metamacro_head的作用返回第一个参数。返回到上一级metamacro_at20,如果我们从最源头的@weakify(self),传递进来,那么metamacro_at20(self,20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1),截取前20个参数,最后一个留给metamacro_head_(1),那么就应该返回1。

metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self)) = metamacro_concat(metamacro_foreach_cxt, 1) 最终可以替换成metamacro_foreach_cxt1。

在源码中继续搜寻。

// metamacro_foreach_cxt expansions

#define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT)

#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \

metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \

SEP \

MACRO(1, CONTEXT, _1)

#define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \

metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \

SEP \

MACRO(2, CONTEXT, _2)

#define metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \

metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \

SEP \

MACRO(3, CONTEXT, _3)

#define metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \

metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \

SEP \

MACRO(4, CONTEXT, _4)

#define metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \

metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \

SEP \

MACRO(5, CONTEXT, _5)

#define metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \

metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \

SEP \

MACRO(6, CONTEXT, _6)

#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)

#define metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \

metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \

SEP \

MACRO(8, CONTEXT, _8)

#define metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \

metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \

SEP \

MACRO(9, CONTEXT, _9)

#define metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \

metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \

SEP \

MACRO(10, CONTEXT, _10)

#define metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \

metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \

SEP \

MACRO(11, CONTEXT, _11)

#define metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \

metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \

SEP \

MACRO(12, CONTEXT, _12)

#define metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \

metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \

SEP \

MACRO(13, CONTEXT, _13)

#define metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \

metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \

SEP \

MACRO(14, CONTEXT, _14)

#define metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \

metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \

SEP \

MACRO(15, CONTEXT, _15)

#define metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \

metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \

SEP \

MACRO(16, CONTEXT, _16)

#define metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \

metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \

SEP \

MACRO(17, CONTEXT, _17)

#define metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \

metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \

SEP \

MACRO(18, CONTEXT, _18)

#define metamacro_foreach_cxt20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \

metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \

SEP \

MACRO(19, CONTEXT, _19)

metamacro_foreach_cxt这个宏定义有点像递归,这里可以看到N 最大就是20,于是metamacro_foreach_cxt19就是最大,metamacro_foreach_cxt19会生成rac_weakify_(0,__weak,_18),然后再把前18个数传入metamacro_foreach_cxt18,并生成rac_weakify_(0,__weak,_17),依次类推,一直递推到metamacro_foreach_cxt0。

#define metamacro\_foreach\_cxt0(MACRO, SEP, CONTEXT)

metamacro_foreach_cxt0就是终止条件,不做任何操作了。

于是最初的@weakify就被替换成

autoreleasepool {}

metamacro_foreach_cxt1(rac_weakify_, , __weak, self)

#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

代入参数

autoreleasepool {}

rac_weakify_(0,__weak,self)

最终需要解析的就是racweakify

#define rac_weakify_(INDEX, CONTEXT, VAR) \

CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);

把(0,weak,self)的参数替换进来(INDEX, CONTEXT, VAR)。 INDEX = 0, CONTEXT = weak,VAR = self,

于是

CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);

等效替换为

__weak __typeof__(self) self_weak_ = self;

最终@weakify(self) = weak typeof_(self) self_weak = self;

这里的selfweak 就完全等价于我们之前写的weakSelf。

2. strongify

再继续分析strongify(…)

rac_keywordify还是和weakify一样,是autoreleasepool {},只为了前面能加上@

_Pragma("clang diagnostic push") \

_Pragma("clang diagnostic ignored \"-Wshadow\"") \

_Pragma("clang diagnostic pop")

strongify比weakify多了这些_Pragma语句。

关键字_Pragma是C99里面引入的。_Pragma比#pragma(在设计上)更加合理,因而功能也有所增强。

上面的等效替换

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wshadow"

#pragma clang diagnostic pop

这里的clang语句的作用:忽略当一个局部变量或类型声明遮盖另一个变量的警告。

最初的

#define strongify(...) \

rac_keywordify \

_Pragma("clang diagnostic push") \

_Pragma("clang diagnostic ignored \"-Wshadow\"") \

metamacro_foreach(rac_strongify_,, __VA_ARGS__) \

_Pragma("clang diagnostic pop")

strongify里面需要弄清楚的就是metamacroforeach 和 rac_strongify。

#define metamacro_foreach(MACRO, SEP, ...) \

metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)

#define rac_strongify_(INDEX, VAR) \

__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);

我们先替换一次,SEP = 空 , MACRO = racstrongify , VA_ARGS , 于是替换成这样。

metamacro_foreach_cxt(metamacro_foreach_iter,,rac_strongify_,self)

根据之前分析,metamacroforeach_cxt再次等效替换,metamacro_foreach_cxt##1(metamacro_foreach_iter,,rac_strongify,self)

根据

#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

再次替换成metamacroforeach_iter(0, rac_strongify, self)

继续看看metamacro_foreach_iter的实现

#define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG)

最终替换成racstrongify(0,self)

#define rac_strongify_(INDEX, VAR) \

__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);

INDEX = 0, VAR = self,于是@strongify(self)就等价于

__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);

等价于

__strong __typeof__(self) self = self_weak_;

注意@strongify(self)只能使用在block中,如果用在block外面,会报错,因为这里会提示你Redefinition of ‘self’。

总结一下

@weakify(self) = @autoreleasepool{} weak typeof_ (self) self_weak = self;

@strongify(self) = @autoreleasepool{} strong typeof_(self) self = self_weak;

经过分析以后,其实@weakify(self) 和 @strongify(self) 就是比我们日常写的weakSelf、strongSelf多了一个@autoreleasepool{}而已,至于为何要用这些复杂的宏定义来做,目前我还没有理解。如果有大神指导其中的原因,还请多多指点。

更新

针对文章中给的例子3,大家都提出了疑问,为何没有检测出循环引用?其实这个例子有点不好。因为这个ViewController的引用计数一出来就是6,因为它被其他很多对象引用着。当然它是强引用了student,因为student的retainCount值是2。ViewController释放的时候才会把student的值减一。针对这个例子3,我重新抽取出中间的模型,重新举一个例子。

既然ViewController特殊,那我们就新建一个类。

#import

#import "Student.h"

@interface Teacher : NSObject

@property (copy , nonatomic) NSString *name;

@property (strong, nonatomic) Student *stu;

@end

#import "ViewController.h"

#import "Student.h"

#import "Teacher.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

[super viewDidLoad];

Student *student = [[Student alloc]init];

Teacher *teacher = [[Teacher alloc]init];

teacher.name = @"i'm teacher";

teacher.stu = student;

student.name = @"halfrost";

student.study = ^{

NSLog(@"my name is = %@",teacher.name);

};

student.study();

}

深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用(下)

深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用(下)

如图所示,还是出现了循环引用,student的block强引用了teacher,teacher又强引用了student,导致两者都无法释放。