首页 > 代码库 > 深入研究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解决循环引用(下)