Alexander Tsepkov
Alexander Tsepkov

Reputation: 4186

initializing class properties before use in Swift/iOS

I'm having trouble grasping the proper way of instantiating variables that always need to be set before an object is fully functional but may need to be instantiated after the constructor. Based on Swift's other conventions and restrictions it seems like there is a design pattern I'm unaware of.

Here is my use case:

My problem seems to be that both of the approaches in bullet 3 seem flawed.

In the first case, there is only one legitimate constructor this class can be called with, yet I'm forced to override other constructors and initialize member variables with fake values even if the other constructors are never intended to be used (I'm also trying to keep these variables as let types based on Swift's best practices).

In the second case, I'm effectively splitting my constructor into two parts and introduce an additional point of failure in case the second part fails to be called prior to class being used. I also can't move this second part to a method that's guaranteed to be called prior to usage (such as viewDidLoad) because I still need to pass in additional arguments from the config. While I can make sure to call the initPartTwo manually, I'd prefer to have a mechanism that better groups it with the actual constructor. I can't be the first one to run into this and it seems like there is a pattern I'm not seeing to make this cleaner.

UPDATE: I ended up going with a modified version of the pattern matt suggested:

struct Thing {
    let item1: String
    let item2: String
    struct Config {
        let item3: String
        let item4: String
    }

    var config:Config! {
        willSet {
            if self.config != nil {
                fatalError("tried to initialize config twice")
            }
        }
    }

    init() {
        self.item1 = ...
        self.item2 = ...
        ...
    }

    public func phaseTwoInit(item3: String, item4: String) {
        self.item3 = item3
        self.item4 = item4
        ...
    }
}

var t = Thing()
...
t.phaseTwoInit(...)
...
// start using t

Upvotes: 1

Views: 1170

Answers (1)

matt
matt

Reputation: 534977

If an initial instance variable property value can't be supplied at object initialization time, the usual thing is to declare it as an Optional. That way it doesn't need to be initialized by the class's initializers (it has a value - it is nil automatically), plus your code subsequently can distinguished uninitialized (nil) from initialized (not nil).

If the Optional if an implicitly unwrapped Optional, this arrangement need have no particular effect on your code (i.e. it won't have to be peppered with unwrappings).

If your objection is that you are forced to open the door to multiple settings of this instance variable because now it must be declared with var, then close the door with a setter observer:

struct Thing {
    var name:String! {
        willSet {
            if self.name != nil {
                fatalError("tried to set name twice")
            }
        }
    }
}
var t = Thing()
t.name = "Matt" // no problem
t.name = "Rumplestiltskin" // crash

Upvotes: 2

Related Questions