49

Let's say I have the following class in Swift (which has obvious problems)

class MyClass {
    let myProperty: String

    init() {
        super.init()
        self.setupMyProperty()
    }

    func setupMyProperty() {
        myProperty = "x"
    }
}

This is overly simplified but I'm basically trying to delegate the initialization of myProperty into the setupMyProperty() method. It's a pattern I use often to break down the different parts of the setup of a class.

But of course, I can't call self until the super initializer has run, and I can't run the super initializer until all the properties have been set, so I'm in a catch 22. On top of it since setupMyProperty() isn't considered an initializer, it won't be able to assign myProperty anyway.

Can anyone tell me how to implement this pattern in Swift?

mprivat
  • 21,582
  • 4
  • 54
  • 64
  • I don't know if this would be ok for you, but you can assign a value when declaring the property. `let myProperty: String = "x"` – Alessandro Vendruscolo Jun 09 '14 at 11:12
  • 1
    Yeah I use that where it makes sense. But in cases where these things are initialized based on params given to the initializer, the initializer becomes messy. I guess it's a sign my class has too many attributes. – mprivat Jun 09 '14 at 11:16
  • 2
    Check out WWDC Session 403: Intermediate Swift, about 29 min in covers class initialisation. – DogCoffee Jun 09 '14 at 11:34

6 Answers6

52

declare it as an implicitly unwrapped optional

class MyClass : NSObject {
    var myProperty: String!

    init() {
        super.init()
        self.setupMyProperty()
    }

    func setupMyProperty() {
        self.myProperty = "x"
    }
}

page 499 of "The Swift Programming Language" manual

LombaX
  • 17,265
  • 5
  • 52
  • 77
  • 4
    Don't you lose some safety by doing this? I guess I must make sure that the property is initialized before it gets accessed or else there will be a runtime error. And it can't be a constant for this instance. I guess this is the best way I've seen, just not perfect – mprivat Jun 09 '14 at 11:21
  • It will be accessed before the initialization only in the case that another thread tries to access it between the super.init() and the real "set"...I don't know if you can lock the variable until it's fully initialized (see http://stackoverflow.com/questions/24045895/what-is-the-swift-equivalent-to-objective-cs-synchronized , but I'm not documented enough to say if it's possible and if it could be a problem using it in an initializer). Bill's answer seems another good alternative. – LombaX Jun 09 '14 at 12:38
  • 2
    So there is no way to keep `myProperty` a constant (let)? – Van Du Tran Nov 10 '15 at 16:33
  • 1
    You can use a Closure or Function, look at this chapter: [Setting a Default Property Value with a Closure or Function](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID203) . It is not the same thing, but it seems a good alternative. Moving the initialization of a constant in a function like `setupMyProperty()` doesn't seem a good idea imho, since `setupMyProperty()` could be called more than once. Using a Closure initializer will avoid this – LombaX Nov 10 '15 at 17:01
  • Even though implicitly unwrapped optionals are part of Swift, it's best practice to stay away from them as much as possible, since a misplaced IUO will crash your app – Alex Mar 25 '16 at 17:37
  • 1
    @Andrei at the time of the answer this was the official solution in the manual – LombaX Mar 25 '16 at 22:04
  • @LombaX Sure, but the language and the patterns have evolved since, and this should no longer considered the best answer – Alex Mar 28 '16 at 18:05
  • 3
    Is there a new official solution from apple? What is the new, evolved one? Il you have updated information you can provide a link so I can update my answer. Or, if you prefer, add your own answer – LombaX Mar 28 '16 at 18:10
  • @Andrei Any plans to provide or point people to a better solution? – Relequestual Jul 05 '16 at 08:17
  • 1
    This looks fairly ugly to me, as the public interface of the class is changed due to an implementation convenience. I'd prefer to just initialize to a temporary value and then call the method to set it - at least this way the ugliness is self-contained next to the code of convenience that necessitated it. – Tim MB Jul 01 '19 at 20:19
  • 1
    If you decide to initialize other properties directly (without using a method) be sure to call `setupMyProperty()` _after_ the direct initializations. – Arie Aug 25 '22 at 12:28
20

Does setupMyProperty need to access self? If not, you can achieve this with a class method:

class MyClass: NSObject {
    let myProperty: String

    init() {
        myProperty = MyClass.setupMyProperty()
        super.init()
    }

    class func setupMyProperty() -> String {
        return "foo"
    }
}
Bill
  • 44,502
  • 24
  • 122
  • 213
6

You can't use self until your instance's memory is fully initialized (at which point you can't set constant properties anymore), so the order in which your designated initializer has to do things is:

  1. initialize all properties added by this class
  2. call superclass initializer (if there is a superclass)
  3. now you can use self, call methods, etc

I'm not sure if there's a good workaround for the case of a constant property. One alternative would be to declare the property as an implicitly unwrapped optional, so it's initially set to nil:

class MyClass {
    var myProperty: String!
    init() {
        super.init() // only if you actually have a superclass, and make sure this does not use `myProperty`
        self.setupMyProperty()
    }

    func setupMyProperty() {
        myProperty = "x"
    }
}

Be careful with this, it loses a bit of type safety, as myProperty can now be nil, and if it is when you try to access it, it will lead to a runtime error. So only do this if you're sure it'll be initialized by all your designated initializers, not be used by anything called before setupMyProperty() in the initializer chain (e.g. when a superclass initializer calls a method you override that accesses myProperty), and never set to nil explicitly.

Also, cf. the docs, especially the section on Class Inheritance and Initialization for the whole call-order stuff I explained above.

Lukas
  • 3,093
  • 1
  • 17
  • 9
  • 2
    `private(set) var myProperty: String` would prevent external nefarious changes to the value of your property – bshirley Feb 18 '16 at 18:10
3

Try putting setupMyProperty() in a constant. Then it is there before you initialise and you can call it from init(). You can even access parameters as follows:

class MyClass {
    var myProperty: String
    let setupMyProperty : (String) -> (void) = { 
        param in
        self.myProperty = param
    }

    init(x: String) {
    // removed redundant line: super.init()
        self.setupMyProperty(x)
    }
}
Tchelyzt
  • 161
  • 1
  • 10
2
class MyClass {

    let myProperty: String

    init() {
        super.init()
        self.myProperty = {
          // do your logic here 
          return "x" 
        }()
    }
}
pzaenger
  • 11,381
  • 3
  • 45
  • 46
-1

Surprised to see the top voted answer.. It risks a lot of safety using an implicitly unwrapped optional.

If you're looking reset your properties, but keep one source of truth, use a Constants struct

class MyClass {
    var myProperty = Constants.myProperty
    
    init() {
        reset()
    }
    
    func reset() {
        myProperty = Constants.myProperty
    }
    
    struct Constants {
        static let myProperty = "hello"
    }
}

If you want to hide complicated set up logic away from init, use a static func

class MyClass {
    var myProperty: String
    
    init() {
        myProperty = MyClass.setUpProperty()
    }
    
    static func setUpProperty() -> String {
        // Complicated set up logic goes here..
        return "hello"
    }
}

If you're looking to have different initializers for an object, use a Constants struct combined with initialization logic

class MyClass {
    var myProperty = Constants.myProperty1
    
    init() {
        if externalState {
            init1()
        } else {
            init2()
        }
    }
    
    func init1() {
        myProperty = Constants.myProperty1
    }
    
    func init2() {
        myProperty = Constants.myProperty2
    }
    
    struct Constants {
        static let myProperty1 = "hello"
        static let myProperty2 = "there"
    }
}
joshuakcockrell
  • 5,200
  • 2
  • 34
  • 47