How to use lazy variables in Swift

Sep 27, 2023#swift

A lazy variable in Swift is a variable whose initial value is not calculated until the first time it is accessed. This can be useful when the initial value of a variable is expensive to compute, or when it is not needed until it is actually used in the code.

For example, you can use a lazy variable to store the result of a complex calculation that you only need to perform once, or to create an object that depends on some external factors that are not available at the time of initialization.

To declare a lazy variable, you need to use the lazy keyword before the var keyword, and provide an initial value for the variable. The initial value can be either a literal value, or a closure that returns a value.

class FileManager {
  // This is a lazy property that will be initialized when first accessed
  lazy var fileContents: String = {
    print("Initializing fileContents property")
    // Simulate some expensive initialization
    return "This is the contents of the file"
  }()

  // This is a regular property
  var filePath: String

  init(filePath: String) {
    self.filePath = filePath
  }

  func readFileContents() {
    print("Reading file contents...")
    let contents = self.fileContents  // Accessing the lazy property
    print("File contents: \(contents)")
  }
}

let fileManager = FileManager(filePath: "/path/to/somefile.txt")

// At this point, the fileContents property has not been initialized yet

fileManager.readFileContents()
// Reading file contents...
// Initializing fileContents property
// File contents: This is the contents of the file

// Now, the fileContents property has been initialized and its value is cached for future accesses

Note that you need to add parentheses () at the end of the closure to indicate that you want to execute it and assign its return value to the variable. Otherwise, you would be assigning the closure itself to the variable, which is not what you want.

There are some important things to remember when using lazy variables:

  • Lazy variables must always be declared as var, not let, because their initial value is not known at compile time and may change later.
  • Lazy variables are not thread-safe, which means that if you access them from multiple threads simultaneously, you might get unexpected results or errors. You should only use lazy variables when you are sure that they will be accessed from a single thread, or when you don’t care about thread safety.
  • Lazy variables are only initialized once, and their value is cached for future use. This means that if you change the value of a lazy variable after it has been initialized, the change will not affect its initial value. For example:
struct Person {
  var name: String
  lazy var greeting = "Hello, \(name)!"

  init(name: String) {
    self.name = name
  }
}

var person = Person(name: "Alice")
print(person.greeting)
// Prints "Hello, Alice!"

person.name = "Bob"
print(person.greeting)  
// Still prints "Hello, Alice!"

As you can see, changing the name of the person does not change the greeting, because the greeting was already initialized with the original name.

If you want a variable that can change its value dynamically based on some conditions, you should use a computed property instead of a lazy variable. A computed property does not store any value, but rather calculates it every time it is accessed. For example:

struct Person {
  var name: String
  var greeting: String {
    return "Hello, \(name)!"
  }

  init(name: String) {
    self.name = name
  }
}

var person = Person(name: "Alice")
print(person.greeting) // Prints "Hello, Alice!"

person.name = "Bob"
print(person.greeting) // Prints "Hello, Bob!"

Now, changing the name of the person also changes the greeting, because the greeting is computed every time it is accessed.