Search code examples
iosswiftlazy-loadingcomputed-properties

How to re-initialize a lazy property from another view?


I have data that I want to read from disk into memory that takes a nontrivial amount of time. I want to be able to do two things:

  1. I don't the data to be read every time the view loads.
  2. I want to be able to invoke it from another view.
lazy var data: [String: String] = {
    guard let data = readFromDisk() else { return [:] }
    return processData(data: data)
}()

Above code gets initialized only once when the view loads for the first time, which is perfect for eliminating unnecessary computation. The problem is I also want to be able to trigger it from another view when needed.

I tried to trigger re-initialization:

func getData() {
    guard let data = readFromDisk() else { return [:] }
    data = processData(data: data)
}

and invoke it from another view:

let vc = ViewController()
vc.getData()

but, doesn't work. I tried to see if I could use static since it's also lazy, but I get an error saying:

Instance member cannot be used on type 'ViewController'

Finally, I tried creating a separate class:

class DataImporter {
    var data: [String: String] {
        guard let data = readFromDisk() else { return [:] }
        return processData(data: data)
    }
    
    func readFromDisk() -> [String: String] {}
    func processData(data: [String: String]) -> [String: String] {}
}

and have the lazy property in ViewController:

lazy var importer = DataImporter()

thinking that instantiating a class achieves the dual effect of taking advantage of a lazy property and invoking it when needed:

let vc = ViewController()
vc.importer = DataImporter()

This instantiates the class about a hundred times for some reason which is not ideal.


Solution

  • I would suggest creating a function that loads the data into data and then whenever you need to reload data, simply reassign it.

    class DataStore {
        lazy var data: [String: String] = loadData()
    
        func readFromDisk() -> Data? {...}
    
        func processData(data: Data) -> [String:String] { ... }
    
        func loadData() -> [String:String] {
            guard let data = readFromDisk() else { return [:] }
            return processData(data: data)
        }
    }
    
    let store = DataStore()
    let data = store.data // only loaded here
    store.data = store.loadData() // reloads the data
    

    If you don't want the loadData function to be exposed, you can also create a separate reloadData function.

    class DataStore {
        ...
        func reloadData() {
            data = loadData()
        }
    }
    

    and then instead of doing store.data = store.loadData(), simply call store.reloadData()