Reputation: 5617
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
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