对NSValue的探索
开发过程中我有把结构体
或者基本数据类型
加入数组
或字典
的需求,比如CGSize
,CGRect
,CGPoint
等数据,但是大家都知道OC的容器中只能加入对象类型的数据。
一般有两个解决办法,第一种办法是转成 NSString
,用时再从NSString
转成需要的数据类型,但是这种方式有两个缺点:
- 不支持自定义类型的结构体
- 性能太差!不,非常差!!
综上,如果没有必要我还是比较倾向于用NSValue,除此以外,使用NSValue还可以满足一些奇葩的需求,比如想加入容器中,但是不增加引用计数。
说起来NSValue,大家用的可能不多,但是NSNumber肯定很常用,其实NSNumber是NSValue的子类,在这就不详细介绍NSNumber了,下面说一下NSValue.
NSValue的API:
基本方法
- initWithBytes:objCType:
以指定的类型初始化一个包装了传入数据的NSValue对象
objCType
获得当前NSValue对象包装的数据类型
基本用法:
1 | // 以CGRect 举例 |
现在系统支持的结构体类型有以下几种:
支持类型 | 便捷设置方法 | 取值方法 |
---|---|---|
CGPoint | + valueWithCGPoint: | CGPointValue |
CGVectorValue | + valueWithCGVector: | CGVectorValue |
CGSize | + valueWithCGSize: | CGSizeValue |
CGRect | + valueWithCGRect: | CGRectValue |
CGAffineTransform | + valueWithCGAffineTransform: | CGAffineTransformValue |
UIEdgeInsets | + valueWithUIEdgeInsets: | UIEdgeInsetsValue |
NSDirectionalEdgeInsets | + valueWithDirectionalEdgeInsets: | directionalEdgeInsetsValue |
UIOffset | + valueWithUIOffset: | UIOffsetValue |
如果是自定义的结构体类型怎么办?
其实苹果的官方开发文档已经提出了解决办法:创建一个NSValue的分类
例如下面仿照苹果的开发文档写一个直线的结构体分类
1 | ---- .h文件中 ------------- |
初始化方法
+ valueWithBytes:objCType:
作用与基本方法中的初始化方法一致
+ value:withObjCType:
作用与基本方法中的初始化方法一致
扩展方法
+ valueWithNonretainedObject:
创建一个包装了传入对象的NSValue对象,并且不增加引用计数,如果你想把一个对象加入到容器(NSArray,NSDictary,NSSet等)中,并不想使其引用计数增加,可以使用此方法。
1 | self.m_array = @[].mutableCopy; |
可以看到在使用+ valueWithNonretainedObject:
包装前object1添加到数组中后引用计数增加了1,包装后,object2引用计数没有增加。但是需要注意的一点是:如果被包装的对象被释放了,从NSValue对象中取出的值并不是nil,具体是什么我并没有深究,有了解的可以告知我一下。
+ valueWithPointer:
创建一个包装了指针的NSValue对象
这个方法是一个很强大的方法,你可以用NSValue包装一个指针,比如一个结构体的指针,一个方法的指针
结构体的指针包装
1 | // 假如有一个这样的结构体,需要包装成对象,但是不想写NSValue的分类 |
// 方法指针的包装(@selector())
1 | NSValue *selValue = [NSValue valueWithPointer:@selector(p_selecterTest)]; |
针对NSValue和NSString的类型转换性能测试
原始数据:
- 测试环境:Xcode 9.2 + iPhone 6sPlus(真机) + iOS 11.2.5
- 测试方式:将不同的数据类型使用各自的方式转成对象,再从相应对象转成原始数据,各循环1000000(一百万)次,每项测试三次
- 测试指标:运行开始的时间戳和运行结束后的时间戳
- 其中NSInteger测试项是使用NSNumber类进行转换的,NSNumber 是NSValue的子类
比较项目 | NSValue_1 | NSValue_2 | NSValue_3 | NSString_1 | NSString_2 | NSString_3 |
---|---|---|---|---|---|---|
CGRect | 1518600937.330795 1518600937.963562 |
1518601232.691197 1518601233.306452 |
1518601284.956521 1518601285.562490 |
1518601339.185438 1518601348.121756 |
1518601371.606468 1518601380.774911 |
1518601403.208475 1518601412.353251 |
CGPoint | 1518601895.491483 1518601895.959701 |
1518601921.690009 1518601922.152504 |
1518601946.091435 1518601946.550951 |
1518601742.319816 1518601745.790220 |
1518601806.652705 1518601810.159964 |
1518601830.311894 1518601833.820591 |
NSInteger | 1518602407.599263 1518602407.659061 |
1518602429.995714 1518602430.066774 |
1518602457.383714 1518602457.455960 |
1518602514.271342 1518602515.732950 |
1518602538.392223 1518602539.877542 |
1518602561.810762 1518602563.315673 |
处理结果
- 处理方式:时间戳相减,获得运行消耗的准确时间,然后取三次的平均值
- 处理结果:每项测试运行的时间,单位是秒,保留六位小数
比较项目(秒) | NSValue | NSString |
---|---|---|
CGRect | 0.617997 | 9.083179 |
CGPoint | 0.463410 | 3.495453 |
NSInteger | 0.067701 | 1.483946 |
结论:结构体和NSValue互转时性能比NSString更好