Implementing NSCopying in Swift with subclasses
The Short Answer
You cannot use self.dynamicType()
without marking init()
as required
because there's no guarantee subclasses of Vehicle
will also implement init()
.
Exploring The Problem
Taking a look at The Swift Programming Language: Initialization, it's mentioned how
subclasses do not inherit their superclass initializers by default
The situations in which a subclass will inherit its superclass' initialisers are:
Assuming that you provide default values for any new properties you
introduce in a subclass, the following two rules apply:Rule 1
If your subclass doesn’t define any designated initializers, it
automatically inherits all of its superclass designated initializers.Rule 2
If your subclass provides an implementation of all of its superclass
designated initializers—either by inheriting them as per rule 1, or by
providing a custom implementation as part of its definition—then it
automatically inherits all of the superclass convenience initialisers.
Take a look at the example:
class MySuperclass {
let num = 0
// MySuperclass is given `init()` as its default initialiser
// because I gave `num` a default value.
}
class MySubclass : MySuperclass {
let otherNum: Int
init(otherNum: Int) {
self.otherNum = otherNum
}
}
According to the information above, since MySubclass
defined the property otherNum
without an initial value, it doesn't automatically inherit init()
from MySuperclass
.
Now suppose I want to add the following method to MySuperclass
:
func myMethod() {
println(self.dynamicType().num)
}
You'll get the error you described because there is no guarantee subclasses of MySuperclass
will implement init()
(and in this example they don't).
To solve this problem you therefore need to mark init()
as required
, to ensure all subclasses of MySuperclass
implement init()
, and so calling self.dynamicType()
is a valid thing to do. It's the same problem as in your question: Swift knows Vehicle
implements init()
, however it doesn't know any subclasses will implement init()
and so you need to make it required
.
An alternative solution, which isn't suitable in your example, is to mark Vehicle
as final
, meaning Vehicle
can't be subclassed. Then you'll be able to use self.dynamicType()
; but you might as well just use Vehicle()
in that case.
Implementing NSCopying in Subclass of Subclass
One of those things you realize right after asking...
The implementation of copyWithZone:
in the superclass (Shape) shouldn't be assuming it's a Shape. So instead of the wrong way, as I mentioned above:
- (id)copyWithZone:(NSZone *)zone {
Shape *s = [[Shape allocWithZone:zone] init];
s.sides = self.sides;
return s;
}
You should instead use:
- (id)copyWithZone:(NSZone *)zone {
Shape *s = [[[self class] allocWithZone:zone] init]; // <-- NOTE CHANGE
s.sides = self.sides;
return s;
}
NSCopying copy(with:) - Does it really need to return Any?
This is the Objective-C API for the NSObject method copy
:
- (id)copy;
That translates into Swift as:
func copy() -> Any
Thus, no type information is attached to the new object; that's why you have to cast.
Now, if you don't like that, there's an easy solution in your situation: don't adopt NSCopying and don't use the built-in NSObject copy
method! Write your own one-off method that clones a Person — e.g. an instance method called makeClone()
. NSObject copy
is a convenience, but no law requires you to use it (unless there is some other reason why Person needs to conform to NSCopying, e.g. it is to be used as the key to an NSDictionary, but I'm betting you are never encountering any such reason).
Is this ok to do in a swift subclass copy()?
Your Method
override func copy() -> AnyObject {
let clone = super.copy() as SubClassType
return clone
}
My Answer
I'm not sure exactly what you want the method to do.
let clone = super.copy() as SubClassType
statically types the constant clone
to be of type SubClassType
. It doesn't make any changes to the object. The very next line of code
return clone
statically types the return value to be AnyObject
. Again, it doesn't make any changes to the object.
The code is identical to
override func copy() -> AnyObject {
return super.copy()
}
Which is the default behavior when you don't override a method.
In the end, you have 4 lines of code that are identical to 0 lines of code.
How to implement copy constructor in Swift subclass?
init(copyFrom: Square)
is an overload, not an override, of init(copyFrom: Shape)
. What I mean is that they are unrelated methods because they accept different types. In Swift that's acceptable. In ObjC, that's illegal. There are no overloads in ObjC.
Swift initializers don't automatically inherit. So in Swift, you couldn't try to copy a random Shape
as a Square
. The initializer isn't available. But in ObjC, initializers do automatically inherit (and you can't stop them from doing so). So if you have a method initWithCopyFrom:(*Shape)
, it is required that every subclass be willing to accept it. That means you could (in ObjC) try to create a copy of a Circle as a Square. That's of course nonsense.
If this is an NSObject
subclass, you should use NSCopying
. Here's how you would go about that:
import Foundation
class Shape : NSObject, NSCopying { // <== Note NSCopying
var color : String
required override init() { // <== Need "required" because we need to call dynamicType() below
color = "Red"
}
func copyWithZone(zone: NSZone) -> AnyObject { // <== NSCopying
// *** Construct "one of my current class". This is why init() is a required initializer
let theCopy = self.dynamicType()
theCopy.color = self.color
return theCopy
}
}
class Square : Shape {
var length : Double
required init() {
length = 10.0
super.init()
}
override func copyWithZone(zone: NSZone) -> AnyObject { // <== NSCopying
let theCopy = super.copyWithZone(zone) as Square // <== Need casting since it returns AnyObject
theCopy.length = self.length
return theCopy
}
}
let s = Square() // {{color "Red"} length 10.0}
let copy = s.copy() as Square // {{color "Red"} length 10.0} // <== copy() requires a cast
s.color = "Blue" // {{color "Blue"} length 10.0}
s // {{color "Blue"} length 10.0}
copy // {{color "Red"}
Swift 3
class Shape: NSObject, NSCopying {
required override init() {
super.init()
}
func copy(with zone: NSZone? = nil) -> Any {
let copy = type(of: self).init()
return copy
}
}
class Square: Shape {
required override init() {
super.init()
}
func copy(with zone: NSZone? = nil) -> Any {
let copy = super.copy(with: zone) as! Square
copy.foo = self.foo
......
return copy
}
}
Implementing NSCopying
To implement NSCopying, your object must respond to the -copyWithZone:
selector. Here’s how you declare that you conform to it:
@interface MyObject : NSObject <NSCopying> {
Then, in your object’s implementation (your .m
file):
- (id)copyWithZone:(NSZone *)zone
{
// Copying code here.
}
What should your code do? First, create a new instance of the object—you can call [[[self class] alloc] init]
to get an initialized obejct of the current class, which works well for subclassing. Then, for any instance variables that are a subclass of NSObject
that supports copying, you can call [thatObject copyWithZone:zone]
for the new object. For primitive types (int
, char
, BOOL
and friends) just set the variables to be equal. So, for your object Vendor, it’d look like this:
- (id)copyWithZone:(NSZone *)zone
{
id copy = [[[self class] alloc] init];
if (copy) {
// Copy NSObject subclasses
[copy setVendorID:[[self.vendorID copyWithZone:zone] autorelease]];
[copy setAvailableCars:[[self.availableCars copyWithZone:zone] autorelease]];
// Set primitives
[copy setAtAirport:self.atAirport];
}
return copy;
}
Right way to implement deep copy for object which contains nested objects in Swift?
Assuming your filter choice also conforms:
let copy = ZLFilters(filterChoices: filterChoices!.copyWithZone(zone))
Best practice when implementing copyWithZone:
You should always use
[[self class] allocWithZone:zone]
to make sure you are creating a copy using the appropriate class. The example you give for 002 shows exactly why: Subclasses will call[super copyWithZone:zone]
and expect to get back an instance of the appropriate class, not an instance of the super class.I access the ivars directly, so I don't need to worry about any side effects I might add to the property setter (e.g., generating notifications) later on. Keep in mind, subclasses are free to override any method. In your example, you are sending two extra messages per ivar. I would implement it as follows:
Code:
- (id)copyWithZone:(NSZone *)zone {
Crime *newCrime = [super copyWithZone:zone];
newCrime->_month = [_month copyWithZone:zone];
newCrime->_category = [_category copyWithZone:zone];
// etc...
return newCrime;
}
Of course, whether you copy the ivars, retain them, or just assign them should mirror what the setters do.
Related Topics
Format Realtime Stopwatch Timer to the Hundredth Using Swift
Why am I Allowed Method Access Less Restrictive Than Class Access
Swift 4.0 Mapview Running Slow
Function Throws and Returns Optional.. Possible to Conditionally Unwrap in One Line
How to Integrate Uiactivityviewcontroller with Swiftui's Scrollview
Swift Error: 'Missing Return in Function'
Transform a Swift Sequence in to Adjacent Pairs
I Won't Be Able to Return a Value with Alamofire in Swift
Single-Element Parethesized Expressions/Tuples VS Common Use of Parentheses
Expandable Sections Uitableview Indexpath Swift
Localizewithformat and Variadic Arguments in Swift
How to Add Kerning to a Textfield in Swiftui
How to Create an Array of Functions
How to Create Several Cached Uicolor
Call Completion Block When Two Other Completion Blocks Have Been Called
Make Int Round Off to Nearest Value