美文网首页
IOS解决循环引用

IOS解决循环引用

作者: 传火的余烬 | 来源:发表于2017-02-13 17:16 被阅读0次

    虽然目前IOS都普遍使用了ARC开发,但是还是有一些情况下是必然存在循环引用的,为了更好的说明循环引用。先以MRC的代码来描述一些内存关系。

    @interface ViewController ()

    @end

    @implementation ViewController

    • (void)open{
      //av 引用计数 1
      AViewController *av = [[AViewController alloc] init];
      // presentViewController的时候 AViewController引用计数 2
      [self presentViewController:av animated:YES completion:^{
      }];
      // 这里释放一次 AViewController 引用计数 1
      [av release];
      }

    • (void)addButton{
      UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
      button.backgroundColor = [UIColor redColor];
      button.frame = CGRectMake(100, 100, 100, 100);
      [button addTarget:self action:@selector(open) forControlEvents:UIControlEventTouchUpInside];
      [self.view addSubview:button];
      }

    • (void)viewDidLoad {
      [super viewDidLoad];
      [self addButton];
      }

    @end

    这里我们看到 AViewController *av 的引用计数是1, 以下是 AViewController 的代码

    @implementation AViewController

    • (void)back{
      // AViewController 引用计数-1 (正常情况的话会形成 0)
      [self dismissViewControllerAnimated:YES completion:^{
      }];
      }

    • (void)viewDidLoad {
      [super viewDidLoad];
      }
      @end

    正常逻辑下 当我们 调用 back 的时候 这个时候 AViewController 会因为 dismissViewControllerAnimated 使得引用计数变成0,然后完成正常的内存释放。

    这个时候我们入一个 BClass 先看 BClass的定义
    @protocol BClassDelegate <NSObject>

    • (void)finished;
      @end
      @interface BClass : NSObject
      @property (nonatomic, retain) id delegate;
    • (void)startAnimation;
      @end

    注意 delegate 是 retain 关键点来了。这种设计摆明了跟其他 委托模式不一样(参考UITableView 的 delegate 是assign[因为目前是MRC 所以不是weak])。

    这个时候 在 AViewController 使用 BClass。
    @interface AViewController ()
    @property (nonatomic, retain) BClass *animation;
    @end

    • (void)userBClass{
      //tempAnimation 引用计数 1
      _animation = [[BClass alloc] init];
      //关键点(这里 AViewController 引用计数变成了 2)
      _animation.delegate = self;
      }

    • (void)viewDidLoad {
      [super viewDidLoad];
      [self userBClass];
      }

    为了降低复杂度 我们先只考虑AViewController 的内存释放,先暂时放下 animation的内存释放问题。
    这里重点情况是 _animation.delegate = self; 这里会使得AViewController 引用计数变成2。
    看下 BClass 源码

    @implementation BClass

    • (void)dealloc{
      NSLog(@"%s", func);
      [_delegate release];
      _delegate = nil;
      [super dealloc];
      }
    • (void)startAnimation{
      }
      // 这里传进来的 adelegate 是 AViewController
    • (void)setDelegate:(id)adelegate{
      [_delegate release];
      [adelegate retain]; //关键的一部使得这个引用计数 + 1 AViewController(引用计数变成2)
      _delegate = adelegate;
      }
      @end

    关键点是因为 delegate 设计成 retain类型,所以 _animation.delegate = self; 这句代码使得 AViewController 变成了2,
    这个时候 dismissViewControllerAnimated 引用计数减1, 但是 AViewController 引用计数没有变成0,所以AViewController内存没有释放,这就造成内存泄漏。到这里给出完整BClass的代码

    @interface AViewController ()
    @property (nonatomic, retain) BClass *animation;
    @end

    @implementation AViewController

    • (void)dealloc
      {
      [_animation release];
      _animation = nil;
      [super dealloc];
      }

    • (void)back{
      // AViewController 引用计数-1 (正常情况的话会形成 0)
      [self dismissViewControllerAnimated:YES completion:^{
      }];
      }

    • (void)userBClass{
      //tempAnimation 引用计数 1
      _animation = [[BClass alloc] init];
      //关键点(这里 AViewController 引用计数变成了 2)
      _animation.delegate = self;
      }

    这里因为 back的时候 我们没有办法使得AViewController引用计数变成0,所以没有调用AViewController的dealloc,所以无法
    触发 [_animation release]; 进而使得 BClass也没有被释放内存。这里就是循环引用了。
    解决方案可以这样考虑,只要在调用back之前我们能够使得 AViewController的引用计数由2变成1就可以了。
    这个时候改写back

    • (void)back{
      // 先使得AViewController 引用计数-1
      [_animation.delegate release];
      // AViewController 引用计数-1 (正常情况的话会形成 0)
      [self dismissViewControllerAnimated:YES completion:^{
      }];
      }
      这样就可以在调用back的时候 我们的AViewController引用计数减了2次,这种写法非常丑陋,虽然能够解决问题,但是不够方便。
      参考网络上有一种通过Proxy来处理这种循环引用的 先看下TestProxy的定义
      @interface TestProxy : NSProxy
      @property (nonatomic, assign) id target;
      @end

    @implementation TestProxy

    • (void)dealloc{
      [super dealloc];
      }

    • (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
      {
      NSMethodSignature *signature = [_target methodSignatureForSelector:selector];
      return signature;
      }

    • (void)forwardInvocation:(NSInvocation *)invocation{
      SEL sel = invocation.selector;
      if (_target) {
      if ([_target respondsToSelector:sel]) {
      [invocation invokeWithTarget:_target];
      }
      }
      }

    @end

    这里可以先忽略 methodSignatureForSelector 和 forwardInvocation 先重点放在内存泄漏上。
    引入TestProxy后,改写AViewController里面的userBClass和dealloc,back

    • (void)dealloc
      {
      [_animation release];
      [_proxy release];
      [super dealloc];
      }

    • (void)back{
      [self dismissViewControllerAnimated:YES completion:^{
      }];
      }

    • (void)userBClass{
      //_animation 引用计数1
      _animation = [[BClass alloc] init];
      // _proxy 引用计数1
      _proxy = [TestProxy alloc];
      // _proxy 引用计数2
      _animation.delegate = _proxy;
      }

    重点是 _animation.delegate = _proxy 这里并没有引起AViewController 引用计数的改变,所以 back的时候 AViewController能正常释放内存。所以 back触发的时候 会进入 dealloc 这个时候 [_animation release]; 会使得_animation引用计数变成了0。
    到这里我们能够正常释放了 AViewController和BClass了。剩下只有能解决NSProxy正常释放那么就能够解决所有的内存泄漏问题了。 目前 proxy引用计数是2,参考代码可以知道 AViewController 的 dealloc 有一次 [_proxy release], BClass里面有一个
    [_delegate release], BClass里面的delegate就是_proxy 所以 TestProxy的引用计数变成0,最终 AViewController, BClass 和 TestProxy 出现的3个类都成功完成内存释放。

    对比原来非常粗暴的back函数里面调用release来解决内存问题,另一种通过引入 TestProxy 把释放内存的时机放到了 AViewController 的 dealloc。一般情况建议大家这样引用TestProxy, 因为这样写法不需要考虑内存释放的时机的。同理NSTimer这种也可以引入Proxy,然后在 dealloc 调用 invalidate即可。

    总结:循环引用计数的关键点在于 setDelegate方法, aaa.delegate = self; aaa.delegate = nil 。

    相关文章

      网友评论

          本文标题:IOS解决循环引用

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