Search code examples
objective-cswiftobjective-c-runtime

Swift isa pointer remapping or other supported method swizzling


Do Swift classes have something like an isa pointer that can be remapped?

We've seen that Swift uses a more static method dispatch than objective-C, which (unless a class dervices from Foundation/NSObject) prevents the style of swizzling based on remapping method implementations at runtime.

I'm wondering how we'll implement method interception-based dynamic features like the observer pattern, notifications, etc? Currently all this stuff is provided by the Objective-C layer, and can be easily integrated into Swift. But, if we want to provide these kinds of features in a framework (or app) of our own, is it necessary to implement them in Objective-C? I would assume there's a way to do it 'natively'.

Another kind of swizzling common to objective-C is remapping the isa-pointer to generate a sub-class on the fly. Is this kind of swizzling supported in Swift? If not what is the supported way of intercepting arbitrary method invocations?

Edit: As @jatoben points out, as of arm64 isa-remapping must be done by calling object_setClass() and not by accessing the value directly. This is still referred to as 'isa pointer swizzling'


Solution

  • It looks like both method exchanging and the isa pointer remapping technique only works if the Swift class has NSObject as a super-class (either directly or further up). It does not currently work, when the Swift class has no super-class or some other non-Foundation base class.

    The following test shows this:

    Class: Birdy

    class Birdy: NSObject {    
        func sayHello()
        {
            print("tweet tweet")
        }    
    }
    

    Class: HodorBirdy

    class HodorBirdy: Birdy {
    
        override func sayHello()
        {
            super.sayHello()
            print("hodor hodor")
        }
    }
    

    Test:

    func testExample() {        
        let birdy : Birdy = Birdy()
        object_setClass(birdy, HodorBirdy.self)
        birdy.sayHello();
    }
    

    And the output was as expected:

    tweet tweet
    hodor hodor
    

    In this test both the base-class and sub-class were created in advance. Though they could also be created dynamically using the Objective-C runtime as long as the class has NSObject as an ancestor.

    When a Swift class does not derive from the Objective-C foundation, then the compiler will favor static- or vtable-based dispatch, therefore its not clear how method interception will work at all in this case!

    Unless the language/compiler make a specific allowance for it, we'll be foregoing dynamism in favor of performance. (Interception, which is the foundation of 'dynamic' behaviors can either be done at compile-time or run-time. In the case of static- or vtable-dispatch without a virtual machine, only compile-time applies).