前几天有同事在讨论想设计一个检查内存不释放的功能(主要是viewcontroller),大体思路就是每次pushviewcontroller的时候把vc加入到一个集合中,然后pop出去的时候延迟几秒之后在该集合内检查对应vc是否被释放。但是如果用普通的NSArray或者NSDictionary肯定是不行的,因为会强持有这个vc,导致一直无法释放,所以就在考虑怎么能实现一个weak的集合。

思路大概有两个方向:

  • 仍然使用传统的NSArray等类型,保证add进结合的时候不对元数据产生强引用
  • 使用其他集合类型

第一个方向

  1. 第一个想法

还是用NSArray,但每次add的时候不直接add vc,而是add vc的弱引用:

1
2
__weak UIViewController *weakVC = vc;
[array addObject: weakVC];

这种方式仍然会强持有vc,因为虽然add到数组中的是弱引用的指针,但add本身影响的并不是指针的引用计数,而是所指向的堆上的内容的引用计数。所以无论add的strong还是weak的指针,NSArray总会增加它们所指向的堆上的内容的引用计数。

  1. 对VC进行包装

百度了一下发现 NSValue 貌似有个 valueWithNonretainedObject 方法,并且注释上看来正好是适合我们这种需求的类。

尝试了一下

1
2
3
4
5
NSValue *v = [NSValue valueWithNonretainedObject:vc];
[array addObject: v];
// ...vc释放之后
NSValue *v = [array firstObject];
UIViewController *vc = [v nonretainedObjectValue]; // crash

代码直接挂在了从NSValue中取这个vc的地方,看信息是对一个dealloc的对象调用了retain方法,所以猜测可能调用 nonretainedObjectValue 的时候方法本身会对所包含的对象强持有一下(可能是保证取出的该对象可用?),但如果这样的话NSValue这个方法意义就不清楚了,所以暂时这个方法也不行。

  1. 对vc进行另外一种包装

这种方法算是第一个想法的升级版,虽然用__weak不行,但是可以构建一个新的wrapper对象,然后内部有一个weak的property指向vc,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface Wrapper: NSObject
@property (nonatomic, weak) id weakRef;
@end
Wrapper *wrap = [Wrapper new];
wrap.weakRef = vc;
[array addObject:wrap];
// …
[array firstObject].weakRef // nil

可以满足要求,但缺点就是需要建立一个冗余的类型


第二个方向

百度了一下,发现iOS本身还是考虑到了这种需求的,内部有几个特殊的集合类型来满足复杂的内存管理需求。

NSPointerArray,NSMapTable,NSHashTable

只举例说明一下array类型,其余两种字典类型类似,可以参考文档使用

可以从字面上看到,NSPointerArray 内部保存的是对象的指针,并非直接保存OC对象。oc转指针就需要用到 bridge。同时 NSPointerArray 构建方法中通过传入NSPointerFunctionsOptions 来显示控制内存形式。

如果想达到要求的弱引用使用 NSPointerFunctionsWeakMemory 即可:

1
2
3
4
5
6
7
NSPointerArray *pa = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsWeakMemory];
[pa addPointer:(__bridge void*)vc];
// …
UIViewController *vc = [pa pointerAtIndex:0]; // vc is nil