Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invoke code in extension on object deinit?

In Swift (I'm using 4.1), is there a way to do some clean-up in an extension when an object is being destructed? I have in mind the kind of code that would go in deinit(), but an extension can't declare deinit(). (Besides which, if several extensions needed to do this, there would be multiple deinit() declarations.)

like image 257
Ted Hopp Avatar asked Apr 13 '18 18:04

Ted Hopp


People also ask

How do I use Swift 5 extension?

Creating an extension in Swift Creating extensions is similar to creating named types in Swift. When creating an extension, you add the word extension before the name. extension SomeNamedType { // Extending SomeNamedType, and adding new // functionality to it. }

Can you extend a struct Swift?

A Swift extension allows you to add functionality to a type, a class, a struct, an enum, or a protocol.

Why do we use extensions in Swift?

Extensions let us add functionality to classes, structs, and more, which is helpful for modifying types we don't own – types that were written by Apple or someone else, for example.

What is protocol extension?

Protocols let you describe what methods something should have, but don't provide the code inside. Extensions let you provide the code inside your methods, but only affect one data type – you can't add the method to lots of types at the same time.


1 Answers

I did not find a way to exactly get what you want, but maybe this code will help. I have never tried it, so maybe use it more as an inspiration. In a nutshell, it allows you to add bits of code that will excute on deinitialization.

/// This is a simple object whose job is to execute
/// some closure when it deinitializes
class DeinitializationObserver {

    let execute: () -> ()

    init(execute: @escaping () -> ()) {
        self.execute = execute
    }

    deinit {
        execute()
    }
}

/// We're using objc associated objects to have this `DeinitializationObserver`
/// stored inside the protocol extension
private struct AssociatedKeys {
    static var DeinitializationObserver = "DeinitializationObserver"
}

/// Protocol for any object that implements this logic
protocol ObservableDeinitialization: AnyObject {

    func onDeinit(_ execute: @escaping () -> ())

}

extension ObservableDeinitialization {

    /// This stores the `DeinitializationObserver`. It's fileprivate so you
    /// cannot interfere with this outside. Also we're using a strong retain
    /// which will ensure that the `DeinitializationObserver` is deinitialized
    /// at the same time as your object.
    fileprivate var deinitializationObserver: DeinitializationObserver {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.DeinitializationObserver) as! DeinitializationObserver
        }
        set {
            objc_setAssociatedObject(
                self,
                &AssociatedKeys.DeinitializationObserver,
                newValue,
                objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
            )    
        }
    }

    /// This is what you call to add a block that should execute on `deinit`
    func onDeinit(_ execute: @escaping () -> ()) {
        deinitializationObserver = DeinitializationObserver(execute: execute)
    }
}

How it works

Now let's assume you have your object, let's call it A and you want to create some code inside an extension, you can use onDeinit to add your clean-up code like this:

extension A {

    func someSetup() {

        // Do your thing...


        onDeinit {
            /// This will be executed when A is deinitialized
            print("Execute this")
        }
    }
}

You can also add it from outside the extension,

let a = A()
a.onDeinit {
    print("Deinitialized")
}

Discussion

  • This is different than what you want in that instead of definition a function (deinit) you need to call one. But anyway in protocol extensions, you can never really use the underlying object's life cycle, so it should be okay for most cases.
  • I am not sure about the order of execution between A's deinit and the DeinitializationObserver's deinit. You might need to drop the assumption that A is still in memory when you write the code inside the closure.
  • If you need more than one onDeinit you can update the associated object to retain an array of DeinitializationObserver
  • In my code, I usually use ReactiveCocoa's Lifetime for this kind of things. However, it is more complicated than what I wrote here. Instead, it looks like they swizzle the dealloc selector. If you're interested you can take a look inside - https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoa/NSObject+Lifetime.swift
like image 134
Julien Perrenoud Avatar answered Oct 18 '22 09:10

Julien Perrenoud