写在前面
Swift的基本数据类型例如Array, Dictionary, Set 都是值类型的结构体,这点和OC是不一样的。因为篇幅有限,想了解更多的朋友请自行查阅相关资料。
值类型结构体在使用中出现的内存问题
通常情况下我们初始化了一个值类型的数据结构,例如struct A,A里面包含了大量数据比如:10000条数据(可以是自定义的数据类型);如果把这个初始化后的A当作参数传给一个对象,即等于暂时复制了一份A;如果多次高频率操作同类型的A就会造成内存短时间快速增加。而目前我们知道的Array, Dictionary, Set都是此类型的数据,此类数据结构也是如此吗?显然苹果给了解决方案...
苹果解决方式:写时复制(Copy-on-write)-COW
在Swift标准库里,只要是(Array, Dictionary, Set)等基本数据结构,值不被修改仅是作为传值,系统是不会对其进行复制的。例如:把一个有10000条数据的Array a 传递给另一个对象或方法,运行时系统不会对a再有复制操作。这显然是一个非常有用的特性。这时你肯定也发现了,根据前面所说苹果只是对自己的值类型数据结构加入了COW特性,那么我们自定义的值类型数据结构怎么办呢...
自定义值类型结构体加入COW特性:
- 1、自定义一个类对象,用于存储基本数据及操作对应数据,其中
public init() {} 是为了便于外部初始化 private init(_ items: [T]) 是为了内部实现拷贝自身的时候使用;
fileprivate class List<T> {
private var items = [T]()
public init() {} //公用初始化构造器
private init(_ items: [T]) { //私有初始化构造器
self.items = items
}
public func addItem(item: T) {
items.append(item)
}
public func getItem() -> T? {
if items.count > 0 {
return items.remove(at: 0)
} else {
return nil
}
}
public func count() -> Int {
return items.count
}
public func copy() -> List<T> { //复制自身
return List<T>(items)
}
}
- 2、Swift 有一个全局方法 isKnownUniquelyReferenced(),如果有一个该类型的相关实例,返回true,如果有多个相关实例,返回false。利用这个方法我们开始构造拥有COW特性的自定义Struct
struct ArryList {
private var internalQueue = List<Int>()
//判断 List 是否有且只有一个实例对象
mutating private func checkUniquelyReferencedInternalQueue() {
if !isKnownUniquelyReferenced(&internalQueue) {
print("Making a copy of internalQueue")
internalQueue = internalQueue.copy()
} else {
print("Not making a copy of internalQueue")
}
}
public mutating func addItem(item: Int) {
checkUniquelyReferencedInternalQueue()
internalQueue.addItem(item: item)
}
public mutating func getItem() -> Int? {
checkUniquelyReferencedInternalQueue();
return internalQueue.getItem()
}
public func count() -> Int {
return internalQueue.count()
}
mutating public func uniquelyReferenced() -> Bool{
return isKnownUniquelyReferenced(&internalQueue)
}
}
-
3、主要代码已经完成,现在我们通过实例进一步进行理解
1、(问题的产生)实例化一个List对象,对其自身操作及对其内部数据操作进行分析
fileprivate var queue1 = List<Int>() queue1.addItem(item: 1) queue1.addItem(item: 2) isKnownUniquelyReferenced(&queue1) //true 结果为true;结论,只在对象内部修改基本值类型数据不会造成对象复制
!特别注意:赋值拷贝的问题即将出现
fileprivate var queue2 = queue1 isKnownUniquelyReferenced(&queue1) //false 结果为false;结论,把初始化后的对象赋值给一个新的对象,造成了复制操作
2、(问题的解决)使用我们加入COW特性的Struct ArryList来解决这个问题
var queue3 = ArryList() queue3.addItem(item: 1) queue3.uniquelyReferenced() //true 结果为true;结论,只在对象内部修改基本值类型数据不会造成对象复制(和1的第一个其实是一样的)
!特别注意:加入COW特性的struct解决了赋值拷贝的问题
var queue4 = queue3 queue3.uniquelyReferenced() //false queue4.uniquelyReferenced() //false 结果为false;结论,把初始化后的对象赋值给一个新的对象,不会造成对象的复制
总结
很多情况下,我们使用struct就是看上了其值类型,不容易造成内存问题的特性,因此 在Swift中常常使用结构体封装一些属性、数据处理的方法模块来组成新的复杂类型,目的是简化运算。可是当我们项目中如果数据处理模块封装了类似struct,而且封装数据可能会很大的情况下,建议自己加入COW特性。