zoukankan      html  css  js  c++  java
  • iOS 循环引用讲解(中)

    谈到循环引用,可能是delegate为啥非得用weak修饰,可能是block为啥要被特殊对待,你也可能仅仅想到了一个weakSelf,因为它能解决99%的关于循环引用的事情。下面我以个人的理解谈谈循环引用,读完这篇文章,大约需要15-20分钟的时间。

    一、循环引用的产生

    当A对象里面强引用了B对象,B对象又强引用了A对象,这样两者的retainCount就一直都无法为0于是内存无法释放,导致内存泄露,所谓的内存泄露,本应该释放的对象,在生命周期结束之后依旧存在。换句话说:得说下内存中和变量有关的分区:堆、栈、静态区。其中,栈和静态区是操作系统自己管理的,对程序员来说相对透明,所以,一般我们只需要关注堆的内存分配,而循环引用的产生,也和其息息相关,即循环引用会导致堆里的内存无法正常回收。如下图:


    此处:若想释放内存,需要A的引用计数为0,而B对象持有A,所以想要A dealloc,需要B发送release消息到A。而B只有dealloc的时候才会发送release消息到A,并且B dealloc也需要A发送release消息到B。这样A和B相互等待对方的release消息,造成循环引用,内存无法释放。

    二、循环引用的情况

    下面我们分析造成循环引用的几种情况:

    1.delegate与环

     1 @protocol ClssADelegate 
     2 - (void)eat;
     3 @end
     4 @interface ClassA : UIViewController
     5 @property (nonatomic, strong) id  delegate;
     6 @end
     7 //ClassB:
     8 @interface ClassB ()
     9 @property (nonatomic, strong) ClassA *classA;
    10 @end
    11 @implementation ClassB
    12 - (void)viewDidLoad {
    13     [super viewDidLoad]; 
    14     self.classA = [[ClassA alloc] init];  
    15     self.classA.delegate = self;
    16 }

    如上代码,B强引用A,而A的delegate属性指向B,这里的delegate是用strong修饰的,所以A也会强引用B,这是一个比较典型的循环引用样例。所以要将代理delegate改为弱引用weak。

    2.block与环

     1 @interface ClassA ()
     2 @property (nonatomic, copy) dispatch_block_t block;
     3 @property (nonatomic, assign) NSInteger tem;
     4 @end
     5 @implementation ClassA
     6 - (void)viewDidLoad {
     7     [super viewDidLoad];
     8     self.block = ^{
     9         self.tem = 1;
    10     };  
    11 }

    如上代码,self持有block,而堆上的block又会持有self,所以会导致循环引用,这个例子非常好,因为xcode都能检测出来,报出警告:[capturing self strongly in this block is likely to lead to a retain cycle],当然大部分循环引用的情况xcode是不会报警告的。解决这种循环引用的常用方式如下:

    @interface ClassA ()
    @property (nonatomic, copy) dispatch_block_t block;
    @property (nonatomic, assign) NSInteger tem;
    @end
    @implementation ClassA
    - (void)viewDidLoad {
        [super viewDidLoad];
        __weak typeof(self) weakSelf = self
        self.block = ^{
            weakSelf.tem = 1;
        };  
    }

    结论:

    如上delegate和block引起的循环引用的处理方式,有一个共同的特点,就是使用weak(弱引用)来打破坏,使环消失了,所以得出结论,我们可以通过将Strong(强引用)用weak来代替来解决循环引用。

    >>>>>>>拓展

    (1)weakSelf与其缺陷

     1 //ClassB是一个UIViewController,假设从ClassA pushViewController将ClassB展示出来
     2 @interface ClassB ()
     3 @property (nonatomic, copy) dispatch_block_t block;
     4 @property (nonatomic, strong) NSString *str;
     5 @end
     6 @implementation ClassB
     7 - (void)dealloc {
     8 }
     9 - (void)viewDidLoad {
    10     [super viewDidLoad];
    11     self.str = @"111";
    12     __weak typeof(self) weakSelf = self;
    13     self.block = ^{
    14         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    15             NSLog(@"%@", weakSelf.str);
    16         });
    17     };
    18     self.block();   
    19 }

    这里有两种情况:

    (!)若从Apush到B,10s之内没有pop回到A的话,B中block会执行打印出来111.

    (!!)若从Apush到B,10s之内pop回A的话(B控制器已经释放掉了),B会立即执行dealloc,从而导致B中block打印出(null),这种情况是使用weakSelf的缺陷,可能会内存被提前释放。

    (2)weakSelf和strongSelf

     1 @interface ClassB ()
     2 @property (nonatomic, copy) dispatch_block_t block;
     3 @property (nonatomic, strong) NSString *str;
     4 @end
     5 @implementation ClassB
     6 - (void)dealloc {
     7 }
     8 - (void)viewDidLoad {
     9     [super viewDidLoad];
    10     self.str = @"111";
    11     __weak typeof(self) weakSelf = self;
    12     self.block = ^{
    13         __strong typeof(self) strongSelf = weakSelf;
    14         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    15             NSLog(@"%@", strongSelf.str);
    16         });
    17     };
    18     self.block();   
    19 }

    这样做解决了上面的问题,但可能有一些问题不是很理解:

    (!)这么做和直接用self有什么区别,为什么不会有循环引用;外部的weakSelf是为了打破环,从而没有循环引用,而内部的strongSelf仅仅是个局部变量,存在栈中,会在block执行结束之后回收,不会再造成循环引用。

    (!!)这么做和weakSelf有什么区别:唯一的区别就是多了一个strongSelf,这么的strongSelf会使classB的对象引用计数+1,使用ClassB pop到A的时候,并不会执行dealloc,因为计数还不为0,strongSelf仍持有classB,而在block执行完,局部的strongSelf才会回收,此时ClassB dealloc。

    这样做其实已经可以解决所有问题,但是强迫症的我们依然能找到它的缺陷:

    3、@weakify和@strongify

    查看github上开源的libextobjc库,可以发现,里面的EXTScope.h里面有两个关于weak和strong的宏定义。

     1 // 宏定义
     2 #define weakify(...) 
     3     ext_keywordify 
     4     metamacro_foreach_cxt(ext_weakify_,, __weak, __VA_ARGS__)
     5 #define strongify(...) 
     6     ext_keywordify 
     7     _Pragma("clang diagnostic push") 
     8     _Pragma("clang diagnostic ignored "-Wshadow"") 
     9     metamacro_foreach(ext_strongify_,, __VA_ARGS__) 
    10     _Pragma("clang diagnostic pop")
    11 
    12 // 用法
    13 @interface ClassB ()
    14 @property (nonatomic, copy) dispatch_block_t block;
    15 @property (nonatomic, strong) NSString *str;
    16 @end
    17 @implementation ClassB
    18 - (void)dealloc {
    19 }
    20 - (void)viewDidLoad {
    21     [super viewDidLoad];
    22     self.str = @"111";
    23     @weakify(self)
    24     self.block = ^{
    25         @strongify(self)
    26         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    27             NSLog(@"%@", self.str);
    28         });
    29     };
    30     self.block();   
    31 }

    可以看出,这样就完美的解决了上述的缺陷,我们可以在block随意使用self。

    3.NSTimer的循环引用

    使用NSTimer可能会碰到循环引用的问题。特别是当类具有NSTimer类型的成员变量,并且需要反复执行计时任务时。例如:

    1 _timer = [NSTimer scheduledTimerWithTimeInterval:5.0
    2                                           target:self
    3                                        selector:@selector(startCounting) userInfo:nil
    4                                          repeats:YES];

    类有一个成员变量_timer,给_timer设置的target为这个类本身。这样类保留_timer_timer又保留了这个类,就会出现循环引用的问题,最后导致类无法正确释放。

    解决这个问题的方式也很简单,当类的使用者能够确定不需要使用这个计时器时,就调用

    1 [_timer invalidate];
    2 _timer = nil;

    这样就打破了保留环,类也可以正确释放。但是,这种依赖于开发者手动调用方法,才能让内存正确释放的方式不是一个非常好的处理方式。所以需要另外一种解决方案。如下所示:

     1 @interface NSTimer (JQUsingBlock)
     2 + (NSTimer *)jq_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
     3                                      block:(void(^)())block
     4                                    repeats:(BOOL)repeats;
     5 @end
     6 
     7 @implementation NSTimer (JQUsingBlock)
     8 
     9 + (NSTimer *)jq_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
    10                                      block:(void(^)())block
    11                                    repeats:(BOOL)repeats{
    12 
    13     return [self scheduledTimerWithTimeInterval:ti
    14                                      target:self
    15                                    selector:@selector(jq_blockInvoke:)
    16                                    userInfo:[block copy]
    17                                     repeats:repeats];
    18 }
    19 
    20 + (void)jq_blockInvoke:(NSTimer *)timer{
    21 
    22     void(^block)() = timer.userInfo;
    23     if (block) {
    24         block();
    25     }
    26 }
    27 
    28 @end

    定义一个NSTimer的类别,在类别中定义一个类方法。类方法有一个类型为块的参数(定义的块位于栈上,为了防止块被释放,需要调用copy方法,将块移到堆上)。使用这个类别的方式如下:

    1 __weak ViewController *weakSelf = self;
    2 _timer = [NSTimer jq_scheduledTimerWithTimeInterval:5.0
    3                                               block:^{
    4                                                   __strong ViewController *strongSelf = weakSelf;
    5                                                   [strongSelf startCounting];
    6                                               }
    7                                             repeats:YES];
    使用这种方案就可以防止NSTimer对类的保留,从而打破了循环引用的产生。__strong ViewController *strongSelf = weakSelf主要是为了防止执行块的代码时,类被释放了。在类的dealloc方法中,记得调用[_timer invalidate]

    今天先到此为止,改天继续讲解@property与Ivar等区别!!!
  • 相关阅读:
    python--列表,元组,字符串互相转换
    10月清北学堂培训 Day 2
    10月清北学堂培训 Day 1
    网络流小结
    P1850 换教室
    P1948 [USACO08JAN]电话线Telephone Lines
    P3147 [USACO16OPEN]262144
    8月清北学堂培训 Day 7
    8月清北学堂培训 Day3
    8月清北学堂培训 Day1
  • 原文地址:https://www.cnblogs.com/guohai-stronger/p/9011806.html
Copyright © 2011-2022 走看看