Reputation: 24902
I commonly use a "faulting"
or lazy initialization
pattern in iOS when dealing with big objects.
Whenever a class has a property pointing to a "fat" object, I create a custom getter that checks if the iVar
is nil
. If it is, it creates the fat object and returns it. If it isn't, it just returns the "fat" object.
The container of this property also subscribes to memory warnings, and when one is received, it sets the iVar
to nil
, reducing the memory footprint. As you see, it's pretty similar to faulting in Core Data.
I'm trying to reproduce this in Swift, but haven't found a decent and elegant solution so far.
a) First Attempt: lazy stored properties
This doesn't work, because if I set the property to nil, it will remain nil forever. The "magic" only happens the first time you access the property:
struct FatThing{
// I represent something big, which might have to be
// "faulted" (set to nil) when a memory warning
// is received
var bigThing = "I'm fat"
}
class Container {
lazy var fat: FatThing? = FatThing()
}
var c = Container()
c.fat
c.fat = nil
c.fat // returns nil
b) Second attempt: A stored property with observers
This also fails, because of the lack of get observers. I need a willGet
and didGet
, not just willSet
and didSet
.
Why on Earth are there no get obeservers? What's the use of this half backed thing called observers?
c) Third attempt: A computed property with a stored helper property
This is the only working option I've found so far, but it's as ugly as a baboon's rear end!
struct FatThing{
// I represent something big, which might have to be
// "faulted" (set to nil) when a memory warning
// is received
var bigThing = "I'm fat"
}
class Container {
private var _fat : FatThing? // having this extra and exposed var kills my inner child
var fat: FatThing? {
get{
if _fat == nil {
_fat = FatThing()
}
return _fat
}
set{
_fat = newValue
}
}
}
var c = Container()
c.fat
c.fat = nil
c.fat // returns FatThing
So much for making my code look simpler and shorter...
Is there a simple and elegant way to implement this? This is no exotic stuff in a memory deprived environment as iOS!
Upvotes: 2
Views: 701
Reputation: 72760
The ability to override the getter or setter individually is a peculiarity of objective C that has no counterpart in swift.
The right option at your disposal is no. 3, using a backing property to store the fat data and a computed property to make it accessible. I agree that there's some boilerplate code, but that's the tradeoff for having what you need.
However, if you use that pattern frequently, then you can create a protocol:
protocol Instantiable {
init()
}
and implement it in your FatThing
struct/class. Next, create a generic function containing the boilerplate code:
func lazyInitializer<T: Instantiable>(inout property: T?) -> T {
if property == nil {
property = T()
}
return property!
}
Note that T
must implement Instantiable
- that allows you to create an instance with a parameterless constructor.
Last, use it as follows:
private var _fat : FatThing? // having this extra and exposed var kills my inner child
var fat: FatThing? {
get { return lazyInitializer(&self._fat) }
set { _fat = newValue }
}
Note that in your code you don't have to declare the fat
computer property as optional - you are ensuring in the get
implementation that it is always not nil, so a better implementation is:
var fat: FatThing {
get{
if _fat == nil {
_fat = FatThing()
}
return _fat!
}
set{
_fat = newValue
}
}
Upvotes: 3