2

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?

quan
  • 49
  • 3

3 Answers3

2

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.

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • Thanks, but I think of other ways, do you know the wrong of this? In the second answer – quan Jun 30 '22 at 09:08
  • @quan The method you suggest looks like it'll only work with Objective-C objects. That may be fine for your purposes, but it's an important limitation to consider when working in Swift. Also, it's often important not to just blindly copy every property. There are good reasons that you can't make a deep copy of many object graphs e.g. UIView-based view hierarchies. – Caleb Jun 30 '22 at 18:15
  • @quan Note that a `struct` in Swift offers much of what you want. A struct has a default initializer that takes values for every property, and copying a struct is as easy as assigning it to a variable. I don't know what your situation is or whether it makes sense to use a `struct` instead of a `class` that implements `NSCopyable`, but it's something to consider. – Caleb Jun 30 '22 at 18:24
1

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")
iUrii
  • 11,742
  • 1
  • 33
  • 48
0

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
    }
}
quan
  • 49
  • 3