iOS-Swift 内存管理--优化自定义结构体Struct

1,498 阅读4分钟

写在前面

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特性。