Implementing copy() in Swift

前端 未结 10 1399
难免孤独
难免孤独 2020-11-27 15:31

I want to be able to copy a custom class in Swift. So far, so good. In Objective-C I just had to implement the NSCopying protocol, which means implementing

相关标签:
10条回答
  • 2020-11-27 16:25

    Copyable instances in swift

    NOTE: The great thing about this approach to copying Class instances is that it doesn't rely on NSObject or objc code, and most importantly it doesn't clutter up the "Data-Style-Class". Instead it extends the protocol that extends the "Data-Style-Class". This way you can compartmentalize better by having the copy code in another place than the data it self. The inheritance between classes is also taken care of as long as you model the protocols after the classes. Here is an example of this approach:

    protocol IA{var text:String {get set}}
    class A:IA{
        var text:String
        init(_ text:String){
            self.text = text
        }
    }
    extension IA{
        func copy() -> IA {
            return A(text)
        }
    }
    protocol IB:IA{var number:Int {get set}}
    class B:A,IB{
        var number:Int
        init(_ text:String, _ number:Int){
            self.number = number
            super.init(text)
        }
    }
    extension IB{
        func copy() -> IB {
            return B(text,number)
        }
    }
    let original = B("hello",42)
    var uniqueCopy = original.copy()
    uniqueCopy.number = 15
    Swift.print("uniqueCopy.number: " + "\(uniqueCopy.number)")//15
    Swift.print("original.number: " + "\(original.number)")//42
    

    NOTE: To see an implementation of this approach in real code: Then check out this Graphic Framework for OSX: (PERMALINK) https://github.com/eonist/Element/wiki/Progress2#graphic-framework-for-osx

    The different shapes uses the same style but each style uses a style.copy() call to create an unique instance. Then a new gradient is set on this copy rather than on the original reference like this:

    The code for the above example goes like this:

    /*Gradients*/
    let gradient = Gradient(Gradients.red(),[],GradientType.Linear,π/2)
    let lineGradient = Gradient(Gradients.teal(0.5),[],GradientType.Linear,π/2)
    /*Styles*/
    let fill:GradientFillStyle = GradientFillStyle(gradient);
    let lineStyle = LineStyle(20,NSColorParser.nsColor(Colors.green()).alpha(0.5),CGLineCap.Round)
    let line = GradientLineStyle(lineGradient,lineStyle)
    /*Rect*/
    let rect = RectGraphic(40,40,200,200,fill,line)
    addSubview(rect.graphic)
    rect.draw()
    /*Ellipse*/
    let ellipse = EllipseGraphic(300,40,200,200,fill.mix(Gradients.teal()),line.mix(Gradients.blue(0.5)))
    addSubview(ellipse.graphic)
    ellipse.draw()
    /*RoundRect*/
    let roundRect = RoundRectGraphic(40,300,200,200,Fillet(50),fill.mix(Gradients.orange()),line.mix(Gradients.yellow(0.5)))
    addSubview(roundRect.graphic)
    roundRect.draw()
    /*Line*/
    let lineGraphic = LineGraphic(CGPoint(300,300),CGPoint(500,500),line.mix(Gradients.deepPurple()))
    addSubview(lineGraphic.graphic)
    lineGraphic.draw()
    

    NOTE:
    The copy call is actually done in the mix() method. This is done so that code can be more compact and an instance is conveniently returned right away. PERMALINK for all the supporting classes for this example: https://github.com/eonist/swift-utils

    0 讨论(0)
  • 2020-11-27 16:27

    I was looking for a simpler solution. The concept was simple enough, duplicate the data by initialization however in my case the object chain was large and nested.

    Luckily my objects were already conforming to Codable so I simply used Encode and Decode to deep-copy the entire object.

    Simple Example:

    class MyObject: Codable, CustomStringConvertible {
        var name: String
        var description: String { name }
        
        init(name: String) {
            self.name = name
        }
    }
    
    let originalArr = [MyObject(name: "a"), MyObject(name: "b")]
    
    do {
        let data = try JSONEncoder().encode(originalArr)
        let copyArr = try JSONDecoder().decode([MyObject].self, from: data)
        
        //modify
        copyArr.forEach { obj in
            obj.name = "\(obj.name) modified"
        }
    
        print(originalArr, copyArr) //-> [a, b] [a modified, b modified]
    } catch {
        fatalError(error.localizedDescription)
    }
    

    Refactor (Advanced):

    To simplify future cases we can create a protocol and conform to it, allowing interesting ways to use it.

    protocol Copyable: Codable {
        func copy() -> Self
    }
    
    extension Copyable {
        func copy() -> Self {
            do {
                let encoded = try JSONEncoder().encode(self)
                let decoded = try JSONDecoder().decode(Self.self, from: encoded)
                return decoded
            } catch {
                fatalError(error.localizedDescription)
            }
        }
    }
    

    That's it. We can then conform our object to Copyable

    class MyObject: Copyable, CustomStringConvertible {
        var name: String
        var description: String { name }
        
        init(name: String) {
            self.name = name
        }
    }
    
    let a = MyObject(name: "A")
    let b = a.copy()
    b.name = "B"
    
    print(a.name, b.name) //-> "A B"
    

    We can quickly add support on Array as well:

    extension Array where Element: Copyable {
        func copy() -> [Element] {
            return self.map { $0.copy() }
        }
    }
    
    let originalArr = [MyObject(name: "a"), MyObject(name: "b")]
    let copyArr = originalArr.copy()
    copyArr.forEach { (obj) in
        obj.name = "\(obj.name) modified"
    }
    
    print(originalArr, copyArr) //-> [a, b] [a modified, b modified]
    
    0 讨论(0)
  • 2020-11-27 16:28

    Well, there is a really easy solution for this and you do not have to create root class.

    protocol Copyable {
        init(instance: Self)
    }
    
    extension Copyable {
        func copy() -> Self {
            return Self.init(instance: self)
        }
    }
    

    Now, if you want to make your custom class be able to copy, you have to conform it to Copyable protocol and provide init(instance: Self) implementation.

    class A: Copyable {
        var field = 0
    
        init() {
        }
    
        required init(instance: A) {
            self.field = instance.field
        }
    
    }
    

    Finally, you can use func copy() -> Self on any instance of A class to create a copy of it.

    let a = A()
    a.field = 1
    let b = a.copy()
    
    0 讨论(0)
  • 2020-11-27 16:36

    Only if you are using ObjectMapper library : do like this

    let groupOriginal = Group(name:"Abc",type:"Public")    
    let groupCopy = Mapper<Group>().mapAny(group.toJSON())! //where Group is Mapable
    
    0 讨论(0)
提交回复
热议问题