Array().addObject(ViewController())造成控制器不释放

场景

项目中有一个单例Singleton。有多个UIViewController需要向Singleton中注册观察者来接受Singleton的消息。
这种情况下,使用NSNotificationCenter的方案有些松散,因此不作考虑;
还有一种是使用delegate的方式。为了让delegate可以实现一对多的功能,可以在Singleton的里面增加一个mutableArray的变量和一个addDelegate:的方法。然后在需要接受这个Singleton通知的地方Singleton().addDelegate(self),这样可以达到目的,但是你会发现这个控制器再也不会调用dealloc

问题1

那个调用了Singleton().addDelegate(self)的控制器为什么不会dealloc()了呢?

分析1

  1. Singleton()作为单例是不会释放的;
  2. mutableArraySingleton()强引用着,因此也是不会释放的;
  3. 接受消息的控制器被添加到了mutableArray()中,就会被mutableArray()强引用,因此也不会释放;

    结论1

    控制器会一直被强引用,因此不会调用dealloc()

    问题2

    那么,如果把Singleton().addDelegate(self)中的self改成__weak呢?
    1
    2
    __weak UIViewController *weakself = self;
    Singleton().addDelegate(weakself);

    分析2

    先来看一段代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    _arr = [NSMutableArray array];
    Dog *dog1 = [[Dog alloc] init];
    NSLog(@"-- retain count: %ld", CFGetRetainCount((__bridge CFTypeRef)(dog1)));
    __weak Dog *weakDog = dog1;
    NSLog(@"-- retain count: %ld", CFGetRetainCount((__bridge CFTypeRef)(dog1)));
    NSLog(@"-- retain count: %ld", CFGetRetainCount((__bridge CFTypeRef)(weakDog)));
    [self.arr addObject:weakDog];
    NSLog(@"-- retain count: %ld", CFGetRetainCount((__bridge CFTypeRef)(dog1)));
    NSLog(@"-- retain count: %ld", CFGetRetainCount((__bridge CFTypeRef)(weakDog)));

    Dog *dog2 = dog1;

    NSLog(@"dog1 = %p", dog1);
    NSLog(@"weakdog1 = %p", weakDog);
    NSLog(@"weakdog1 = %p", self.arr.firstObject);
    NSLog(@"dog2 = %p", dog2);
    NSLog(@"retain count: %ld", CFGetRetainCount((__bridge CFTypeRef)(dog1)));
    打印结果是:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    retain count: 1
    retain count: 1
    retain count: 2
    retain count: 2
    retain count: 3
    dog1 = 0x60400000b600
    weakdog1 = 0x60400000b600
    weakdog1 = 0x60400000b600
    dog2 = 0x60400000b600
    retain count: 3
    可以验证:
    1. __weak的作用是在不增加对象引用计数的前提下持有对象的引用
    2. mutableArray会增加所持有指针所指对象的引用计数(不论是不是强指针)
    3. 指针赋值可以使新的指针指向相同的地址(与本问题无关)

      结论2

      使用__weak的弱指针同样会使当前控制器被数组强引用,同样会造成当前控制器不释放

      结论3

      通过以上分析可以看出,想要控制器得到释放,就不能让数组持有控制器的强引用,下面有三个方案可以达到这个目的:

      方法1 NSValue 不要使用这个了,最近发现会造成野指针,具体原因不明。

      1
      2
      3
      4
      NSValue *value = [NSValue valueWithNonretainedObject:myObj];
      [array addObject:value];

      value.nonretainedObjectValue

      方法2 NSPointerArray

      参见:http://blog.csdn.net/jeffasd/article/details/60774974

      方法3 Bridge

      增加一个中间层
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      @interface DelegateBridge: NSObject <aDelegate>

      @property (nonatomic, assign) id <aDelegate>delegate;

      - (instancetype)initWithDelegate:(id<aDelegate>)delegate;

      @end

      @implementation DelegateBridge

      - (instancetype)initWithDelegate:(id<aDelegate>)delegate;
      {
      self = [super init];
      if (self) {
      self.delegate = delegate;
      }
      return self;
      }

      - (void)delegateMethod {
      if (self.delegate && [self.delegate respondsToSelector:@selector(delegateMethod:)]) {
      [self.delegate delegateMethod:aMessages];
      }
      }

      @end
      addDelegate()的方法改为:
      1
      2
      3
      4
      5
      - (void)addDelegate:(id<aDelegate>)delegate
      {
      DelegateBridge *bridge = [[DelegateBridge alloc] initWithDelegate:delegate];
      [_delegates addObject:bridge];
      }