3

I want to log some statements in deinit in each subclass of UIViewController in my project. I don't want to copy/paste the same lines in each view controller subclass.

ZaEeM ZaFaR
  • 1,508
  • 17
  • 22

2 Answers2

9

There is a way to achieve this.

You can't swizzle deinit, but you can swizzle another method like viewDidLoad to poison the class with associatedObject. When viewController deallocates, the associatedObject gets deallocated as well.

final class Deallocator {

    var closure: () -> Void

    init(_ closure: @escaping () -> Void) {
        self.closure = closure
    }

    deinit {
        closure()
    }
}

private var associatedObjectAddr = ""

extension UIViewController {

    @objc fileprivate func swizzled_viewDidLoad() {
        let deallocator = Deallocator { print("Deallocated") }
        objc_setAssociatedObject(self, &associatedObjectAddr, deallocator, .OBJC_ASSOCIATION_RETAIN)
        swizzled_viewDidLoad()
    }

    static let classInit: Void = {
        let originalSelector = #selector(viewDidLoad)
        let swizzledSelector = #selector(swizzled_viewDidLoad)
        let forClass: AnyClass = UIViewController.self
        let originalMethod = class_getInstanceMethod(forClass, originalSelector)
        let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector)
        method_exchangeImplementations(originalMethod!, swizzledMethod!)
    }()
}

Caveat

The closure will not get called exactly when viewController is deallocated, since the Deallocator is deallocated after viewController is completely deallocated.

farzadshbfn
  • 2,710
  • 1
  • 18
  • 38
0

I updated the solution that indirectly calls swift code, might not be the best. You can get the de-init implementation by using NSSelectorFromString and extend the implementation with swizzling and call swift code through bridging. Try this code that might help you:

//
//  UIViewController+ExtendDealloc.h
//  extension
//
//  Created by Amir Kamali on 26/2/19.
//  Copyright © 2019 Amir Kamali. All rights reserved.
//

@import UIKit;

@interface UIViewController (ExtendDealloc)


@end

.m file:

//
//  UIViewController+ExtendDealloc.m
//  extension
//
//  Created by Amir Kamali on 26/2/19.
//  Copyright © 2019 Amir Kamali. All rights reserved.
//

#import "UIViewController+ExtendDealloc.h"
#import <objc/runtime.h>
#import "test_objc-Swift.h"


@implementation UIViewController (ExtendDealloc)

#pragma mark - Swizzle Dealloc
+ (void)load {
    // is this the best solution?
    method_exchangeImplementations(class_getInstanceMethod(self.class, NSSelectorFromString(@"dealloc")),
                                   class_getInstanceMethod(self.class, @selector(swizzledDealloc)));
}

- (void)swizzledDealloc {
    [CustomBehaviorHandler printMe];
    [self swizzledDealloc];
}

@end

Swift code:

import Foundation
import UIKit

class CustomBehaviorHandler:NSObject {

    @objc
    static func printMe() {
        print("deinitializing ....")
    }
}

[UPDATED]

Amir.n3t
  • 2,859
  • 3
  • 21
  • 28
  • 1
    Question is for Swift, can yo also add a Swift sample usage? – Cristik Feb 26 '19 at 05:29
  • Couldn't see any direct solution, however I updated the above code that indirectly calls swift codes. – Amir.n3t Feb 26 '19 at 06:12
  • You can't call +load anymore on any extension (here `UIViewController (ExtendDealloc)`) starting with Xcode 10.2 beta 3. – Cœur Feb 26 '19 at 12:34