• Cocoa 集合类型:NSPointerArray,NSMapTable,NSHashTable


    iOS 中有很多种集合类型,最为常见的可能就 NSArrayNSDictionaryNSSet,但其实还有 NSPointerArrayNSMapTableNSHashTable 等类型,虽然后面三个类型不常见,但是它们能在关键时刻,「救你一命」。

    环境信息
    macOS 10.12.1
    Xcode 8.2
    iOS 10.2

    先来看看传统的集合类型都有哪些短板:

    • 放到集合中的对象,只能强引用
    • 如果想要弱引用,要先用 NSValue 打包
    • 不能放入 nil

    而对于 NSPointerArrayNSMapTableNSHashTable 来说,除了以上共有的特点外,还各有各的「特长」,那就一起来看看吧。

    NSPointerArray

    对应着 NSArray,先来看看 API 中介绍的特点:

    • 和传统 Array 一样,用于有序的插入或移除;
    • 与传统 Array 不同的是,可以存储 NULL,并且 NULL 还参与 count 的计算;
    • 与传统 Array 不同的是,count 可以 set,如果直接 set count,那么会使用 NULL 占位;
    • 可以使用 weak 来修饰成员;
    • 成员可以是所有指针类型;
    • 遵循 NSFastEnumeration,可以通过 for...in 来进行遍历。

    初始化

    NSPointerArray 与 NSMutableArray 很像,都是可变有序集合。最大的不同就是它们的初始化方法,NSPointerArray 有两个初始化方法:

    - (instancetype)initWithOptions:(NSPointerFunctionsOptions)options;
    - (instancetype)initWithPointerFunctions:(NSPointerFunctions *)functions; 

    NSPointerFunctionsOptions

    首先来看一下 NSPointerFunctionsOptions,它是个 option,主要分为三大类:

    • 内存管理
      • NSPointerFunctionsStrongMemory:缺省值,在 CG 和 MRC 下强引用成员
      • NSPointerFunctionsZeroingWeakMemory:已废弃,在 GC 下,弱引用指针,防止悬挂指针
      • NSPointerFunctionsMallocMemory 与 NSPointerFunctionsMachVirtualMemory: 用于 Mach 的虚拟内存管理
      • NSPointerFunctionsWeakMemory:在 CG 或者 ARC 下,弱引用成员
    • 特性,用于标明对象判等方式
      • NSPointerFunctionsObjectPersonalityhashisEqual、对象描述
      • NSPointerFunctionsOpaquePersonalitypointer 的 hash 、直接判等
      • NSPointerFunctionsObjectPointerPersonalitypointer 的 hash、直接判等、对象描述
      • NSPointerFunctionsCStringPersonalitystring 的 hashstrcmp 函数、UTF-8 编码方式的描述
      • NSPointerFunctionsStructPersonality:内存 hashmemcmp 函数
      • NSPointerFunctionsIntegerPersonality:值的 hash
    • 内存标识
      • NSPointerFunctionsCopyIn:根据第二类的选择,来具体处理。
        如果是 NSPointerFunctionsObjectPersonality,则根据 NSCopying 来拷贝。

    所以在使用时,可以多个组合,比如:需要强引用成员、使用对象方式对比、并且 add 时 copy 对象:

    NSPointerFunctionsOptions *options = NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality | NSPointerFunctionsCopyIn;

    NSPointerFunctions

    自定义成员的处理方式,如:内存管理、hashisEqual 等,可以看到 API 中定义了一系列属性,它们都是函数指针,使用注释分段:

    // pointer personality functions
    ...
    // custom memory configuration
    

    可以自行实现函数,然后将函数指针赋给对应属性即可,比如,isEqual

    static BOOL IsEqual(const void *item1, const void *item2, NSUInteger (*size)(const void *item)) {
        return *(const int *)item1 == *(const int *)item2;
    }
    
    NSPointerFunctions *functions = [[NSPointerFunctions alloc] init];
    [functions setIsEqualFunction:IsEqual]; 

    compact

    之前谈到,NSPointerArray 可以存储 NULL,作为补充,它也提供了 compact 方法,用于剔除数组中为 NULL 的成员。但是 compact 函数有个已经报备的 bug

    NSPointerArray *array = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsWeakMemory];
    array.count = 10;
    [array addPointer:[NSObject new]];
    // compact 调用之后,NULL 并未被剔除
    [array compact];

    出现这种 bug 的原因,和 compact 函数的实现机制有关,当我们主动给 NSPointerArray 添加 NULL 时,数组会标记有空元素插入,此时 compact 函数才会生效,也就是说,compact 函数会先判断是否有标记,之后才会剔除。所以,当直接 set count,或者成员已经释放,自动置空的情况出现时,就会出现这个 bug。解决也很简答:

    // 在调用 compact 之前,手动添加一个 NULL,触发标记
    [array addPointer:NULL];
    [array compact];

    NSMapTable

    介绍完 NSPointerArrayNSMapTable 就方便多了,毕竟都是集合,很多东西都是相通的。

    与 NSMapTable 对应的,是 NSMutableDictionary。除了 集合的共有特点以外,比起传统字典,它还有一些优势:

    • key 可以不用遵循 NSCopying 协议;
    • key 和 value 的内存管理方式可以分开,如:key 是强引用,value 是弱引用;

    相比起 NSPointerArrayNSMapTable 的初始化方法要多得多:

    // 实例方法,虽然有 capacity 参数,但实际没用到
    - (instancetype)initWithKeyOptions:(NSPointerFunctionsOptions)keyOptions valueOptions:(NSPointerFunctionsOptions)valueOptions capacity:(NSUInteger)initialCapacity;
    - (instancetype)initWithKeyPointerFunctions:(NSPointerFunctions *)keyFunctions valuePointerFunctions:(NSPointerFunctions *)valueFunctions capacity:(NSUInteger)initialCapacity;
    
    // 便利构造器
    + (NSMapTable<KeyType, ObjectType> *)mapTableWithKeyOptions:(NSPointerFunctionsOptions)keyOptions valueOptions:(NSPointerFunctionsOptions)valueOptions;
    
    // 返回指定 key、value 内存管理类型的 map
    + (NSMapTable<KeyType, ObjectType> *)strongToStrongObjectsMapTable NS_AVAILABLE(10_8, 6_0);
    + (NSMapTable<KeyType, ObjectType> *)weakToStrongObjectsMapTable NS_AVAILABLE(10_8, 6_0);
    + (NSMapTable<KeyType, ObjectType> *)strongToWeakObjectsMapTable NS_AVAILABLE(10_8, 6_0);
    + (NSMapTable<KeyType, ObjectType> *)weakToWeakObjectsMapTable NS_AVAILABLE(10_8, 6_0);

    其实,这么多的初始化方法就对应着四种搭配:

    • key 为 strong,value 为 strong
    • key 为 strong,value 为 weak
    • key 为 weak,value 为 strong
    • key 为 weak,value 为 weak

    当用 weak 修饰 key 或 value 时,有一方被释放,则该键值对移除。

    NSHashTable

    NSHashTable 对应 NSMutableSet,它的 API 更为简单,与 NSMapTable 同样,初始化方法的 capacity 并未生效。

    - (instancetype)initWithOptions:(NSPointerFunctionsOptions)options capacity:(NSUInteger)initialCapacity;
    - (instancetype)initWithPointerFunctions:(NSPointerFunctions *)functions capacity:(NSUInteger)initialCapacity;

    值得注意的是,NSHashTable 有一个 allObjectes 的属性,返回 NSArray,即使 NSHashTable 是弱引用成员,allObjects 依然会对成员进行强引用。

    小结

    这类集合类型在 10.5 之后引入,比传统的集合类型更为强大,但是它们的方法却没有传统集合类型多,比如对于 NSPointerArray 来说:

    • 操作均基于 index,无法通过 object 来进行操作;
    • 无法直接插入 array,或用 array 初始化;
    • 查找功能没有 NSArray 强大;
    • 没有逆序、排序等 API 提供

    以上几点仅仅是举的例子,所以 NSPointerArray 也并没有看起来的那么强大,一切选择标准,都应该依据具体需求。

    在之前的一个项目中,要用到蓝牙。蓝牙的连接、发送、断开等操作,被我封装成了单例,但是很多地方都需要检测蓝牙当前状态,是否连接、是否发送成功等等,所以需要多个回调。首先考虑传值方式:代理、block、通知。通知我不太考虑,但是代理和 block 对于单例来说,都只能有一个值,一个对象设置以后,另一个就收不到。对于这种情况,就可以选择使用 NSHashTable。根据 NSHashTable 不持有对象,能添加空对象的特点,几乎完全不会影响成员的生命周期,也不会造成 crash。

    所以,还是要先熟悉各种集合的特性,然后去匹配需求,才是最好的。

  • 相关阅读:
    学习篇之String()
    js之Math对象
    js之date()对象
    css之描点定位方式
    js详解之作用域-实例
    js精要之构造函数
    js精要之继承
    js精要之模块模式
    js精要之对象属性
    js精要之函数
  • 原文地址:https://www.cnblogs.com/lxlx1798/p/6651949.html
Copyright © 2020-2023  润新知