6

Is there any way to use NSCopying without the returned object being of type Any? It always forces me to cast. This seems strange. I'm copying the object, shouldn't Swift know it's the same type by definition of the word copy?

Is there another way to copy objects that I don't know about, or is there some "gotcha" that I'm missing that requires this.

The class is very simple, like:

class Person {
   var name: String
   var age: Int
}

It must be a class because I need to have inheritance.

example:

var john = Person(name: "John", age: 30)
var johnsClone = john.copy() as! Person

I suppose I could create an initializer that takes an existing object, but this seems semantically less good than the word "copy".

init(person: Person) {
     return Person(name: person.name. age: person.age)
}

The only issue I have with implementing my own clone() method is that I would also like to have a protocol which can call clone() on many of such objects and implicitly return the same object type.

MH175
  • 2,234
  • 1
  • 19
  • 35

3 Answers3

7

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).

Community
  • 1
  • 1
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Thanks that was my idea as well. As I am newish to Swift I I didn't want to write a copy() method only to find out later that my class needs NSCopying and I get some kind of a method name conflict. – MH175 Apr 28 '17 at 15:41
  • 1
    @MH175 Do not call the method `copy`! – matt Apr 28 '17 at 15:46
  • I think I see an issue with this: If I need to make a protocol called Cloneable to operate on many of such objects. I will update my question. http://stackoverflow.com/questions/25645090/protocol-func-returning-self – MH175 Apr 28 '17 at 15:56
  • I don't see why that's an issue. You know how to declare the protocol, so what's the problem? – matt Apr 28 '17 at 16:31
  • OK. I think I got something working. I will post my solution below based off your explanation which led me to the stack overflow post I mentioned above. – MH175 Apr 28 '17 at 16:49
  • And as it turns out since NSCopying returns Any, and my custom implementation returns the type, the two have different method signatures, so in fact you can implement your own copy() method without collision. Will update. – MH175 Apr 28 '17 at 17:02
2

copy(with:) is the Swift imported version of the Objective C method, -copyWithZone. At the time this protocol was made Objective C didn't have a mechanism for expressing that copyWithZone should return the same type as the self it's called on. instancetype was introduced to be able to do just this, but NSCopying predates the existence of instancetype. Switching it from id to instancetype would have been a breaking change, so it was never done.

Alexander
  • 59,041
  • 12
  • 98
  • 151
  • 2
    Nitpicking, the question is about `[NSObject copy]` not `[NSCopying copyWithZone]`. And you are not correct about the reason for not using `instancetype`. `copy` is not required to return the same instance. A typical example is `[NSMutableArray copy]`. – Sulthan Apr 28 '17 at 07:51
  • @Sulthan You're not nitpicking. The answer is just wrong (except in spirit, i.e. "this is how it comes over from the Objective-C API"). – matt Apr 28 '17 at 15:28
  • 1
    @Sulthan `[NSObject copy]` returns the result of `[NSCopying copyWithZone:]`, as the [documentation clearly states](https://developer.apple.com/reference/objectivec/nsobject/1418807-copy). – Alexander Apr 28 '17 at 19:32
1

Based off the information I received above and this post Protocol func returning Self, this is a way to do it without using NSCopying.

Because the custom copy() returns the type of the object, while NSCopying's copy() returns Any, the two have different method signatures, so you can use your own copy() without ambiguity.

protocol Copyable {
    init(copy: Self)
}

extension Copyable {
    func copy() -> Self {
        return Self.init(copy: self)
    }
 }

class Person: Copyable{

    var name: String
    var age: Int

   init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    required init(copy: Person) {
        self.name = copy.name
        self.age = copy.age
    }
 }


 let john = Person(name: "John", age: 30)
 let johnsClone = john.copy()
Community
  • 1
  • 1
MH175
  • 2,234
  • 1
  • 19
  • 35
  • Great, but there was still never any need to call the method `copy`, and I think it's kind of silly to do so (even though, as you say, it causes no issues). – matt Apr 28 '17 at 17:09