19

I am rewriting an Objective-C class in Swift to get a feel for the language. In Objective-C my class included the following method:

- (id) initWithCoder:(NSCoder *)aDecoder
{
    return [self initWithActionName:[aDecoder decodeObjectOfClass:[NSString class] forKey:@"actionName"]
                            payload:[aDecoder decodeObjectOfClass:[NSDictionary class] forKey:@"payload"]
                          timestamp:[aDecoder decodeObjectOfClass:[NSDate class] forKey:@"timestamp"]];
}

After some trial and error with the compiler, I managed to rewrite it like this:

convenience init(aDecoder: NSCoder) {
    let actionName = aDecoder.decodeObjectOfClass(NSString.self, forKey: "actionName") as NSString
    let payload = aDecoder.decodeObjectOfClass(NSDictionary.self, forKey: "payload") as NSDictionary
    let timestamp = aDecoder.decodeObjectOfClass(NSDate.self, forKey: "timestamp") as NSDate
    self.init(actionName: actionName, payload: payload, timestamp: timestamp)
}

However, this still gives me an error on the last line: “Could not find an overload for init that accepts the supplied arguments.” The method signature of init is

init(actionName: String, payload: Dictionary<String, NSObject>, timestamp: NSDate)

so I assume the problem is that I am trying to pass an NSDictionary instead of a Dictionary<String, NSObject>. How can I convert between these two not-quite-equivalent types? Should I just be using NSDictionary for all of the code that has to interact with other Objective-C components?

bdesham
  • 15,430
  • 13
  • 79
  • 123

2 Answers2

14

Decode your dictionary as Dictionary<String, NSObject> instead of NSDictionary.

let payload = aDecoder.decodeObjectOfClass(NSDictionary.self, forKey: "payload") as! Dictionary<String, NSObject>

Since you are using Cocoa Touch to serialize and deserialize, they are serialized as NSDictionary, but you can cast it to a Swift Dictionary<,>, as per WWDC 2014 Intermediary Swift and Advanced Swift videos.

You can also decode as NSDictionary and then explicitly cast the object like so:

self.init(actionName: actionName, payload: payload as! Dictionary<String, NSObject>, timestamp: timestamp)

Basically, you are playing here with covariance/contravariance.

Léo Natan
  • 56,823
  • 9
  • 150
  • 195
1

Are you sure that that's the correct declaration for init? The argument order is different than the one you're calling in your Objective-C code.

So, if you want a precise reimplementation, the last call should probably be self.init(actionName: actionName, timestamp: timestamp, payload: payload). Calling the initializer with the wrong order of arguments would result in exactly the error message you're getting.

Lukas
  • 3,093
  • 1
  • 17
  • 9
  • Sorry for the confusion—since I was rewriting the class I decided to tweak the API at the same time. The order of the arguments to the Swift method is correct, so that’s not the problem. I’ve edited the question to change the order of the arguments in the ObjC version too so there’s no confusion. – bdesham Jun 07 '14 at 16:34