zoukankan      html  css  js  c++  java
  • ARC下循环引用的问题

    最初

    最近在开发应用时碰到使用ASIHttpRequest后在某些机器上发不出请求的问题,项目开启了ARC,代码是这样写的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @implement MainController
    - (void) fetchUrl{
        ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:currUrl]];
        [request setCompletionBlock:^{
            NSLog(@"completed");
        }];
        [request startAsynchronous];
    }
    @end

    后来发现原因是request这个变量在退出这个函数后就被释放了,自然发不出请求。因为用了ARC,没法手动调用[request retain]让这个变量不被释放,所以只能把这个变量变成实例变量,让Controller实例存在的过程中一直持有这个变量不释放。

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    @interface MainController {
         ASIHTTPRequest *request;
    }
    @end
     
    @implement MainController
    - (void) fetchUrl{
        request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:currUrl]];
        [request setCompletionBlock:^{
            [self complete];
        }];
        [request setFailedBlock:^{
              NSLog(@"failed");
        }];
        [request startAsynchronous];
    }
    @end

    问题一

    这下发送请求没问题了,但出了另一个问题,XCode编译后提示[self complete]这一行可能会导致循环引用。因为MainController实例持有request, request持有completionBlock,completionBlock又持有MainController,导致循环引 用,MainController实例在外界引用计数为0时仍无法被释放,因为自身的变量request里持有MainController实例的引用, 其引用计数永远大于1。

    导致这样循环引用的原因是在completionBlock里调用的self是一个strong类的引用,会使self引用计数+1,可以保证在调 用过程self不会被释放,但在这里不需要这样的保证,可以声明另一个__weak变量指向self,这样在block使用这个变量就不会导致self引 用计数+1,不会导致循环引用。

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    @implement MainController
    - (void) fetchUrl{
         request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:currUrl]];
         __weak id this = self;
     
        [request setCompletionBlock:^{
            [this complete];
        }];
        [request startAsynchronous];
    }
    @end

    这样循环引用问题就解决了,不过__weak只支持iOS5.0以上,5以下的要用__unsafe_unretain代替__weak,区别是对 象被释放后__weak声明的变量会指向nil,安全点,__unsafe_unretain不会,变成野指针容易导致应用crash。

    问题二

    如果在block只是调用下MainController的方法,上面的解决方法就够了,但我的需求是在block里要调用到很多实例变量,包括赋值:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @interface MainController {
         ASIHTTPRequest *request;
         BOOL isLoading;
         UIView *loadingView;
    }
    @end
     
    @implement MainController
    - (void) fetchUrl{
        request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:currUrl]];
     
        [request setCompletionBlock:^{
            isLoading = NO;
            loadingView.hidden = NO;
        }];
        [request startAsynchronous];
    }
    @end

    XCode提示说isLoading = NO和loadingView.hidden = NO两行都可能导致循环引用,这下难办了,对于loadingView,是可以跟self一样再声明一个__weak引用给block用,但像 isLoading这样需要赋值的没法这样做,而且使用的实例变量多的情况下每个都另外声明__weak变量也是很烦。想半天想到三个办法:

    1

    实例变量全部加上get set方法,通过声明的__weak变量访问,缺点是破坏了封装性,把原本私有的实例变量变成公有。

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @interface MainController {
         ASIHTTPRequest *request;
    }
    @property (nonatomic, strong) UIView *loadingView;
    @property (nonatomic, assign) BOOL isLoading;
    @end
     
    @implement MainController
    @synthesize loadingView, isLoading;
     
    - (void) fetchUrl{
         request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:currUrl]];
         __weak id this = self;
     
        [request setCompletionBlock:^{
            this.isLoading = NO;
            this.loadingView.hidden = NO;
        }];
        [request startAsynchronous];
    }
    @end

    2

    在类里声明一个方法专门处理,缺点是麻烦,每一个回调都要另外声明一个实例方法,代码变丑。

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    @interface MainController {
         ASIHTTPRequest *request;
         BOOL isLoading;
         UIView *loadingView;
    }
    @end
     
    @implement MainController
    - (void) complete:(ASIHttpRequest *)request
    {
            isLoading = NO;
            loadingView.hidden = NO;
    }
    - (void) fetchUrl{
         request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:currUrl]];
         __weak id this = self;
         __weak ASIHttpRequest *_request = request;
     
        [request setCompletionBlock:^{
            [this complete:request];
        }];
        [request startAsynchronous];
    }
    @end

    3

    在block结束手动释放request。在循环引用里出现的问题是MainController外部引用计数为0时它仍不能释放,但如果我们通过 手动设置request=nil,导致request变量指向的对象引用计数为0被释放,它对MainController的引用也就释放 了,MainController在外部引用计数为0时就可以正常释放了,解决了循环引用的问题。这个做法的缺点是XCode的警告提示还存在着。

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @interface MainController {
         ASIHTTPRequest *request;
         BOOL isLoading;
         UIView *loadingView;
    }
    @end
     
    @implement MainController
    - (void) fetchUrl{
        request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:currUrl]];
        [request setCompletionBlock:^{
            isLoading = NO;
            loadingView.hidden = NO;
            request = nil;
        }];
        [request startAsynchronous];
    }
    @end

    不知还有没有更好的方法?

  • 相关阅读:
    安卓逆向5.Android Studio JNI静态注册(C++和Java互操作)
    安卓逆向二
    ASP.Net Core Web 在IIS下的发布流程
    Android Studio安装记录
    Vistual studio智能提示不显示或者显示为英文的解决办法
    (转)程序语言理论的学习对于程序员教育的作用
    普通用户ssh无密码登录设置
    (转)完全用GNU/Linux工作 by 王珢
    (转)谁是真正的程序语言专家
    java操作XML
  • 原文地址:https://www.cnblogs.com/duyuiOS/p/4897536.html
Copyright © 2011-2022 走看看