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。

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

Cocoa 集合类型:NSPointerArray,NSMapTable,NSHashTable的更多相关文章

  1. Swift5 语言指南(七) 集合类型

    Swift提供三种主要的集合类型,称为数组,集合和字典,用于存储值集合.数组是有序的值集合.集是唯一值的无序集合.字典是键值关联的无序集合. Swift中的数组,集合和字典总是清楚它们可以存储的值和键 ...

  2. JAVA集合类型详解

    一.前言 作为java面试的常客[集合类型]是永恒的话题:在开发中,主要了解具体的使用,没有太多的去关注具体的理论说明,掌握那几种常用的集合类型貌似也就够使用了:导致这一些集合类型的理论有可能经常的忘 ...

  3. C#集合类型大盘点

    C#集体类型( Collections in C#) 集合是.NET FCL(Framework Class Library)中很重要的一部分,也是我们开发当中最常用到的功能之一,几乎是无处不在.俗话 ...

  4. Python学习笔记——集合类型

    集合类型有两种不同的类型——可变集合(set)和不可变集合(frozenset) 可变集合不是可哈希的,不能用作字典的键,也不能用做其他集合中的元素 不可变集合是有哈希值的,能被用做字典的键或者是作为 ...

  5. Spring中集合类型属性注入

    我们都知道如何去注入普通属性的值,非常简单,那么我们如何去注入开发中常见的集合类型的属性了,别急,往下看. 这里将介绍如何给Map list set Array Properties 这些属性注入值. ...

  6. Redis常用命令入门5:有序集合类型

    有序集合类型 上节我们一起学习了集合类型,感受到了redis的强大.现在我们接着学Redis的最后一个类型——有序集合类型. 有序集合类型,大家从名字上应该就可以知道,实际上就是在集合类型上加了个有序 ...

  7. Redis常用命令入门4:集合类型

    集合类型 之前我们已经介绍过了最基本的字符串类型.散列类型.列表类型,下面我们一起学习一下集合类型. 集合类型也是体现redis一个比较高价值的一个类型了.因为Redis的集合类型,所以我们可以很容易 ...

  8. Redis从基础命令到实战之有序集合类型(SortedSet)

    有序集合类型是Redis五种数据类型中最高级的.也是最复杂的类型.有序集合具有集合类型的特性,在其基础上给每个元素关联了一个分值,或称为权重,操作时既可以在添加元素时指定分值,也可以单独修改集合中某一 ...

  9. Redis从基础命令到实战之集合类型(Set)

    Redis集合类型的基础功能也是存储字符串列表,和列表类型的区别是字符串不能重复且没有顺序.当然,存储元素唯一性也可以通过应用程序保证,单从这一点上并没有体现出对比列表类型的特点. 其实,集合类型的一 ...

随机推荐

  1. 浅谈iPhone OS(iOS)架构

    iPhone OS(现在叫iOS)是iPhone, iPod touch 和 iPad 设备的操作系统

  2. python运行显示编码错误

    python中运行显示编码错误一般有2种原因: 编码与译码的方式不一致 在编写Python时,当使用中文输出或注释时运行脚本,会提示错误信息: SyntaxError: Non-ASCII chara ...

  3. python的httplib、urllib和urllib2的区别及用

    慢慢的把它们总结一下,总结就是最好的学习方法 宗述 首先来看一下他们的区别 urllib和urllib2 urllib 和urllib2都是接受URL请求的相关模块,但是urllib2可以接受一个Re ...

  4. iOS开发-VFL初窥

    VFL是苹果为了简化Autolayout的编码而推出的抽象语言,在上一篇博客中我们发现如果使用NSLayoutConstraint来添加约束是非常繁琐的. 一个简单的Frame需要添加四个NSLayo ...

  5. JavaWeb学习总结(十七)EL表达式

    语法格式: ${expression} 1. 表达式支持算术运算符合逻辑运算符 <%@ page language="java" contentType="text ...

  6. C++ template —— template metaprogram(九)

    metaprogramming含有“对一个程序进行编程”的意思.换句话说,编程系统将会执行我们所写的代码,来生成新的代码,而这些新代码才真正实现了我们所期望的功能.通常而言,metaprogrammi ...

  7. 通过java的i/o机制进行图片流的存储以及对网络图片的存储

    存储内地图片思路:首先把原有的图片以流的方式读取出来,再以流的方式存储到目标文件: package imgStream; import java.io.*; public class ImgStrea ...

  8. 基于thinkphp和ajax的省市区三级联动

    练习,就当练习. 省市区三级联动,样式如下图所示: 1,导入两个js文件并且导入数据库文件. 两个js文件分别是jquery-2.1.4.min.js和jquery-1.js,数据库文件,见附件. 2 ...

  9. tp3.2中怎么访问分类及子分类下面的文章

    在项目开发过程中,我们可能会遇到在进入文章分类时需要遍历文章分类及文章子分类下面的文章的情况,具体解决步骤如下: 一.为便于理解,这里列出用到的表及字段 文章分类表(article_cate) 文章表 ...

  10. [sql异常]SQL Server 阻止了对组件 'Ad Hoc Distributed Queries' 的

    //执行远程数据库表查询 SELECT * FROM OPENDATASOURCE('SQLOLEDB', 'Data Source=数据库地址;User ID=sa;password=sa' ).n ...