iOS 中的循环引用

作者: 天空中的球 | 来源:发表于2016-07-01 00:39 被阅读5899次

此处记录的都是ARC情况下~~~

循环引用是什么?

当两个不同的对象各有一个强引用指向对方,那么循环引用便产生了,当然多一个对象产生的环也是一样的。

两个对象通过彼此之间的强引用构成保留环

也可以直接从引用计数说明,此时如果互相引用的时候,双方的引用计数都是+1的,导致任何时候引用计数都不为0,始终无法释放,无法释放他们的内存,即使已经没有变量持有他们。

循环引用带来什么危害

循环引用对 app 有潜在的危害,会使内存消耗过高,性能变差和 app 闪退等。

循环引用有哪些具体的情况?block ? delegate ? NSTimer?

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

进行弱引用后,打破了循环,我们可以这样:

 __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,那么 两个类之间调用代理就是这样的啦

BViewController *bViewController = [[BViewController alloc] init];
bViewController.delegate = self; //假设 self 是AViewController
[self.navigationController pushViewController:bViewController animated:YES];

/**
 假如是 strong 的情况
    bViewController.delegate ===> AViewController (也就是 A 的引用计数 + 1)
    AViewController 本身又是引用了 <BViewControllerDelegate> ===> delegate 引用计数 + 1
 导致: AViewController <======> Delegate ,也就循环引用啦
 */
  • Delegate创建并强引用了 AViewController;(strong ==> A 强引用、weak ==> 引用计数不变)

所以用 strong的情况下,相当于 Delegate 和 A 两个互相引用啦,A 永远会有一个引用计数 1 不会被释放,所以造成了永远不能被内存释放,因此weak是必须的。

4、NSTimer

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

循环引用

此时解决的方法还是用 “Effective Objective-C ”中的52条方法

#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方法。

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

相关文章

网友评论

  • yt_c742:个人认为有个小错误。timer是在runloop里面执行的,不预先停止的话引用数就不会为0,dealloc方法也应该不会走。以上纯属个人观点,欢迎大神指点
    尴尬的炫半人马:放在viewWillDisapper方法中释放NSTimer比较好,在dealloc中不会走的
  • Pusswzy:能麻烦您解释一下[但是注意 block 里面直接用“成员变量”的情况,有些情况下我们是没法直接扑捉到 间接引用的那个self,或者说扑捉到的那个self 是不对的,也就没法对其进行weak引用啦,循环依然存在,所以建议直接用属性!]么?
  • 梁森的简书:block中那样写不会解决循环引用吧?B类对象是A类的一个属性,B类中的一个block中使用了A类本身,使用weak还是会有循环引用啊
    天空中的球:这个一般情况下是解决了的,使用 weak 对其弱引用了,那个环已经断了的。
    按照以 A类 和 B类对比的就是,A 之前默认是强引用 B的, B 也相当于默认强引用了 该 block , 该 block 再引用 A,这样就形成环了。当用 weak 的时候,A 就弱引用了 B 啊,这个环就破坏啦啊。
  • 郑明明:再次看到这篇文章哈哈,有个点需要和问楼主一下,不知道楼主是否对NSNumber的分类中所写的代码能够全部理解,userInfo的值为什么是[block copy],希望楼主能给出解答
    天空中的球:@NtZheng 是指 NSTimer 常规的写法吗? 最好使用下该分类的形式或者其他类似的处理方法,当然也有一些做法是在 viewWillAppear 和 viewWillDisappear 做相应的处理来避免这个问题也行。
    郑明明:@天空中的球 那如果用常规的写法应该怎么做呢
    天空中的球:因为此处是传参的,通过 copy 将该 block 从栈上拷贝到堆上保留它的,否则等到执行它的时候,可能该 blcok 是无效的。
  • 绍清_shao:写的很好:+1:
  • 郑明明:好!
  • 维维豆奶1991:有个疑问。3.delegate BViewController *bViewController = [[BViewController alloc]init]; 其中bViewController 是局部变量 。当方法执行完,就销毁。。那所谓对A的引用计数不就减1了嘛
    天空中的球:@维维豆奶招上进代理 那个地方是我没表达清楚,不应该那样举例,已经改了,谢谢

本文标题:iOS 中的循环引用

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