How to Conform to Nscopying and Implement Copywithzone in Swift 2

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



Leave a reply



Submit