Search code examples
iosswiftaccess-controlswift-extensions

Swift - How can I override an extension method in a concrete subclass


I have an extension on UIView implementing a protocol

protocol SomeProtocol {
  var property : Int
}
    extension UIView : SomeProtocol {
      var property : Int {
        get {
          return 0
        }
        set {
          // do nothing
        }
      }
    }

in a concrete subclass I want to override this extension method:

class Subclass : UIView, SomeProtocol {
  var _property : Int = 1
  var property : Int {
    get { return _property}
    set(val) {_property = val}
  }
}

I set breakpoints and see that the extension method is called and not the concrete subclass method:

var subclassObject = Subclass()

someObject.doSomethingWithConcreteSubclassObject(subclassObject)

// other code;

fun doSomethingWithConcreteSuclassObject(object : UIView) {
  var value = object.property // always goes to extension class get/set
}

Solution

  • As others have noted, Swift does not (yet) allow you to override a method declared in a class extension. However, I'm not sure whether you'll ever get the behavior you want even if/when Swift someday allows you to override these methods.

    Consider how Swift deals with protocols and protocol extensions. Given a protocol to print some metasyntactic variable names:

    protocol Metasyntactic {
        func foo() -> String
        func bar() -> String
    }
    

    An extension to provide default implementations:

    extension Metasyntactic {
        func foo() -> String {
            return "foo"
        }
    
        func bar() -> String {
            return "bar"
        }
    }
    

    And a class that conforms to the protocol:

    class FooBar : Metasyntactic {
        func foo() -> String {
            return "FOO"
        }
    
        func bar() -> String {
            return "BAR"
        }
    }
    

    Swift will use dynamic dispatch to call the appropriate implementations of foo() and bar() based on each variable's runtime type rather than on the type inferred by the compiler:

    let a = FooBar()
    a.foo()  // Prints "FOO"
    a.bar()  // Prints "BAR"
    
    let b: Metasyntactic = FooBar()
    b.foo()  // Prints "FOO"
    b.bar()  // Prints "BAR"
    

    If, however, we extend the protocol further to add a new method:

    extension Metasyntactic {
        func baz() -> String {
            return "baz"
        }
    }
    

    And if we override our new method in a class that conforms to the protocol:

    class FooBarBaz : Metasyntactic {
        func foo() -> String {
            return "FOO"
        }
    
        func bar() -> String {
            return "BAR"
        }
    
        func baz() -> String {
            return "BAZ"
        }
    }
    

    Swift will now use static dispatch to call the appropriate implementation of baz() based on the type inferred by the compiler:

    let a = FooBarBaz()
    a.baz()  // Prints "BAZ"
    
    let b: Metasyntactic = FooBarBaz()
    b.baz()  // Prints "baz"
    

    Alexandros Salazar has a fantastic blog post explaining this behavior in depth, but suffice it to say that Swift only uses dynamic dispatch for methods declared in the original protocol, not for methods declared in protocol extensions. I imagine the same would be true of class extensions, as well.