Lazy Initialization and Deinit

Lazy initialization and deinit

You can use a private variable to track if the session has been created. This example does what you want, I think (code from a playground):

class Thing {
private var hasMadeSession: Bool = false
lazy fileprivate var session: Int = {
self.hasMadeSession = true
return 1
}()

deinit {
print(#function)
if self.hasMadeSession {
print("Hello")
}
}
}

var thing: Thing? = Thing()
thing = nil // Prints "deinit"
thing = Thing()
thing?.session
thing = nil // Prints "deinit" and "Hello"

Lazy initialisation and retain cycle

I tried this [...]

lazy var personalizedGreeting: String = { return self.name }()

it seems there are no retain cycles

Correct.

The reason is that the immediately applied closure {}() is considered @noescape. It does not retain the captured self.

For reference: Joe Groff's tweet.

Avoid crash when lazy var referencing self is accessed for the first time during deinit

I just created of what you said like below:

protocol Hello {
func thisGetsCalledSometimes()
}

class MyService {

var delegate: Hello?

init(key: String) {
debugPrint("Init")
}

func start() {
debugPrint("Service Started")
}

func stop() {
debugPrint("Service Stopped")
}
}

class MyClass: Hello {

lazy var service: MyService = {
// To init and configure this service,
// we need to reference `self`.
let service = MyService(key: "") // Just pretend key exists :)
service.delegate = self
return service
}()

func thisGetsCalledSometimes() {
// Calling this function causes the lazy var to
// get initialised.
self.service.start()
}

deinit {
// If `thisGetsCalledSometimes` was NOT called,
// this crashes because the initialising closure
// for `service` references `self`.
self.service.stop()
}
}

and I access like this: var myService: MyClass? = MyClass() which got me the below output:

"Init"
"Service Stopped"

Is that what you are looking for?

Update:

Here is I have edited your class based on tagged answer.

import UIKit

// Stuff to create a view stack:

class ViewController: UINavigationController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

let firstController = FirstController()
let navigationController = UINavigationController(rootViewController: firstController)
self.present(navigationController, animated: false, completion: nil)
}
}

class FirstController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

let button = UIButton()
button.setTitle("Next screen", for: .normal)
button.addTarget(self, action: #selector(onNextScreen), for: .touchUpInside)
self.view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
}

@objc func onNextScreen() {
let secondController = SecondController()
self.navigationController?.pushViewController(secondController, animated: true)
}
}

// The service and view controller where the crash happens:

protocol ServiceDelegate: class {
func service(_ service: Service, didReceive value: Int)
}

class Service {
weak var delegate: ServiceDelegate?

func start() {
print("Starting")
self.delegate?.service(self, didReceive: 0)
}

func stop() {
print("Stopping")
}

deinit {
delegate = nil
}
}

class SecondController: UIViewController {

private var isServiceAvailable: Bool = false

private lazy var service: Service = {
let service = Service()
service.delegate = self
//Make the service available
self.isServiceAvailable = true
return service
}()

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// service.start() // <- Comment/uncomment to toggle crash
}

deinit {
if self.isServiceAvailable {
self.service.stop()
}
}
}

extension SecondController: ServiceDelegate {
func service(_ service: Service, didReceive value: Int) {
print("Value: \(value)")
}
}

This is the only option, I think! Let me know if you find anything interesting.

Closure - Timing of deinit self object

First of all you need to know definition of lazy variable.

According to apple docs, A lazy stored property is a property whose initial value isn’t calculated until the first time it’s used. You indicate a lazy stored property by writing the lazy modifier before its declaration.

Means that when the class is initial, they don't know they have variable lazy inside which is incrementNumber.

That the reason when you call

do {
let increment = Increment().incrementNumber(3)
}

Increment don't recognized incrementNumber in the class when you access directly. So Swift only see that you do nothing with Increment class in the rest of code then it automatically deinit the unused class.

Update: As Mr. DuncanC's mentioned, because of deinit class first so the compiler create an instance of Increment as AutoReleased, and keep it in memory in order to evaluate the second part of the expression

At the second, you call

let increment = Increment()
increment.incrementNumber(3)

Means that you make a class at first then you make lazy variable ( Swift sees that you do something with that class in second line so it waits until everything in class is called). Then in the rest of code class Increment is unused then it automatically deinit. That's the reason you see lazy is call before deinit.

For more further knowledge, you can do like making a not lazy function to see the difference.

class Increment {
var number = 0

init(){
print(#function)
}

deinit {
print(#function)
}

public func increaseNumber(_ value: Int) {
self.number += value
print(#function)
}
}

do {
let increment = Increment().increaseNumber(3)
}

//init()
//increaseNumber(_:)
//deinit

As you can see that increaseNumber is called before deinit because the class know that it owns func increaseNumber so it was called. Then in the rest of code class Increment is unused then it automatically deinit.

Lazy Initialization sounds and works great. Why not always default to using lazy initialization?

Consider this example of a lazy var:

struct S {
lazy var lazyVar: Int = { /* compute some value */ 0 }()
}

What really happens behind the scenes is something like this:

struct S {
var _lazyVar: Int? = nil

var lazyVar: Int {
mutating get {
if let existingValue = _lazyVar {
return existingValue
}
else {
let newlyComputedValue = /* compute some value */ 0
_lazyVar = newlyComputedValue
return newlyComputedValue
}
}
}
}

As you see, every access of lazyVar requires a branch, to check if there's a value already, or if it's necessary to compute one for the first time. This adds overhead that easily outweighs the benefit of lazily evaluating values with simple (fast) derivations.

Lazy initialization using self and unown self,

In this code:

lazy var greeting: String = {
return "Hello, my name is \(self.firstName)"
}()

... there is no retained closure. Notice the (). This is just a function, like any other; it is called, not stored. greeting is a String, not a stored closure.

Set lazy var from outside without initializing

The lazy initializer won't run if you just assign a value normally before ever reading. Here's a test case:

class Class {
lazy var lazyVar: String = {
print("Lazy initializer ran")
return "Default value"
}()
}

let object = Class()
object.lazyVar = "Custom value"
print(object.lazyVar)

Swift initializing lazy value

I wonder if this is possibly a compiler bug. I don't see the same error you are getting, but this will create a Swift compiler segmentation fault 11:

struct Test {
let foo: String
lazy var bar: String = { return "" }()
}

extension Test {
init(_ foo: String) {
self.foo = foo
}
}


Related Topics



Leave a reply



Submit