swiftcocoamethod-swizzling

Swizzle `init` and invoke actual implementation inside, Swift 4


I want to swizzle init(frame:) of NSView but call real init(frame:) inside. Is this possible? I know, that I can achive the same with subclassing and overriding, but it will too much work to change it in all places. I don't know how to correctly implement swizzled method to not get recursion. My code:

@objc func swizzledFrameInit(frame: NSRect) {
    //?? how to call init here
    self.wantsLayer = true
}


static func swizzleInitDescription() {
    DispatchQueue.once(token: "swizzleInitDescription") {
        let originalInitWithFrameSelector = #selector(NSView.init(frame:))

        let swizzledFrameSelector = #selector(NSView.swizzledFrameInit(frame:))

        let originalFrameInit = class_getInstanceMethod(self, originalInitWithFrameSelector)

        let swizzledFrameInit = class_getInstanceMethod(self, swizzledFrameSelector)

        let didAddSwizzledFrameInit = class_addMethod(self, originalInitWithFrameSelector, method_getImplementation(swizzledFrameInit!), method_getTypeEncoding(swizzledFrameInit!))

        if didAddSwizzledFrameInit {
            class_replaceMethod(self, swizzledFrameSelector, method_getImplementation(originalFrameInit!), method_getTypeEncoding(originalFrameInit!))
        } else {
            method_exchangeImplementations(originalFrameInit!, swizzledFramaInit!)
        }
    }
}

Also I tried pretty much the same but with convenience init and got recursion. Any Help would be appreciated.


Solution

  • Putting aside that this is a very dangerous and ill-advised swizzle if used for anything but exploration and debugging, the point of method_exchangeImplementations is that it exchanges implementations. This means that the old implementation becomes the swizzled method. So to call the old implementation you call yourself:

    @objc func swizzledFrameInit(frame: NSRect) {
        self.swizzledFrameInit(frame: frame)
        self.wantsLayer = true
    }
    

    Generally the correct way to implement what you're doing here is to set wantsLayer on the top-level view. This property applies to all subviews.

    Creating a layer-backed view implicitly causes the entire view hierarchy under that view to become layer-backed. Thus, the view and all of its subviews (including subviews of subviews) become layer-backed