3

I would like to implement a lazy property so that it will not have a value until it is accessed for the first time. But then later I want to set it to nil to free up memory resources. Then when the app tries to access it again, it will be recreated, therefore it shouldn't ever be nil when it's about to be accessed.

I looked through the Swift Programming book and read up on lazy properties, but the information there is sparse, there was no example of this use case.

I saw this answer by Rudolf Adamkovic and implemented that format, but the project won't compile in Xcode 6.2 beta 3: Use of undeclared type 'String'

let model = MyModelClass()
lazy var recentlyAdded: [​String] = self.recents() //error here

func recents() -> [String] {
    return self.model.recentlyAdded()
}

I also tried this format, but it too fails to compile with the same compile-time error.

lazy var recentlyAdded: [​String] = self.model.recentlyAdded()

What is the proper way to implement this type of lazy property?

4 Answers 4

2

Something like this?

struct S {
    var _actualVar: String? = nil
    var lazyVar: String? {
        mutating get {
            if _actualVar == nil {
                _actualVar = someCalc()
            }
            return _actualVar
        }
        set(newVar) {
            _actualVar = newVar
        }
    }
}
var s = S()
s.lazyVar  // someCalc will be called
s.lazyVar  // but not here
s.lazyVar = nil
s.lazyVar  // but it'll be called again here

Note, as with a lazy property, this does require that to use the get version of lazyVar, your s variable still has to be declared with var which can be a bit surprising.

Sign up to request clarification or add additional context in comments.

Comments

2

I've found a solution. You cannot use the lazy property in Swift to perform lazy instantiation like you would in Objective-C, at least not with Swift 1-3. If you attempt to, it won't be instantiated until it's accessed for the first time, but if you set it to nil later on, when you go to access it again it will still be nil. It will only be lazily instantiated a single time, which is not what I wanted.

To implement 'true' lazy instantiation you'll need a computed property as well as a regular property, both vars. I'll term the regular property the "backing" property. Essentially, you check in the computed property if the backing property is nil and create it if it is, then return that backing property. This is a computed property so it doesn't store a value in memory, while the backing property does. When you wish to get its value, access the computed property. When you wish to set its value to nil, set the backing property.

Apple uses this technique in their Core Data templates. They use the same property name for both, but place an underscore before the backing property, which mimics an iVar from Objective-C.

Something like this will do the trick:

var fetchedResultsController: NSFetchedResultsController {
    get {
        if _fetchedResultsController != nil {
            return _fetchedResultsController!
        }
        _fetchedResultsController = NSFetchedResultsController(fetchRequest: ...)
        return _fetchedResultsController!
    }
    set {
        _fetchedResultsController = newValue
    }
}
var _fetchedResultsController: NSFetchedResultsController? = nil

To use it:

_fetchResultsController = nil
self.fetchResultsController.performFetch()

Comments

1

I couldn't figure out the issue. But I solved it.

When I copied your code and pasted, I got the same error:

Use of undeclared type 'String'

I deleted that and re-typed it (Without auto-completion) and it worked for me.

Looks like a bug in XCode.

11 Comments

Interesting! Both of them worked for you? Did you run the app or just build it? I can't get either to work but I'll try it again.
@Joey: When I copy pasted, it is not working. When I re-typed the String class name it worked for me. Successfully build and run
@Joey: I implemented it like: class MyModelClass { func recentlyAdded() -> [String]{ return ["7"]; } } class ViewController: UIViewController { let model = MyModelClass() lazy var recentlyAdded: [String] = self.recents() //error here func recents() -> [String] { return self.model.recentlyAdded() }}
Ok, it's not complaining for me now on that line after retyping it, however when I try to set it to nil it says [String] doesn't conform to nil literal convertible. I changed it to [String]? and it runs but after setting it to nil the next time you access it it does not lazily instantiate again, it's nil.
@Joey: It only works for the first time. If you need to initialize it again, write your own custom property
|
-1

I'd just use the optional notation - ie a question mark to indicate it can be nil. For example

var myVar:String?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.