12

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'

Community
  • 1
  • 1
Jasper Blues
  • 28,258
  • 22
  • 102
  • 185
  • It's not safe to access isa directly in Obj-C, either: http://sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html – jatoben Jun 05 '14 at 04:22

2 Answers2

12

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).

Satyam
  • 15,493
  • 31
  • 131
  • 244
Jasper Blues
  • 28,258
  • 22
  • 102
  • 185
  • Related: Is it normal (within the context of a Cocoa/CocoaTouch application) for a Swift class to have no Cocoa/NSObject ancestor? http://stackoverflow.com/q/24057525/404201 – Jasper Blues Jun 05 '14 at 10:55
2

I can't answer your question about swift "isa" equivalent, but I think I know part of the answer to your underlying question.

Property Observers seem to be the built-in means for the Observer Pattern. Instead of runtime discovery of "type" (RTTI, what-have-you) it is woven in explicitly.

From 'The Swift Programming Language' page 345:

Property observers observe and respond to changes in a property's value. Property observers are called every time a property's value is set, even if the new value is the same as the property's current value.

You can add property observers to any stored properties you define, apart from lazy stored properties. You can also add property observers to any inherited property (whether stored or computed) by overriding the property within a subclass.

You have the option to define either or both of these observers on a property:

  • willSet is called just before the value is stored.
  • didSet is called immediately after the new value is stored.

I am not sure how this is all going to work out, but I am intrigued.

Relying on run-time type discovery also seems to run counter to strong static type orthodoxy.

blank_dan
  • 41
  • 4
  • 1
    So property observers are baked right in. Useful (and useful to know). But there's still many more useful things to do with the intercept pattern. . let's see if we can turn up a direct answer as well. – Jasper Blues Jun 05 '14 at 04:16
  • I also expect Haskell devotees might explain that properly written closures are the way to go. While I am hoping to uncover explicit support for concurrency, like channels and Goroutines. if only. – blank_dan Jun 05 '14 at 04:38
  • One of the valid uses of method interception is the ability to modularize "cross-cutting requirements" as per the Aspect Oriented Programming paradigm . . ObjC fans were enamored with its ability to do this at runtime, rather than use a compile time tool. In fact there was no formal AOP framework for ObjC as the "raw materials" for runtime interception were good enough to get by. – Jasper Blues Jun 05 '14 at 05:34