Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

swift How to implement the copy method more simply?

I have created multiple classes, all of which need to implement the NSCopying protocol, but there are a lot of properties in my classes, is there an easier way? Below is my current way:

    class TestA: NSObject, NSCopying {
    var a: CGFloat = 0
    var b: CGFloat = 0
    
    required override init() {
         
    }
    
    func copy(with zone: NSZone? = nil) -> Any {
        let item = type(of: self).init()
        item.a = a
        item.b = b
        return item
    }
}

class TestB: TestA {
    var c: CGFloat = 0
    var d: CGFloat = 0
    
    override func copy(with zone: NSZone? = nil) -> Any {
        let item = super.copy(with: zone) as! TestB
        item.c = c
        item.b = b
        return item
    }
}

My thought is, can we take all the properties of the class, automatically create a new object, assign values to the new object?

like image 790
quan Avatar asked Feb 27 '26 06:02

quan


2 Answers

Use the initializer.

class TestA: NSObject, NSCopying {
    var a: CGFloat = 0
    var b: CGFloat = 0

    required override init() {}
    
    convenience init(a: CGFloat, b: CGFloat) {
        self.init()
        self.a = a
        self.b = b 
    }
    
    func copy(with zone: NSZone? = nil) -> Any {
        let item = TestA(a: a, b: b)
        return item
    }
}

Doing it this way doesn't really save code since you still need an initializer that takes values for all properties, but you do get a simplified copy method and another initializer that might be useful in other situations too.

like image 78
Caleb Avatar answered Mar 01 '26 21:03

Caleb


You can look at KeyValueCoding package with KeyValueCoding protocol which implements enumeration of all properties of an object and setting values by key paths for pure swift classes and structs.

Based on it you can implement Copying protocol:

protocol Copying: KeyValueCoding {
    init()
}

extension Copying {
    func makeCopy() -> Self {
        var item = Self()
        var _self = self
        metadata.properties.forEach {
            item[$0.name] = _self[$0.name]
        }
        return item
    }
}

How it works:

class TestA: Copying {
    var a: CGFloat = 1
    var b: Int = 2
    
    required init() {}
}

class TestB: TestA {
    let c: String = "Hello Copy!"
    let d: Date = Date(timeIntervalSince1970: 123456789)
}

let objectA = TestA()
objectA.a = 100
objectA.b = 200
let copiedA = objectA.makeCopy()
print(copiedA.a) // "100.0"
print(copiedA.b) // "200"

let objectB = TestB()
objectB.a = 100
objectB.b = 200
let copiedB = objectB.makeCopy()
print(copiedB.a) // "100.0"
print(copiedB.b) // "200"
print(copiedB.c) // "Hello Copy!"
print(copiedB.d.timeIntervalSince1970) // "123456789.0"

So as you can see this approach works with inherited properties as well.

Moreover it works with structs:

struct MyStruct: Copying {
    let a = 1.0
    let b = 2
    let c = "c"
}

let myStruct = MyStruct()
let copied = myStruct.makeCopy()
print(copied) // MyStruct(a: 1.0, b: 2, c: "c")
like image 43
iUrii Avatar answered Mar 01 '26 20:03

iUrii



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!