Home > front end >  swift How to implement the copy method more simply?
swift How to implement the copy method more simply?

Time:07-01

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?

CodePudding user response:

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.

CodePudding user response:

I think I've found a solution, but I'm not sure if this will have any ill effects. Can someone tell me what's wrong with this。Thanks!

  @objcMembers class TestA: NSObject, NSCopying {
    var a: CGFloat = 0
    var b: CGFloat = 0
    var c: CGFloat = 0
    
    required override init() {
        
    }
    
    func copy(with zone: NSZone? = nil) -> Any {
        let item = type(of: self).init()
        for property in getAllPropertys() {
            let value = self.value(forKey: property)
            item.setValue(value, forKey: property)
        }
        return item
    }

    func getAllPropertys()->[String]{
        var result = [String]()
        var count:UInt32 = 0
        let proList = class_copyPropertyList(object_getClass(self),&count)
        for i in 0..<numericCast(count) {
            let property = property_getName((proList?[i])!);
            let proper = String.init(cString: property)
            result.append(proper)
          }
          return result
    }
}

CodePudding user response:

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")
  • Related