How to conform to NSCopying and implement copyWithZone in Swift 2?
NSZone
is no longer used in Objective-C for a long time. And passed zone
argument is ignored. Quote from the allocWithZone...
docs:
This method exists for historical reasons; memory zones are no longer
used by Objective-C.
You're safe to ignore it as well.
Here's an example how to conform to NSCopying
protocol.
class GameModel: NSObject, NSCopying {
var someProperty: Int = 0
required override init() {
// This initializer must be required, because another
// initializer `init(_ model: GameModel)` is required
// too and we would like to instantiate `GameModel`
// with simple `GameModel()` as well.
}
required init(_ model: GameModel) {
// This initializer must be required unless `GameModel`
// class is `final`
someProperty = model.someProperty
}
func copyWithZone(zone: NSZone) -> AnyObject {
// This is the reason why `init(_ model: GameModel)`
// must be required, because `GameModel` is not `final`.
return self.dynamicType.init(self)
}
}
let model = GameModel()
model.someProperty = 10
let modelCopy = GameModel(model)
modelCopy.someProperty = 20
let anotherModelCopy = modelCopy.copy() as! GameModel
anotherModelCopy.someProperty = 30
print(model.someProperty) // 10
print(modelCopy.someProperty) // 20
print(anotherModelCopy.someProperty) // 30
P.S. This example is for Xcode Version 7.0 beta 5 (7A176x). Especially the dynamicType.init(self)
.
Edit for Swift 3
Below is the copyWithZone method implementation for Swift 3 as dynamicType
has been deprecated:
func copy(with zone: NSZone? = nil) -> Any
{
return type(of:self).init(self)
}
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.
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).
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;
}
Using NSCopy to Copy a Custom Object Containing Pointers?
UIView
does not conform to NSCopying
, but it does conform to NSCoding
:
another.imageView = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject:self.imageView]];
This serializes and then deserializes the object, which is the standard way to perform a deep copy in ObjC.
EDIT: See https://stackoverflow.com/a/13664732/97337 for an example of a common -clone
category method that uses this.
Type 'MyWeights' does not conform to protocol 'NSCopying'
It's telling you exactly what to do.
You need to declare that your class conforms to the NSCopying
protocol, and then you need to implement the only function in that protocol, copy(with:)
class MyWeights: NSObject, MPSCNNConvolutionDataSource, NSCopying {
func copy(with zone: NSZone? = nil) -> Any {
return MyWeights(
name: self.name,
kernelWidth: self.kernelWidth,
kernelHeight: self.kernelHeight,
inputFeatureChannels: self.inputFeatureChannels,
outputFeatureChannels: self.outputFeatureChannels,
useLeaky: self.useLeaky)
}
//The rest of your class
}
swift - NSCopying class
copyWithZone:
returns AnyObject
, so you have to cast the copy to the expected type:
var target1 = target.copy() as! TargetValue
Related Topics
Change Uibarbuttonitem from Uisearchbar
Distinction Between Private and Fileprivate Top-Level Classes
Multiple Bottom Sheets - the Content Doesn't Load Swiftui
Create Codable Struct with Generic Type
How to Validate Dynamically Added Textfields on a Button Click in Swiftui
How to Use Keywords as Parameter Names in Swift
Coreplot with Swift: There Is No Yaxis.Majorintervallength
How to Retrieve a Random Object from Firebase Using a Sequential Id
Swift Parsing Attribute Name for Given Elementname
Uiscrollview with Embedded Uiimageview; How to Get the Image to Fill the Screen
Missing Return in a Function Expected to Return 'Double'
Make Int Round Off to Nearest Value
Two Tables on One View in Swift