Swift - Self in Deinit Method

Swift - self in deinit method

In 90% of cases you have to use self in deinit. Both in Swift and Objective-C.

That's actually the whole point of that method - the last chance to access that object before deallocation.

What you should avoid is storing self to another object from deinit, not accessing self.

Also, in Objective-C some people try to avoid using property setters and getters in init and dealloc to avoid dangerous side-effects that might be hidden in them, and they are accessing ivars directly instead (_prop = nil instead of self.prop = nil). That's actually impossible in Swift because there are no ivars. However, Swift is much safer in that regard. Note that _prop = nil in Objective-C still accesses self. It's just a short syntax for self->_prop = nil. We are avoiding properties, not self.

When is `deinit` exactly called? (in Swift)

The deinit is intended to release resources (such as freeing memory that is not under ARC).

(Thanks to Martin
and Rob's input, we can conclude below)

When is deinit called?

Usually, when last strong-reference gets out of scope,
deinit is called instantly (then deallocation happens).

  • But if affected by autorelease feature (which has conditions), deinit is called significantly later,
    long after last reference gets out of scope (when autorelease pool is drained).
  • And when App is terminating, deinit is guaranteed to never get called!?
    (if deinit was not already called).
  • Also in extremely common cases, deinit is called before strong-ref-variable's scope ends:
    • In Swift unlike other languages, when we set a weak-reference equal to a strong-reference,
      it could result to nil (which is absolutely allowed by Swift).

    • This happens if compiler detects that the remaining lines of scope,
      have NOT any strong-reference.

    • Possible workaround is using withExtendedLifetime(_:_:) global-method / function, like:

    withExtendedLifetime(myStrongRefVariable) {
    // Do something that only needs non-nil weak reference.
    }

Is it like C++ destructor?

There is no equivalent of a C++ destructor in ObjC or Swift.

(Objective-C++ object's destructor (dealloc) are called during program termination,
because that is required by C++ spec,
but that's all and else Obj-C++'s dealloc behavior is same as deinit.)

Is Swift using Garbage-Collector?

No, but whenever autorelease feature affects objects,
the deinit can be postponed (till autorelease-pool is drained, as mentioned above).

deinit not called in specific case

I expect deinit to be called at program termination

You should not expect that. Objects that exist at program termination are generally not deallocated. Memory cleanup is left to the operating system (which frees all of the program's memory). This is a long-existing optimization in Cocoa to speed up program termination.

deinit is intended only to release resources (such as freeing memory that is not under ARC). There is no equivalent of a C++ destructor in ObjC or Swift. (C++ and Objective-C++ objects are destroyed during program termination, since this is required by spec.)

how to call deinit method within class definition in swift

You can create a protocol which does the self destruction based on a certain criteria. Here's an example using a class

class SelfDestructorClass
{
var calledTimes = 0
let MAX_TIMES=5
static var instancesOfSelf = [SelfDestructorClass]()

init()
{
SelfDestructorClass.instancesOfSelf.append(self)
}

class func destroySelf(object:SelfDestructorClass)
{
instancesOfSelf = instancesOfSelf.filter {
$0 !== object
}
}

deinit {
print("Destroying instance of SelfDestructorClass")
}

func call() {
calledTimes += 1
print("called \(calledTimes)")
if calledTimes > MAX_TIMES {
SelfDestructorClass.destroySelf(self)
}
}
}

You can derive your class from this class and then call call() on those object. The basic idea is to have the ownership of the object at one and only one place only and then detach the ownership when the criteria is met. The ownership in this case is a static array and detaching is removing it from the array. One important thing to note is that you have to use weak reference to the object wherever you are using it.

E.g.

class ViewController: UIViewController {

weak var selfDestructingObject = SelfDestructorClass()

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

@IBAction func countDown(sender:AnyObject?)
{
if selfDestructingObject != nil {
selfDestructingObject!.call()
} else {
print("object no longer exists")
}
}
}

Why can I access an object after its deinit method has been called?

The code works as expected in a regular project, as opposed to a playground.

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.



Related Topics



Leave a reply



Submit