Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice to implement a failable initializer in Swift

With the following code I try to define a simple model class and it's failable initializer, which takes a (json-) dictionary as parameter. The initializer should return nil if the user name is not defined in the original json.

1. Why doesn't the code compile? The error message says:

All stored properties of a class instance must be initialized before returning nil from an initializer.

That doesn't make sense. Why should I initialize those properties when I plan to return nil?

2. Is my approach the right one or would there be other ideas or common patterns to achieve my goal?

class User: NSObject {      let userName: String     let isSuperUser: Bool = false     let someDetails: [String]?      init?(dictionary: NSDictionary) {         if let value: String = dictionary["user_name"] as? String {             userName = value         }         else {            return nil         }          if let value: Bool = dictionary["super_user"] as? Bool {             isSuperUser = value         }          someDetails = dictionary["some_details"] as? Array          super.init()     } } 
like image 378
Kai Huppmann Avatar asked Oct 21 '14 20:10

Kai Huppmann


People also ask

How do you designate a Failable initializer Swift?

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.

How do I create a Failable init?

Making a failable initializer takes two steps: Write your initializer as init?() rather than init() Return nil for any paths that should fail.

What must a convenience initializer call?

The convenience initializer must call one of the two designated initializers, because it can only call another initializer from the same class. This satisfies rules 2 and 3 from above. Both designated initializers must call the single designated initializer from the superclass, to satisfy rule 1 from above.

How many types of Initializers are there in Swift?

Swift defines two kinds of initializers for class types to help ensure all stored properties receive an initial value. Designated initializers are the primary initializers for a class.


2 Answers

That doesn't make sense. Why should I initialize those properties when I plan to return nil?

According to Chris Lattner this is a bug. Here is what he says:

This is an implementation limitation in the swift 1.1 compiler, documented in the release notes. The compiler is currently unable to destroy partially initialized classes in all cases, so it disallows formation of a situation where it would have to. We consider this a bug to be fixed in future releases, not a feature.

Source

EDIT:

So swift is now open source and according to this changelog it is fixed now in snapshots of swift 2.2

Designated class initializers declared as failable or throwing may now return nil or throw an error, respectively, before the object has been fully initialized.

like image 104
mustafa Avatar answered Sep 26 '22 19:09

mustafa


Update: From the Swift 2.2 Change Log (released March 21, 2016):

Designated class initializers declared as failable or throwing may now return nil or throw an error, respectively, before the object has been fully initialized.


For Swift 2.1 and earlier:

According to Apple's documentation (and your compiler error), a class must initialize all its stored properties before returning nil from a failable initializer:

For classes, however, a failable initializer can trigger an initialization failure only after all stored properties introduced by that class have been set to an initial value and any initializer delegation has taken place.

Note: It actually works fine for structures and enumerations, just not classes.

The suggested way to handle stored properties that can't be initialized before the initializer fails is to declare them as implicitly unwrapped optionals.

Example from the docs:

class Product {     let name: String!     init?(name: String) {         if name.isEmpty { return nil }         self.name = name     } } 

In the example above, the name property of the Product class is defined as having an implicitly unwrapped optional string type (String!). Because it is of an optional type, this means that the name property has a default value of nil before it is assigned a specific value during initialization. This default value of nil in turn means that all of the properties introduced by the Product class have a valid initial value. As a result, the failable initializer for Product can trigger an initialization failure at the start of the initializer if it is passed an empty string, before assigning a specific value to the name property within the initializer.

In your case, however, simply defining userName as a String! does not fix the compile error because you still need to worry about initializing the properties on your base class, NSObject. Luckily, with userName defined as a String!, you can actually call super.init() before you return nil which will init your NSObject base class and fix the compile error.

class User: NSObject {      let userName: String!     let isSuperUser: Bool = false     let someDetails: [String]?      init?(dictionary: NSDictionary) {         super.init()          if let value = dictionary["user_name"] as? String {             self.userName = value         }         else {             return nil         }          if let value: Bool = dictionary["super_user"] as? Bool {             self.isSuperUser = value         }          self.someDetails = dictionary["some_details"] as? Array     } } 
like image 27
Mike S Avatar answered Sep 22 '22 19:09

Mike S