The Fragile Base Class Problem

I haven't seen a lot of discussion on the fragile base class problem w.r.t. Swift lately. After reading some discussion of the topic on the cocoa-dev forums, I decided to do a little test:


Framework code:

public class BaseClass {
    public init() {}

    public func foo() {
        print("Foo called in framework class")
    }
}


App code:

import FBCFramework

class DerivedClass: BaseClass {
    func bar() {
        print("Bar called in subclass")
    }
}

let obj = DerivedClass()

obj.foo()


This returns what you'd expect:

Foo called in framework class


Now, let's change the framework, so that it looks like this:

public class BaseClass {
    public init() {}

    public func foo() {
        print("Foo called in framework class")
        self.bar()
    }

    internal func bar() {
        print("Bar called in framework class")
    }
}


Recompiling the framework, but not the app, and running results in:

Foo called in framework class
Bar called in subclass


Now, let's try reordering the methods in the framework, so that it looks like this:

public class BaseClass {
    public init() {}
  
    internal func bar() {
        print("Bar called in framework class")
    }
  
    public func foo() {
        print("Foo called in framework class")
        self.bar()
    }
}


Recompiling the framework and not the app results in this:

Foo called in framework class
Foo called in framework class
Foo called in framework class
Foo called in framework class
Foo called in framework class
Foo called in framework class
Foo called in framework class
Foo called in framework class
Foo called in framework class
Foo called in framework class
...


ad infinitum.


Recompiling the app, of course, fixes everything (until the next time the framework changes).


The conclusion seems to be that Swift, as of Xcode 7 beta 6, suffers from the fragile base-class problem even worse than the old Objective-C 1.0 runtime, since that was at least able to add/remove/reorder methods without causing problems with subclasses.


Are there plans to address this in the future?

This is a subset of the more general "Swift doesn't have a stable ABI" issue. Until that changes, everything in a Swift app needs to be compiled together.

Am I correctly interpreting this comment to mean that Apple is planning on having non-fragile base classes by the time the ABI is stabilized?

It's a fascinating topic especially when it comes to Swift.

Realistically, they have to do something if they want to use any of those shiny new Swift types in frameworks or plugins.


It'd be shocking (and really intriguing) if this didn't get fixed.

The thing about it is that the base-class problem is endemic to the kind of vtable dispatch that Swift (and C++) use. The reason Objective-C 1.0 was able to avoid it for methods was because of the message-sending mechanism it used, and the reason Objective-C 2.0 was able to avoid it for ivars was because it added an extra offset value to the class structure that would allow subclasses to figure out the right place for their ivars to go at runtime. Both of these things involved a performance hit, although usually a tiny, imperceptible one. Swift, of course, is all about eliminating imperceptible performance hits, though, so fixing the FBC is going to be at odds with its basic philosophy.

Exactly.


Maybe there is a different approach to solving the problem though. Perhaps the loader/linker could be used to 'correct' things when loading code segments????


I don't know. That's why I say it's a fascinating topic.

CharlesS: "Am I correctly interpreting this comment to mean that Apple is planning on having non-fragile base classes by the time the ABI is stabilized?"


Given Swift 2's failure to require methods always reference `self` when accessing members, I'd say "Good luck with that!"


To illustrate, consider the following example:


Their library:


// v 1.0
public class TheirBaseClass {
     public func doThis() {...}
}


Your program:


class MyClass: TheirBaseClass {
   func doMyThing() {
       doThat()
   }
}

func doThat() {...}


And this is all well and good... until they add a shiny new instance method whose name is coincidentally the same as your existing global function:


// v 1.1 -- added a new method named `doThat`
public class TheirBaseClass {
     public func doThis() {...}
     public func doThat() {...}
}


Next time you hit compile, all of a sudden your `doThat()` is being dispatched somewhere else entirely, even though your code hasn't changed at all. (Ditto for global vs inherited variables and constants too.)


I can only assume someone must've missed class the day Lexical Scoping was taught, because you should never be allowed to refer implicitly to a member that isn't already declared inside your own class declaration's lexical scope, but only by referencing `self` first; e.g.:


class MyClass: TheirBaseClass {
   func doMyThing() {
       self.doThis() // no confusion over who implements `doThis` or where to look for it
   }
}

not:


class MyClass: TheirBaseClass {
   func doMyThing() {
       doThis() // lexical scoping fail
   }
}


And frankly, once you qualify some cases you're best just to qualify all of them, as that simplifies the coding rules and eliminates any confusion about which form is or isn't valid where. (And Swift is a language that could do with quite a bit of simplification as it is.)


Honestly, sometimes it's like being back in Ruby - another language that can't do robust namespacing to save itself. Even Objective-C gets it largely right. Though ISTR AppleScript having the same problem with method dispatch, so at least Swift is in good company there.

Oh, I agree 100% on the implicit self. being a mistake (file a Radar! It probably won't get any more attention than my old one requesting a compiler warning for accessing Objective-C ivars without self-> in front, but hey). It doesn't really have to do with the fragile base class problem, though, since the scenario you described above is only likely to blow up on you once you recompile the app.

The Fragile Base Class Problem
 
 
Q