zoukankan      html  css  js  c++  java
  • 深入研究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.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。

     

    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,导致两者都无法释放。

  • 相关阅读:
    ZXing 生成、解析二维码图片的小示例
    OpenLDAP 2.4.x源码安装配置
    Elasticsearch & Kibana with Shield
    Kibana SSL
    Kibana 官方示例
    ELK 处理分析日志(nginx,syslog)
    Elasticsearch 负载均衡集群
    Elasticsearch REST API小记
    ELK 安装配置
    ELK 安装配置
  • 原文地址:https://www.cnblogs.com/fengmin/p/5855188.html
Copyright © 2011-2022 走看看