Implementing copy() in Swift

匿名 (未验证) 提交于 2019-12-03 02:49:01

问题:

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 copyWithZone.

As an example, I have a basic class called Value which stores a NSDecimalNumber.

func copyWithZone(zone: NSZone) -> AnyObject! {     return Value(value: value.copy() as NSDecimalNumber) } 

In Objective-C I, could easily just call copy to copy my object. In Swift, there seems to be no way to call copy. Do I really need to call copyWithZone even if no zone is needed? And which zone do I need to pass as a parameter?

回答1:

The copy method is defined in NSObject. If your custom class does not inherit from NSObject, copy won't be available.

You can define copy for any object in the following way:

class MyRootClass {     //create a copy if the object implements NSCopying, crash otherwise      func copy() -> Any {         guard let asCopying = ((self as AnyObject) as? NSCopying) else {             fatalError("This class doesn't implement NSCopying")         }          return asCopying.copy(with: nil)     } }  class A : MyRootClass {  }  class B : MyRootClass, NSCopying {      func copy(with zone: NSZone? = nil) -> Any {         return B()     } }   var b = B() var a = A()  b.copy()  //will create a copy a.copy()  //will fail 

I guess that copy isn't really a pure Swift way of copying objects. In Swift it is probably a more common way to create a copy constructor (an initializer that takes an object of the same type).



回答2:

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() 


回答3:

You can just write your own copy method

class MyRootClass {     var someVariable:Int     init() {         someVariable = 2     }     init(otherObject:MyRootClass) {         someVariable = otherObject.someVariable     }     func copy() -> MyRootClass {        return MyRootClass(self)     } } 

The benefit of this is when you are using subclasses around your project, you can call the 'copy' command and it will copy the subclass. If you just init a new one to copy, you will also have to rewrite that class for each object...

var object:Object .... //This code will only work for specific class var objectCopy = Object()  //vs  //This code will work regardless of whether you are using subClass or      superClass var objectCopy = object.copy() 


回答4:

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



回答5:

In my opinion, more Swifty way is to use associated type in Copyable protocol which allows define return type for method copy. Other ways don't allow to copy an object tree like this:

protocol Copyable {     associatedtype V     func copy() -> V     func setup(v: V) -> V }   class One: Copyable {     typealias T = One     var name: String?      func copy() -> V {         let instance = One()         return setup(instance)     }      func setup(v: V) -> V {         v.name = self.name         return v     } }  class Two: One {     var id: Int?     override func copy() -> Two {         let instance = Two()         return setup(instance)     }      func setup(v: Two) -> Two {         super.setup(v)         v.id = self.id         return v     } }  extension Array where Element: Copyable {     func clone() -> [Element.V] {         var copiedArray: [Element.V] = []         for element in self {             copiedArray.append(element.copy())         }         return copiedArray     } }  let array = [One(), Two()] let copied = array.clone() print("\(array)") print("\(copied)") 


回答6:

IMO, the simplest way to achieve this is :

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

Implemented in a struct as :

struct Struct : Copyable {   var value: String    init(value: String)   {     self.value = value   }    init(other: Struct)   {     value = other.value   } } 

And, in a class, as :

class Shape : Copyable {   var color: NSColor    init(color: NSColor)   {     self.color = color   }    required init(other: Shape)   {     color = other.color   } } 

And in subclasses of such a base class as :

class Circle : Shape {   var radius: Double = 0.0    init(color: NSColor, radius: Double)   {     super.init(color: color)      self.radius = radius   }    required init(other: Shape)   {     super.init(other: other)      if let other = other as? Circle     {       radius = other.radius     }   } }   class Square : Shape {   var side: Double = 0.0    init(color: NSColor, side: Double)   {     super.init(color: color)      self.side = side   }    required init(other: Shape)   {     super.init(other: other)      if let other = other as? Square     {       side = other.side     }   } } 

If you want to be able to copy an array of Copyable types :

extension Array where Element : Copyable {   func copy() -> Array   {     return self.map { $0.copy() }   } } 

Which then allows you to do simple code like :

{   let shapes = [Circle(color: .red, radius: 5.0), Square(color: .blue, side: 5.0)]    let copies = shapes.copy() } 


回答7:

Only if you are using ObjectMapper library : do like this

let groupOriginal = Group(name:"Abc",type:"Public")     let groupCopy = Mapper().mapAny(group.toJSON())! //where Group is Mapable 


回答8:

Having lots of objects point at the same data can be useful, but frequently you'll want to modify copies so that modifying one object doesn't have an effect on anything else. To make this work you need to do three things:

Make your class conform to NSCopying. This isn't strictly required, but it makes your intent clear. Implement the method copy(with:), where the actual copying happens. Call copy() on your object. Here's an example of a Person class that conforms fully to the NSCopying protocol:

class Person: NSObject, NSCopying {     var firstName: String     var lastName: String     var age: Int      init(firstName: String, lastName: String, age: Int) {         self.firstName = firstName         self.lastName = lastName         self.age = age     }      func copy(with zone: NSZone? = nil) -> Any {         let copy = Person(firstName: firstName, lastName: lastName, age: age)         return copy     } } 

Note that copy(with:) is implemented by creating a new Person object using the current person's information.

With that done, you can test out your copying like this:

let paul = Person(firstName: "Paul", lastName: "Hudson", age: 36) let sophie = paul.copy() as! Person  sophie.firstName = "Sophie" sophie.age = 6  print("\(paul.firstName) \(paul.lastName) is \(paul.age)") print("\(sophie.firstName) \(sophie.lastName) is \(sophie.age)") 

CheckThis



回答9:

Swift making copies of passed class instances

If you use the code in the accepted answer(the OP answered their own question) here, so long as your class is a subclass of NSObject and uses the Copying protocol in that post it will work as expected by calling the copyOfValues() function.

With this, no tedious setup or copy functions where you need to assign all the instance variables to the new instance.

I should know, I wrote that code and just tested it XD



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!