• 小结OC中Retain cycle(循环引用)


    retain cycle 的产生

    说到retain cycle,首先要提一下Objective-C的内存管理机制。

    作为C语言的超集,Objective-C延续了C语言中手动管理内存的方式,但是区别于C++的极其非人道的内存管理,Objective-C提出了一些机制来减少内存管理的难度。 比如:内存计数。

    在 Objective-C中,凡是继承自NSObject的类都提供了两种方法,retain和release。当我们调用一个对象的retain时,这个 对象的内存计数加1,反之,当我们调用release时, 对象的内存计数减1,只有当对象内存计数为0时,这个对象才真正会被释放,此时,对象的delloc方法会被调用来做些内存回收前的工作。

    内 存计数机制的好处在于我们可以明确分配一个使用权。比如,当一个对象A要使用另外一个对象B的时候,A会retain B一次以表示A使用B,而当B被使用完毕之后,A会 调用B的release方法来放弃使用权。这样,一个对象可以被多个其他对象使用。而作为使用它的对象,也不必关心自己之外 被使用对象的使用情况(内存方面)。一般来讲,对于类的成员变量,retain和release分别发生在赋值和自身释放的时候,这就是Obj-C程序中 的经典写法:

    头文件中:

    @property (nonatomic,retain) NSObject *obj;

    在.m文件里:

    - (void)dealloc{    [obj release];    [super dealloc];}

    OK,这种方式可以很容易地管理内存,但是仍存在这一个问题,这就是retain cycle。

    Retain cycle,翻译成中文大概叫保留环吧。既然父对象持有子对象,而子对象会随父对象释放而释放,那么,如果两个对象相互为父对象怎么办?

    比如A和B两个对象,A持有B,B同时也持有A,按照上面的规则,A只有B释放之后才有可能释放,同样B只有A释放后才可能释放,当双方都在等待对方释放的时候, retain cycle就形成了,结果是,两个对象都永远不会被释放,最终内存泄露。

    retain cycle使你编程的时候不得不注意一些问题。例如,要么尽量保持子对象引用父对象的时候使用弱引用,也就是assign,比如

    @property (nonatomic,assign) NSObject *parent;

    要么及时地将造成retain cycle中的一个变量设置为nil,将环break掉。如果注意点,这并不是什么特别大的问题。

    嗯,注意点确实不是什么问题,但是当IOS 4.0只后,block的出现,使你更需要更为谨慎。

    block与内存管理

    block 就是一段可以灵活使用的代码,你可以把它当变量传递,赋值,甚至可以把它声明到函数体里,更灵活的是你可以在里面引用外部的环境。 最后一条使得block要有更多的考虑,既然block可以引用外部环境,那如何保证block被调用的时候当时的环境变量不被释放呢?(block调用 的时机可能是随意的)

    答案就是,被block引用的变量都会被自动retain一次,这样的话至少可以保证我们的调用是有效的。

    说到这里你能想到什么吗?对,还是retain cycle。因为block中的retain是隐式的,所以极易出现retain cycle的问题。

    因为block本身也可以看做一个对象,也存在生命周期,也可以被持有,所以当这种情况出现的时候,我们该注意了,比如:

    DoSomethingManager *manager = [[DoSomethingManager alloc] init];manager.complete = ^{    //...complete actions    [manager otherAction];    [manager release];};

    retain cycle 就这么形成了,即使调用了release,manager也不会释放,因为manager和block相互持有了。为了解除retain cycle的话,我们可以这样写:

    DoSomethingManager *manager = [[DoSomethingManager alloc] init];manager.complete = ^{    //...complete actions    [manager otherAction];    manager.complete = nil;    [manager release];};

    manager的complete被设置为nil,如此一来retain cycle也被破坏掉,前提是你确实不需要再次回调block了。

    本来写到这里就算完了,但是新世纪总有新的挑战,这就在于在Apple有推出了一种新的技术 ARC。

    ARC 和 retain cycle

    ARC (Auto Reference Counting), 翻译为自动引用计数,是Apple为了进一步简化内存管理来推出的技术。虽然为自动内存管理而生,但却并算不上真正的自动管理。 这是因为ARC是一种编译期的技术,它所做的是自动识别你的代码并转换成retain/release的形式,在这个层面上来看,ARC无非是简化了代码 的书写,并提供了部分性能上的优化, 而并不像Java之类的语言可以完全把垃圾回收抛之脑后(基本上)。关于ARC的细节可以看下面的网址:

    http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html

    下面我们主要谈下ARC下retain cycle的问题。

    ARC中,变量可以用三个关键字修饰:

    __strong: 赋值给这个变量的对象会自动被retain一次,如果在block中引用它,block也会retain它一次。__unsafe_unretained: 赋值给这个变量不会被retain,也就是说被他修饰的变量的存在不能保证持有对象的可靠性,它可能已经被释放了,而且留下了一个不安全的指针。不会被block retain。 __week:类似于__unsafe_unretained,只是如果所持有的对象被释放后,变量会自动被设置为nil,这样更安全些,不过只在IOS5.0以上的系统支持,同样不会被block retain。

    另外我们也可以用 __block 关键字修饰一个变量,表示这个变量能在block中被修改(值修改,而不是修改对象中的某一个属性,可以理解为修改指针的指向)。会被自动retain。

    于其他变量不同的是被 __block 修饰的变量在块中保存的是变量的地址。(其他为变量的值)

    首先,上面的代码你现在可以这么写:

    DoSomethingManager *manager = [[DoSomethingManager alloc] init];manager.complete = ^{    //...complete actions    [manager otherAction];    manager.complete = nil;};

    没什么问题,只是去掉了ARC中禁止的release。

    当然,我们也可以这么写。

    __block DoSomethingManager *manager = [[DoSomethingManager alloc] init];manager.complete = ^{    //...complete actions    [manager otherAction];    manager = nil;};

    如 果不用ARC,manager不会在block中被retain,但是采用了ARC就有些复杂了。block会retain manager变量,但是,由于__block变量保存更为底层的变量地址, 因此当此变量被指向其他对象时,block便不对原来的对象负责,引发的结果就是之前对象被release掉,retain cycle被破坏。

    或者这么写:

    __block DoSomethingManager *manager = [[DoSomethingManager alloc] init];DoSomethingManager __week *weekmanager = manager;manager.complete = ^{    //...complete actions    [weekmanager otherAction];};

    上面的__week也可以用 __unsafe_unretained 替代,但是 __week 更安全些,虽然它不支持IOS5.0以下的系统。

    __week 或者 __unsafe_unretained 修饰的变量不会被block retain,所以不会形成retain cycle,但是小心,保证你的对象不会在complete之前被释放,否则会得到你意向不到的结果。

      下面我们对OC中常见的循环引用做个小结

    回顾上面提到的循环引用的概念:

    • 循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放;

    • 造成循环引用的三种情况

      • 计时器NSTimer
        • 原因分析:
          • NSTimer变量通过target对xxxModel有强引用,NSTimer变量是xxxModel内部的成员变量,所以xxxModel对该成员变量有强引用 技术分享技术分享
        • 解决办法:
          • 提供类似cleanTimer的公开方法,让外界主动调用释放NSTimer变量
      • delegate

        • 原因分析:
          • 如果将代理设置为强应用,那么控件就会对它的使用者有一个强引用,由于控件的使用者对控件已经产生强引用,这样就会造成循环引用 技术分享技术分享
        • 解决办法:
          • 声明delegate时请用assign(MRC环境)或者weak(ARC环境),千万别手贱玩一下retain或者strong,毕竟这基本逃不掉循环引用了
      • block

        • 原因分析:
          • 某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了self的成员,因为直接在block使用self的成员,这样block内部就会对self进行强引用,导致循环引用
          • 只要你在block里用到了self的成员,就会出现循环引用,并不需要显式地出现self,block的这种循环引用会被编译器捕捉到并及时提醒 技术分享技术分享
        • 解决办法:
          • (1)构建一个weakSelf,通过这种方式告诉block,不要在block内部对self进行强制strong引用
            // ARC环境:使用__weak关键字
            __weak typeof(self) weakSelf = self;
            // MRC环境:使用__block关键字
            __block typeof(self) weakSelf = self;
            
          • (2)再将weakSelf在block中使用,就不会造成循环引用
          • 值得注意的是:使用系统的某些block API,如UIView的block版本写动画时不需要考虑,但也有一些API需要考虑
            • 以下这些使用方式不会引起循环引用问题
            • [UIView animateWithDuration:(NSTimeInterval) animations:^{
                  [self.superView layoutIfNeeded];
              };
              [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                  [self.someProperty = xyz];
              }
                  
              [[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
                  [self.someProperty = xxx];
              }];

              但是如果方法中的一些参数是成员变量,那就会造成循环引用,如GCD、NSNotificationCenter调用时就要小心一点,比如GCD内部如果引用了self,而且GCD的参数是成员变量,则要考虑到循环引用,举例如下:

            • GCD
               //分析self-->queue-->block-->self 循环引用
                  __weak typeof(self) weakSelf = self;
                  dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, ^{
                      [weakSelf doSomething];
                      [weakSelf doSomethingElse];
                  });
            • NSNotificationCenter
                //分析:self-->observer -->block -->self 形成循环 引用
               __weak typeof(self) weakSelf = self;
              [[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
                  [weakSelf dismissViewControllerAnimated:YES completion:^{
                      //code
                  }];
              }];
     
     
  • 相关阅读:
    mysql函数
    存储过程1
    linux下手动安装git教程
    python离线安装外部依赖包
    自动代码质量分析(GitLab+JenKins+SonarQube)
    Jenkins定时构建和轮询SCM设置说明
    jenkins
    linux在当前目录下根据文件名查找文件
    elastic search报错——“failed to obtain node locks”
    linux下rpm包安装、配置和卸载mysql
  • 原文地址:https://www.cnblogs.com/mysticCoder/p/4999376.html
Copyright © 2020-2023  润新知