首页 > 代码库 > 深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用(下)
深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用(下)
深入研究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.mamicode.com/‘http://www.jobbole.com/members/wx895846013‘>@catch (...) {}
#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。
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();
}
如图所示,还是出现了循环引用,student的block强引用了teacher,teacher又强引用了student,导致两者都无法释放。
深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用(下)