问题的产生
最近在做一个需求,其中有一个场景是一个对象从外部传入一个默认的 Array,对象根据一些其他的配置自动对 Array 进行一些处理,同时使默认的 Array 也作出增减操作。
因为在ObjC
中NSMutableArray
是引用类型,所以这就很简单了,不需要做什么特殊处理。但是在Swift
中Array
是值类型(struct),定义如下:
1 | public struct Array<Element> : RandomAccessCollection, MutableCollection { |
这时候如果使用 ObjC 的思路去做必定出错,因为 Struct 值类型的传递是复制值的操作,而不是传递地址。
解决方法
inout
很简单的第一时间想到的是函数的传递参数使用inout
标记(相信很多人也会这么做)就像这样:
1 |
|
测试一下:
这时候感觉已经完美解决了?
但是此时如果我们再另外一个函数中再去操作对象中的数组colors
,并不会引起原数组inoutColors
的改变。
输出如下图:
- inout 是在函数参数传递的时候复制了一份值传进来- 在函数内部做出改变的时候,原数据并不会改变- 在函数运行的时候会将函数内部改变之后的值复制出去将传入的参数原数据进行替换
inout 参数在函数调用的时候传递的是该值的地址, 但是在函数体内使用的时候, 使用的是对应的真实值, 无法获取传递的地址, 将其赋值给别的变量也是一份对应值类型的拷贝.
在上面的例子中的tio.colors
是改变之后的colors
的一份复制,直接改变tio.colors
并不会引起原数据inoutColors
的改变。
可以测试一下,如下图:
可以发现inout
的局限性,他只能在inout
的函数作用范围内改变传入的参数才可以改变原来的数据;
一旦超出inout
的函数的范围,你将无法改变传入的数据;
除非你直接更改原数据并且使用原数据,但是如果你写的是一个框架,并不知道原数据从哪里来到哪里去怎么办?
或许可以取一个折中的办法:在一个函数中对传入的Array
做完初始化处理,之后就只进行读(使用)而不再进行写。
但是如果如何彻底的解决这种问题呢?
UnsafeMutablePointer<Pointee>
如何像ObjC
中那样在Swift
中使用Array
?
首先重新描述下具体的原因:
- 主要的原因还是因为
Swift
中的值类型与引用类型在内存方式不同, 不能用ObjC
中对NSArray
的看法去看Swift
中的Array
ObjC
中的NSMutableArray
是对象,传递参数是传递对应 array 的地址(指针),对指针进行操作,会使所有的使用该内存的数据都发生改变Swift
中的Array
是值类型,传递的参数是一份拷贝(类似于ObjC
中的Copy
, 但是原理不同),传递后的内存空间与原来数据的内存空间并不是一块内存,之后的改变与原来的数据都没有任何关系。
那么我们可不可以在Swift
中传递一个指针过去呢?很多人可能觉得Swift
没有指针,毕竟都看不到*
了…
但是Swift
可以与C
的的 API 进行无缝混合,这时候想到了UnsafeMutablePointer<Pointee>
(Pointee
是泛型),定义如下:
1 | public struct UnsafeMutablePointer<Pointee> : Strideable, Hashable { |
又是struct
?不要怕,这个是存储Pointee对象的地址
的struct
,使用方法如下:
1 | var sourceColors: [UIColor] = [UIColor.red] |
测试一下:
使用引用符号获取对应的变量的地址传给 UnsafeMutablePointer<[UIColor]>类型的变量然后进行使用,pointee
有时候需要转换,&后面如果很长需要加括号,如:
UnsafeMutablePointer<SomeType>(&(someObject.someAttribute))
另外一些多线程操作也可以使用 UnsafeMutablePointer<SomeType>
这种方式会更加安全稳定。
具体的关于 swift 中指针的用法可以参考喵神的这篇文章:Swift 中的指针使用(喵神这篇文章应该是基于 swift 1,后续 swift 版本中有一些变化)