zeeple
zeeple

Reputation: 5617

How to return early from an init in swift 3

I have an app in which I would like to create an object based on whether there is already an equivalent object saved to user defaults. I would like to detect this in the class init and return early, if an object is detected. This is what I am trying to do:

init() {

    /* There are two possibilities when creating a hero:
     1. The hero is brand new and needs to be built from scratch
     2. The hero is loaded from defaults  */

    // Check to see if there is existing game data:

    if defaultExistsForGameData() {
        // This means there is a hero to load and no need to create a new one
        self = extractHeroFromDefaults() // This just loads from UserDefaults
        print("Loading hero from defaults with name of: \(hero.heroName).")

        return self
    }

    // These actions are for creating a brand new hero
    let size = CGSize(width: 32, height: 32)
    let heroTexture = SKTexture(imageNamed: "hero2.ico")
    super.init(texture: heroTexture, color: .clear, size: size)

    self.isUserInteractionEnabled = true
    self.name = "hero"

    self.zPosition = 50

}

There are a couple errors in the console, self is immutable, etc. What I want to know if this is a valid pattern or if I should be taking an entirely different approach.

Upvotes: 2

Views: 690

Answers (1)

Rob Napier
Rob Napier

Reputation: 299455

In Swift (unlike ObjC), init can't return a different object than itself. A common way you achieve what you're trying to do here is with a class factory method (optionally making init private if you don't want other objects to be able to call it directly).

For example, something along these lines:

class func loadOrCreate() -> Hero {
    if defaultExistsForGameData() {
        // This means there is a hero to load and no need to create a new one
        print("Loading hero from defaults with name of: \(hero.heroName).")
        return extractHeroFromDefaults() // This just loads from UserDefaults
    } else {
        return Hero()
    }
}

private init() {
    let size = CGSize(width: 32, height: 32)
    let heroTexture = SKTexture(imageNamed: "hero2.ico")
    super.init(texture: heroTexture, color: .clear, size: size)

    self.isUserInteractionEnabled = true
    self.name = "hero"

    self.zPosition = 50
}

Another approach that is closer to your current API is to create a separate (possibly private) designated initializer like this:

private init(name: String, zPosition: Int) {
    let size = CGSize(width: 32, height: 32)
    let heroTexture = SKTexture(imageNamed: "hero2.ico")
    super.init(texture: heroTexture, color: .clear, size: size)

    self.isUserInteractionEnabled = true
    self.name = name
    self.zPosition = zPosition
}

public convenience init() {
    let name: String
    let zPosition: Int
    if defaultExistsForGameData() {
        name = defaultName() // Read it out of user defaults
        zPosition = defaultZPosition
    } else {
        name = "hero"
        zPosition = 50
    }
    self.init(name: name, zPosition: zPosition)
}

The one problem with this approach is that it can be a little surprising. It's not precisely clear what's supposed to happen if you create multiple Hero objects. something like loadOrCreate() makes it very clear that there are external influences.

Upvotes: 3

Related Questions