An object of mine has an integer ID. Since this is a required property I am not defining it as an optional and I am requiring it in the designated initializer:
class Thing {
var uniqueID: Int
var name: String?
init (uniqueID: Int) {
self.uniqueID = uniqueID
}
}
Since I am creating one of these from some JSON, the usage is along the lines of:
if let uniqueID = dictionary["id"] as? Int {
let thing = Thing(uniqueID: unique)
}
Next, I would like to be able to add a convenience initializer to the Thing class that accepts the dictionary object and sets the properties accordingly. This includes the required uniqueID
and some other optional properties. My best effort so far is:
convenience init (dictionary: [String: AnyObject]) {
if let uniqueID = dictionary["id"] as? Int {
self.init(uniqueID: uniqueID)
//set other values here?
}
//or here?
}
But of course this isn't sufficient since the designated initializer isn't called on all paths of the conditional.
How should I be handling this scenario? Is it even possible? Or should I accept that uniqueID
must be an optional?
A convenience initializer is a secondary initializer that must call a designated initializer of the same class. It is useful when you want to provide default values or other custom setup. A class does not require convenience initializers.
Convenience initializers are secondary, supporting initializers for a class. You can define a convenience initializer to call a designated initializer from the same class as the convenience initializer with some of the designated initializer's parameters set to default values.
You write a failable initializer by placing a question mark after the init keyword ( init? ). Note: You cannot define a failable and a nonfailable initializer with the same parameter types and names. A failable initializer creates an optional value of the type it initializes.
Example: Swift Initializer Inside the initializer, we have initialized the value of the length property. Here, when the wall1 object is created, the init() initializer is called.
You have a couple of options with this one. One is a failable initialisers:
convenience init?(dictionary: [String: AnyObject]) {
if let uniqueID = dictionary["id"] as? Int {
self.init(uniqueID: uniqueID)
} else {
self.init(uniqueID: -1)
return nil
}
}
Technically this can be tweaked a bit (mainly depending on your preference/version of swift), but my person preference is something as follows:
class func fromDictionary(dictionary: [String: AnyObject]) -> Thing? {
if let uniqueID = dictionary["id"] as? Int {
return self.init(uniqueID: uniqueID)
}
return nil
}
All together, as a playground:
class Thing {
var uniqueID: Int
var name: String?
init(uniqueID: Int) {
self.uniqueID = uniqueID
}
convenience init?(dictionary: [String: AnyObject]) {
if let uniqueID = dictionary["id"] as? Int {
self.init(uniqueID: uniqueID)
} else {
self.init(uniqueID: -1)
return nil
}
}
class func fromDictionary(dictionary: [String: AnyObject]) -> Thing? {
if let uniqueID = dictionary["id"] as? Int {
return self.init(uniqueID: uniqueID)
}
return nil
}
}
let firstThing = Thing(uniqueID: 1)
let secondThing = Thing(dictionary: ["id": 2])
let thirdThing = Thing(dictionary: ["not_id": 3])
let forthThing = Thing.fromDictionary(["id": 4])
let fithThing = Thing.fromDictionary(["not_id": 4])
The best solution is probably to use a failable initializer, which will either return an instantiated object or nil
.
Because Swift objects cannot be partially constructed and convenience initializers must call a non-convenience initializer, we must still do something in the failure case.
The result will look something like this:
convenience init?(dictionary: [String: AnyObject]) {
if let uniqueID = dictionary["id"] as? Int {
self.init(uniqueID: uniqueID)
} else {
self.init(uniqueID: 0)
return nil
}
}
Generally speaking, our non-convenience initializer(s) should be one that accepts all arguments, and convenience initializers should be methods which don't require some of the arguments.
For example, I might make my default initializer look like this:
init(uniqueID: Int, name: String? = nil) {
self.uniqueID = uniqueID
self.name = name
}
This allows us to call the constructor in several different ways:
let thing1 = Thing(1)
let thing2 = Thing(2, nil)
let thing3 = Thing(3, "foo")
let thing4 = Thing(4, myUnwrappedStringVar)
let thing5 = Thing(5, myWrappedStringOptional)
And that already covers a lot of use cases for us.
So, let's add another convenience initializer that accepts an optional Int
.
convenience init?(uniqueID: Int? = nil, name: String? = nil) {
if let id = uniqueID {
self.init(uniqueID: id, name: name)
} else {
self.init(uniqueID: 0)
return nil
}
}
Now we can take an Int?
for our uniqueID
argument and just fail when it's nil
.
So, one more to accept the dictionary.
convenience init?(dictionary: [String: AnyObject]) {
let uniqueID = dictionary["id"] as? Int
let name = dictionary["name"] as? String
self.init(uniqueID: uniqueID, name: name)
}
We still have the slightly weird initialize then return nil
pattern in our first convenience constructor, but everything else we build on top of this can simply call that convenience initializer and doesn't require the weird pattern.
In the initializer that takes the dictionary, if there's no id
key, or if it's something that's not an Int
, then the let uniqueID
will be nil
, so when we call the other constructor, it will call the one that accepts an Int?
, be passed nil
, return nil
, and therefore the one we called will return nil
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With