Implementación de copia () en Swift

Quiero poder copiar una clase personalizada en Swift. Hasta aquí todo bien. En Objective-C solo tuve que implementar el protocolo NSCopying , lo que significa implementar copyWithZone .

Como ejemplo, tengo una clase básica llamada Value que almacena un NSDecimalNumber .

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

En Objective-C I, podría simplemente llamar a copy para copiar mi objeto. En Swift, parece que no hay forma de llamar a copy . ¿Realmente necesito llamar a copyWithZone incluso si no se necesita una zona? ¿Y qué zona necesito pasar como parámetro?

El método de copy se define en NSObject . Si su clase personalizada no hereda de NSObject , la copy no estará disponible.

Puede definir la copy para cualquier objeto de la siguiente manera:

 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 

Supongo que esa copy no es realmente una forma pura de copiar objetos de Swift. En Swift es probablemente una forma más común de crear un constructor de copia (un inicializador que toma un objeto del mismo tipo).

Bueno, hay una solución realmente fácil para esto y no es necesario crear una clase de raíz.

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

Ahora, si desea que su clase personalizada pueda copiar, debe conformarlo con el protocolo Copyable y proporcionar la implementación de init(instance: Self) .

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

Finalmente, puede usar func copy() -> Self en cualquier instancia de la clase A para crear una copia de la misma.

 let a = A() a.field = 1 let b = a.copy() 

Puedes simplemente escribir tu propio método de copia

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

El beneficio de esto es cuando está usando subclases alrededor de su proyecto, puede llamar al comando ‘copiar’ y copiará la subclase. Si acaba de iniciar una nueva para copiar, también tendrá que volver a escribir esa clase para cada objeto …

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

Instancias Copyable en swift

NOTA: Lo mejor de este enfoque para copiar instancias de clase es que no se basa en código NSObject u objc, y lo más importante es que no satura la “clase de estilo de datos”. En su lugar, amplía el protocolo que amplía la “Clase de estilo de datos”. De esta forma puede compartimentar mejor teniendo el código de copiado en otro lugar que no sea el de los datos. La herencia entre clases también se cuida siempre que modele los protocolos después de las clases. Aquí hay un ejemplo de este enfoque:

 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 

NOTA: Para ver una implementación de este enfoque en código real: Luego, consulte este Marco Gráfico para OSX: (PERMALINK) https://github.com/eonist/Element/wiki/Progress2#graphic-framework-for-osx

Las diferentes formas usan el mismo estilo pero cada estilo usa una llamada style.copy () para crear una instancia única. Entonces se establece un nuevo gradiente en esta copia en lugar de en la referencia original como esta:

Ejemplo de StyleKit Graphic Framework

El código para el ejemplo anterior es el siguiente:

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

NOTA:
La llamada de copia se realiza realmente en el método de mezcla (). Esto se hace para que el código pueda ser más compacto y una instancia se devuelva convenientemente de inmediato. PERMALINK para todas las clases de soporte para este ejemplo: https://github.com/eonist/swift-utils

En mi opinión, una forma más rápida es usar el tipo asociado en el protocolo Copyable que permite definir el tipo de retorno para la copia del método. Otras formas no permiten copiar un árbol de objetos como este:

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

OMI, la manera más simple de lograr esto es:

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

Implementado en una estructura como:

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

Y, en una clase, como:

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

Y en subclases de una clase base como:

 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 } } } 

Si desea poder copiar una matriz de tipos Copyable:

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

Que luego le permite hacer un código simple como:

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

Solo si está utilizando la biblioteca ObjectMapper: haga esto

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

Hay dos tipos principales de datos complejos en Swift: objetos y estructuras, y hacen tantas cosas de manera similar que sería perdonado por no estar seguro exactamente donde difieren. Bueno, una de las áreas clave se reduce a la copia: dos variables pueden apuntar al mismo objeto para que cambiarlas las cambie a ambas, mientras que si lo intentas con estructuras, encontrarás que Swift crea una copia completa para que el cambio de la copia no afecta el original.

Tener muchos objetos apuntando a los mismos datos puede ser útil, pero con frecuencia querrá modificar las copias para que la modificación de un objeto no tenga ningún efecto sobre ninguna otra cosa. Para que esto funcione, debes hacer tres cosas:

Haga que su clase se ajuste a NSCopying. Esto no es estrictamente obligatorio, pero deja clara tu intención. Implemente la copia de método (con :), donde ocurre la copia real. Copia de llamada () en tu objeto. Aquí hay un ejemplo de una clase Person que se ajusta completamente al protocolo NSCopying:

 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 } } 

Tenga en cuenta que copy (with 🙂 se implementa creando un nuevo objeto Person utilizando la información de la persona actual.

Una vez hecho esto, puede probar su copia de esta manera:

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

Mira esto

Swift haciendo copias de instancias de clase aprobadas

Si usa el código en la respuesta aceptada (el OP contestó su propia pregunta), siempre que su clase sea una subclase de NSObject y use el protocolo de Copia en esa publicación, funcionará como se esperaba llamando a la función copyOfValues ​​().

Con esto, no hay tediosas funciones de configuración o copia en las que necesite asignar todas las variables de instancia a la nueva instancia.

Debería saber, escribí ese código y lo probé XD