可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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