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
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).
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<Element>
{
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()
}
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)")
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
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()
There are two main complex data types in Swift – objects and structs – and they do so many things similarly that you'd be forgiven for not being sure exactly where they differ. Well, one of the key areas is down to copying: two variables can point at the same object so that changing one changes them both, whereas if you tried that with structs you'd find that Swift creates a full copy so that changing the copy does not affect the original.
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