Swift - Lazy Var vs. Let when creating views programmatically (saving memory)
Whether you will use lazy var
or not depends on your code and its context. It is not bad or good on its own. You have to decide when it is appropriate.
Before you can decide that, you have to know what lazy var
is.
What is lazy var
?
Lazy initialization is a concept where initialization (construction) of variable content is delayed until its first usage. First access to such variable triggers initialization. Since content is not created until variable is used (needed) using lazy initialized variables can save resources.
That is primary drive behind lazy initialization. You don't create something until you need it. That is also logic you will use when deciding whether something should be lazy var
or not.
If you are dealing with views (or anything else) that are always visible (needed) there is little point in using lazy initialization. On the other hand when you are dealing with instances that are not always needed - then using lazy var
is justified.
If your view is always visible in presented view controller, you will not accomplish much by making it lazy. If it is visible only under specific circumstances - for instance when user expands some collapsed panel - then making it lazy makes sense. It will make your view controller load faster and use less memory by default.
As far as thread safety is concerned, lazy var
are not thread safe in Swift.
That means if two different threads try to access the same lazy var
at the same time, before such variable has been initialized it is possible that one of the threads will access partially constructed instance.
You can find more about thread safety in:
Swift - is lazy var thread-safe?
Make "lazy var" threadsafe
What is the advantage of a lazy var in Swift
Lazy Stored Property vs Stored Property
There are a few advantages in having a lazy property instead of a stored property.
- The closure associated to the lazy property is executed only if you read that property. So if for some reason that property is not used (maybe because of some decision of the user) you avoid unnecessary allocation and computation.
- You can populate a lazy property with the value of a stored property.
- You can use
self
inside the closure of a lazy property
Swift access lazy var endless loop cause crash
The problem is that your closure to create a table view ends up being recursive.
When you set a tableFooterView on the table, it immediately tries to find out if that footer should be drawn / visible / onscreen immediately.
To know if the footer should be visible, the table view has to find out how many rows should currently be displayed, so it asks the data source (your view controller) for the number of rows in the the first section
Inside your method
tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
, you have the lineif tableView == hotStockTableView {
. This tries to access the lazyhotStockTableView
var, which still hasn't completed initializing, since that closure is what is calling this code in the first placeSo the lazy var closure is kicked off again and recursively goes through the same steps over and over and over until stack overflow and crash.
Fortunately, the solution is easy. If you haven't set the data source yet then adding the table view footer will never call your number of rows data source method. So just change the order of code in your lazy closure to the following:
private lazy var hotStockTableView: UITableView = {
let tableView = UITableView()
print("hotStockTableView ======> \(tableView) \n")
tableView.tableFooterView = UIView()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.delegate = self
tableView.dataSource = self
return tableView
}()
Now your code will work just fine.
Swift - is lazy var thread-safe?
From The Swift Programming Language: Properties:
If a property marked with the
lazy
modifier is accessed by multiple threads simultaneously and the property has not yet been initialized, there is no guarantee that the property will be initialized only once.
lazy var
is not thread safe. You can use
dispatch_once
(runs once per lifetime of the app)- a constant (
let
) - the nested struct pattern (typically used for singletons)
for thread safety. (See this question for some examples.)
You could also employ your own locking using NSRecursiveLock
but that's probably not as efficient as dispatch_once
.
How to re-initialize a lazy property from another view?
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()
Is there any gain in Swift by defining constants instead of variables as much as possible?
In theory, there should be no difference in speed or memory usage - internally, the variables work the same. In practice, letting the compiler know that something is a constant might result in better optimisations.
However the most important reason is that using constants (or immutable objects) helps to prevent programmer errors. It's not by accident that method parameters and iterators are constant by default.
Using immutable objects is also very useful in multithreaded applications because they prevent one type of synchronization problems.
How do I deinit a lazy var in Swift?
To do this, your lazy variable would have to be declared as optional even though you intend to initialize it as soon as the value is needed (i.e. lazily).
class MyObject {
func run() {
print( "MyObject :: run()" )
}
init() {
print( "MyObject :: init" )
}
deinit {
print( "MyObject :: deinit" )
}
}
class MyContext {
lazy var myObject:MyObject? = MyObject()
}
let myContext = MyContext()
myContext.myObject?.run() //< "MyObject :: init"
myContext.myObject = nil //< "MyObject :: deinit"
Also, I disagree with the notion of the "doom and gloom of optionals"—one only need know which of the many available techniques is most convenient and practical way handle them, and how to avoid allowing too many permutations of state to exist among combinations of optional and non-optional values in a given context. Furthermore, an optional is actually exactly what you want here because you intend to nullify it. Employing an optional doesn't mean that a value will be nil
for now until you initialize it, but rather that it may be nil
at any point in its life, even if it is defined at declaration or any other time beforehand. If avoiding optionals is truly that high of a priority for you, you'd have to take a step back and re-evaluate your architecture to create a situation where you no longer have the need to de-initialize the value.
Lazy stored property in Swift
Mike Buss wrote an article about lazy initialization in Swift http://mikebuss.com/2014/06/22/lazy-initialization-swift/
One example of when to use lazy initialization is when the initial value for a property is not known until after the object is initialized or when the computation of the property is computationally expensive.
Here are two examples for both cases from the post:
In the first example we don't know which value personalized greeting should have. We need to wait until the object is initialized to know the correct greeting for this person.
class Person {
var name: String
lazy var personalizedGreeting: String = {
[unowned self] in
return "Hello, \(self.name)!"
}()
init(name: String) {
self.name = name
}
}
The second example covers the case of an expensive computation. Imagine a MathHelper class which should give you values for pi and other important constants. You don't need to calculate all constants if you only use a subset of them.
class MathHelper {
lazy var pi: Double = {
// Calculate pi to a crazy number of digits
return resultOfCalculation
}()
}
Lazy instantiating a UIDynamicAnimator with referenceView - Swift
In the session video "Building Interruptible and Responsive Interactions", they did exactly this.
The solution is to define animator
as an Optional and initialize it inside viewDidLoad
.
class ViewController: UIViewController {
var animator : UIDynamicAnimator?
@IBOutlet var gameView : UIView
override func viewDidLoad() {
super.viewDidLoad()
animator = UIDynamicAnimator(referenceView: gameView)
animator!.property = value
...
}
...
I slightly dislike this because future references to animator
will all need to unwrap it.
A slightly better approach imo is to define it as an implicitly unwrapped optional. So it would look like this:
class ViewController: UIViewController {
var animator : UIDynamicAnimator!
@IBOutlet var gameView : UIView
override func viewDidLoad() {
super.viewDidLoad()
animator = UIDynamicAnimator(referenceView: gameView)
animator.property = value
...
}
...
Related Topics
Error Using Associated Types and Generics
How to Setup Viewcontroller in Playgrounds
What's the Swift Equivalent of Objective-C's "#Ifdef _Iphone_11_0"
Differencein Approach to Create Dispatchqueue Swift3
How to Add Initializers in Extensions to Existing Uikit Classes Such as Uicolor
Swift: How to Animate the Rowheight of a Uitableview
Can/How to Replace My Kvo Stuff with Rc3
How to Position Banner Ads Over Uitabbar
Using a Property as a Default Parameter Value for a Method in the Same Class
Mock Third Party Classes (Firebase) in Swift
Adding Activity Indicator to Uialertview
Swift 4.2+ Seeding a Random Number Generator
Swiftui - How to Use Oncommand with Nsmenuitem on MACos
Converting String to Data in Swift 3.0
What Does the Swift 'Mutating' Keyword Mean