0
votes

I'm looking for the best way to structure a class with a failable convenience initializer with optional parameters. Current code:

class Member: NSObject {

    var uid: String
    let avatarURL: NSURL
    let created: NSDate
    let email: String
    let name: String
    let provider: String

    var posts = [Post]()
    var comments = [Comment]()

    // Initialize a member with raw data
    init(uid: String, avatarURL: NSURL, created: NSDate, email: String, name: String, provider: String){
        self.uid = uid
        self.avatarURL = avatarURL
        self.created = created
        self.email = email
        self.name = name
        self.provider = provider
        super.init()
    }

    convenience init?(snapshot: FDataSnapshot){
        if let uid = snapshot.key {
            if let avatarURLString = snapshot.value["avatarURL"] as? String {
                if let avatarURL = NSURL(string: avatarURLString) {
                    if let memberCreated = snapshot.value["created"] as? NSDate {
                        if let memberEmail = snapshot.value["email"] as? String {
                            if let memberName = snapshot.value["name"] as? String {
                                if let memberProvider = snapshot.value["provider"] as? String {                                  
                                    self.init(uid: uid, avatarURL: avatarURL, created:memberCreated, email: memberEmail, name: memberName, provider: memberProvider)
                                }
                            }
                        }
                    }
                }
            }
        }
        return nil
    }
}

I need to either initialize a member manually (init) or by passing a Firebase object (convenience init). If any of the optionals fail within the convenience init, I'd like it to fail.

Currently, it won't build because:

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

Not sure what I'm missing.

Any opinions about a better way to approach this are welcome.

1
guard statement was really good improvement... - kelin

1 Answers

2
votes

This is a known issue in failable initializers that the Swift team is still working on improving. As you say, all the properties must be set to something before returning nil, so set them to something ("" or NSDate() in your case). It's a bit tedious, but currently necessary.

Just be clearer, since it can be a little tricky sometimes, here is one way:

convenience init?(snapshot: FDataSnapshot){
    if let uid = snapshot.key {
        if let avatarURLString = snapshot.value["avatarURL"] as? String {
            if let avatarURL = NSURL(string: avatarURLString) {
                if let memberCreated = snapshot.value["created"] as? NSDate {
                    if let memberEmail = snapshot.value["email"] as? String {
                        if let memberName = snapshot.value["name"] as? String {
                            if let memberProvider = snapshot.value["provider"] as? String {
                                self.init(uid: uid, avatarURL: avatarURL, created:memberCreated, email: memberEmail, name: memberName, provider: memberProvider)
                                return
                            }
                        }
                    }
                }
            }
        }
    }

    self.init(uid: "", avatarURL: NSURL(), created: NSDate(), email: "", name: "", provider: "")
    return nil
}

(In Swift 1.2, you will be able to stack all those let's together, which will make the code a bit clearer and let you use an else rather than a return in the middle.)

The key is that in a convenience initializer, you must eventually call self.init, even if you're going to call return nil later. (The Swift team knows this is annoying, but there are some corner cases that are difficult to deal with.)