swift 中对象数组的深拷贝

deep copy for array of objects in swift

我有这个 class 名为 Meal

class Meal {
    var name : String = ""
    var cnt : Int = 0
    var price : String = ""
    var img : String = ""
    var id : String = ""

    init(name:String , cnt : Int, price : String, img : String, id : String) {
        self.name = name
        self.cnt = cnt
        self.price = price
        self.img = img
        self.id = id
    }
}

我有一系列餐食:

var ordered = [Meal]()

我想复制那个数组,然后对其中一个中的 Meal 实例进行一些更改,而不更改第二个中的 Meal 实例,我该如何制作它的深层副本?

此搜索结果对我没有帮助 How do I make a exact duplicate copy of an array?

因为 ordered 是一个 swift 数组,语句

 var orderedCopy = ordered

将有效地复制原始数组。

但是,由于 Meal 是 class,新数组将包含引用 与原始食物中提到的相同食物。

如果你也想复制膳食内容,这样改变一个数组中的膳食不会改变另一个数组中的膳食,那么你必须将 Meal 定义为 struct, 而不是 class:

struct Meal { 
  ...

来自Apple book

Use struct to create a structure. Structures support many of the same behaviors as classes, including methods and initializers. One of the most important differences between structures and classes is that structures are always copied when they are passed around in your code, but classes are passed by reference.

您要么必须像@MarioZannone 提到的那样将其设为结构,因为结构会自动复制,或者您可能不需要结构而需要 class。为此,您必须定义如何复制您的 class。 NSCopying 协议在 ObjC 世界统一了它,但这使得你的 Swift 代码 "unpure" 因为你必须从 NSObject 继承。但是我建议像这样定义你自己的复制协议:

protocol Copying {
    init(original: Self)
}

extension Copying {
    func copy() -> Self {
        return Self.init(original: self)
    }
}

你可以这样实现:

class Test : Copying {
    var x : Int

    init() {
        x = 0
    }

    // required initializer for the Copying protocol
    required init(original: Test) {
        x = original.x
    }
}

在初始化程序中,您必须将传递的 original Test 中的所有状态复制到 self。现在你已经正确地实现了协议,你可以这样做:

let original = Test()
let stillOriginal = original
let copyOriginal = original.copy()

original.x = 10

original.x         // 10
stillOriginal.x    // 10
copyOriginal.x     // 0

这与 NSCopying 基本相同,只是没有 ObjC

编辑:遗憾的是,这个如此美丽的协议在 subclassing...

上效果不佳

要改进@Kametrixom 的回答,请检查: 对于普通对象,可以做的是实现一个支持复制的协议,并使对象 class 像这样实现这个协议:

protocol Copying {
    init(original: Self)
}

extension Copying {
    func copy() -> Self {
        return Self.init(original: self)
    }
}

然后是用于克隆的数组扩展:

extension Array where Element: Copying {
    func clone() -> Array {
        var copiedArray = Array<Element>()
        for element in self {
            copiedArray.append(element.copy())
        }
        return copiedArray
    }
}

差不多就这些了,要查看代码和示例请检查此 gist

一种简单快捷的方法是将原始数组映射到新副本中:

let copyOfPersons: [Person] = allPersons.map({(originalPerson) -> Person in
        let newPerson = Person(name: originalPerson.name, age: originalPerson.age)
        return newPerson
    })

新的 Persons 将有不同的指针但相同的值。

基于之前的回答here

如果您有嵌套对象,即子classes 到 class 那么您想要的是真正的深度复制。

//Example
var dogsForAdoption: Array<Dog>

class Dog{
   var breed: String
   var owner: Person
}

所以这意味着在每个 class(狗、人等)中实施 NSCopying。

你会为 20 个 class 这样做吗? 30..50..100 怎么样?你没看错?我们需要原生的“它只是工作!”方法。但是不,我们没有。然而

截至目前,即 2021 年 2 月,此问题尚无妥善解决方案。不过我们有很多解决方法。

这是我一直在使用的一个,我认为限制较少的一个。

  1. 让你的class符合codable
class Dog: Codable{
    var breed : String = "JustAnyDog"
    var owner: Person
}
  1. 创建这个助手class
class DeepCopier {
    //Used to expose generic 
    static func Copy<T:Codable>(of object:T) -> T?{
       do{
           let json = try JSONEncoder().encode(object)
           return try JSONDecoder().decode(T.self, from: json)
       }
       catch let error{
           print(error)
           return nil
       }
    }
}
  1. 只要您需要对象的真正的深度复制,就调用此方法,如下所示:
 //Now suppose  
 let dog = Dog()
 guard let clonedDog = DeepCopier.Copy(of: dog) else{
    print("Could not detach Dog")
    return
 }
//Change/mutate object properties as you want
 clonedDog.breed = "rottweiler"
//Also clonedDog.owner != dog.owner, as both the owner : Person have dfferent memory allocations   

如您所见,我们借助 Swift 的 JSONEncoder 和 JSONDecoder,使用 Codable 的强大功能,无论我们的对象下有多少嵌套对象,都可以进行真正的深度复制。只需确保您所有的 类 都符合 Codable。

虽然它不是理想的解决方案,但它是最有效的解决方法之一。