美文网首页iOS知识点OC基础ios
iOS 循环引用的四种情况

iOS 循环引用的四种情况

作者: peaktan | 来源:发表于2017-04-10 17:55 被阅读532次

    参考文章:
    http://www.jianshu.com/p/a51c6dd12587
    http://www.jianshu.com/p/ddfd1b3c0298

    一、循环引用是什么?

    ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露。导致iOS对象无法按预期释放的一个无形杀手是——循环引用。循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。若当前对象是一个ViewController,则在dismiss或者pop之后其dealloc无法被调用,在频繁的push或者present之后内存暴增,然后APP就duang地挂了。

    二、循环引用的四种情况

    1、父类与子类

    例如,我们在使用UITableView 的时候,传一个 Controller 给 Cell 使用

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        TestTableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:@"UITableViewCellIden" forIndexPath:indexPath];
        cell.tableView = tableView;
        return cell;
    }
    
    @interface TestTableViewCell : UITableViewCell
    @property (nonatomic, weak) UITableView *tableView;
    @end
    

    此时如果我们给 tableView 的属性特质strong就会用造成循环引用的,UITableView ==> UITableViewCell; UITableViewCell ==> UITableView 。因此 weak是肯定的。

    2、Block

    Block 是我们常用到的,也是我们最要注意循环引用的。在此,先拿一个我们使用 Block 的例子:

    typedef void (^TestCircleBlock)();
    @property (nonatomic, copy) TestCircleBlock testCircleBlock;
    
    if (self.testCircleBlock) {
        // 具体实现
        self.testCircleBlock();
    }
    

    使用:

    self.testObject.testCircleBlock = ^{
       [self doSomething];
    };
    
    • block在copy时都会对block内部用到的对象进行强引用的。
    • 该类又将block作为自己的属性变量,而该类在block的方法体里面又使用了该类本身,此时就很简单的形成了一个环啦。

    解决办法:

     __weak typeof(self) weakSelf = self;
     self.testObject.testCircleBlock = ^{
          __strong typeof (weakSelf) strongSelf = weakSelf;
          [strongSelf doSomething];
    };
    

    在 ARC 中,在被拷贝的 block 中无论是直接引用 self 还是通过引用 self 的成员变量间接引用 self,该 block 都会 retain self。但是注意 block 里面直接用“成员变量”的情况,有些情况下我们是没法直接扑捉到 间接引用的那个self,或者说扑捉到的那个self 是不对的,也就没法对其进行weak引用啦,循环依然存在,所以建议直接用属性!
    总的说来,block 我们很有必要深入了解,了解为什么用 copy,了解它在内存中的位置,更有利于我们理解的。Block基础和retain cycle(循环引用)

    3、Delegate

    相信类似下面这样的代理属性,我们写了不要太多遍了吧

    @property (nonatomic, weak) id <TestDelegate> delegate;
    

    说白了就是循环使用的问题,假如我们是写的strong,那么 该对象强引用delegate,外界不能销毁delegate对象,会导致循环引用(Retain Cycles)

    4、NSTimer

    NSTimer 其实相对来说,我们其实是很容易忽略它这种情况的,毕竟还是很特殊的。


    循环引用.png

    例如:

    #import "OneViewController.h"
    
    @interface OneViewController ()
    
    
    @property (nonatomic, strong) NSTimer *timer;
    
    @end
    
    
    @implementation OneViewController
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
        [self dismissViewControllerAnimated:YES completion:nil];
    }
    
    -(void)viewDidLoad{
    
        [super viewDidLoad];
        self.view.backgroundColor = [UIColor orangeColor];
    
        /**
         1.__weak typeof(self) weakSelf = self; 不能解决
    
         */
    
        //开启定时器 
        self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(testTimerDeallo) userInfo:nil repeats:YES];
    }
    
    /** 方法一直执行 */
    -(void)testTimerDeallo{
    
        NSLog(@"-----");
    }
    

    当开启定时器以后,testTimerDeallo方法一直执行,即使dismiss此控制器以后,也是一直在打印,而且dealloc方法不会执行.循环引用造成了内存泄露,控制器不会被释放.

    /** 开启定时器以后控制器不能被销毁,此方法不会被调用 */
    -(void)dealloc{
        NSLog(@"xiaohui");
        [self.timer invalidate];
    }
    @end
    

    解决办法:由于循环引用的起因是target,则可以包装一个target,让target是另一个对象,而不是ViewController即可

    #import <Foundation/Foundation.h>
    
    @interface NSTimer (YPQBlocksSupport)
    
    + (NSTimer *)ypq_scheduledTimeWithTimeInterval:(NSTimeInterval)interval
                                             block:(void(^)())block
                                           repeats:(BOOL)repeats;
    
    @end
    
    #import "NSTimer+YPQBlocksSupport.h"
    
    @implementation NSTimer (YPQBlocksSupport)
    
    + (NSTimer *)ypq_scheduledTimeWithTimeInterval:(NSTimeInterval)interval
                                             block:(void(^)())block
                                           repeats:(BOOL)repeats
    {
        return [self scheduledTimerWithTimeInterval:interval
                                             target:self
                                           selector:@selector(ypq_blockInvoke:) userInfo:[block copy]
                                            repeats:repeats];
    }
    
    + (void)ypq_blockInvoke:(NSTimer *)timer
    {
        void (^block)() = timer.userInfo;
        if(block)
        {
            block();
        }
    }
    @end
    

    具体使用

    __weak ViewController * weakSelf = self;
    [NSTimer ypq_scheduledTimeWithTimeInterval:4.0f
                                         block:^{
                                             ViewController * strongSelf = weakSelf;
                                             [strongSelf afterThreeSecondBeginAction];
                                         }
                                       repeats:YES];
    

    计时器保留其目标对象,反复执行任务导致的循环,确实要注意,另外在dealloc的时候,不要忘了调用计时器中的 invalidate方法。

    如何避免循环引用?理解上面几种情况的发生的情况,我们一般就不会造成循环引用啦,反正永远遵循,不要让对象不能被释放,谁创建谁释放!

    相关文章

      网友评论

        本文标题:iOS 循环引用的四种情况

        本文链接:https://www.haomeiwen.com/subject/ynvyottx.html