Overriding methods in Swift extensions

拥有回忆 提交于 2019-11-25 20:36:55

Extensions cannot/should not override.

It is not possible to override functionality (like properties or methods) in extensions as documented in Apple's Swift Guide.

Extensions can add new functionality to a type, but they cannot override existing functionality.

Apple Developer Guide

The compiler is allowing you to override in the extension for compatibility with Objective-C. But it's actually violating the language directive.

😊That just reminded me of Isaac Asimov's "Three Laws of Robotics" 🤖

Extensions (syntactic sugar) define independent methods that receive their own arguments. The function that is called for i.e. layoutSubviews depends on the context the compiler knows about when the code is compiled. UIView inherits from UIResponder which inherits from NSObject so the override in the extension is permitted but should not be.

So there's nothing wrong with grouping but you should override in the class not in the extension.

Directive Notes

You can only override a superclass method i.e. load() initialize()in an extension of a subclass if the method is Objective-C compatible.

Therefore we can take a look at why it is allowing you to compile using layoutSubviews.

All Swift apps execute inside the Objective-C runtime except for when using pure Swift-only frameworks which allow for a Swift-only runtime.

As we found out the Objective-C runtime generally calls two class main methods load() and initialize() automatically when initializing classes in your app’s processes.

Regarding the dynamic modifier

From the iOS Developer Library

You can use the dynamic modifier to require that access to members be dynamically dispatched through the Objective-C runtime.

When Swift APIs are imported by the Objective-C runtime, there are no guarantees of dynamic dispatch for properties, methods, subscripts, or initializers. The Swift compiler may still devirtualize or inline member access to optimize the performance of your code, bypassing the Objective-C runtime. 😳

So dynamic can be applied to your layoutSubviews -> UIView Class since it’s represented by Objective-C and access to that member is always used using the Objective-C runtime.

That's why the compiler allowing you to use override and dynamic.

One of the goals of Swift is static dispatching, or rather the reduction of dynamic dispatching. Obj-C however is a very dynamic language. The situation you're seeing is borne out of the link between the 2 languages and the way they work together. It shouldn't really compile.

One of the main points about extensions is that they are for extending, not for replacing / overriding. It's clear from both the name and the documentation that this is the intention. Indeed if you take out the link to Obj-C from your code (remove NSObject as the superclass) it won't compile.

So, the compiler is trying to decide what it can statically dispatch and what it has to dynamically dispatch, and it's falling through a gap because of the Obj-C link in your code. The reason dynamic 'works' is because it's forcing Obj-C linking on everything so it's all always dynamic.

So, it isn't wrong to use extensions for grouping, that's great, but it is wrong to override in extensions. Any overrides should be in the main class itself, and call out to extension points.

There is a way to achieve a clean separation of class signature and implementation (in extensions) while maintaining the ability to have overrides in subclasses. The trick is to use variables in place of the functions

If you make sure to define each subclass in a separate swift source file, you can use computed variables for the overrides while keeping the corresponding implementation cleanly organized in extensions. This will circumvent Swift's "rules" and will make your class's API/signature neatly organized in one place:

 // ---------- BaseClass.swift -------------

 public class BaseClass
 {
     public var method1:(Int) -> String { return doMethod1 }

     public init() {}
 }

 // the extension could also be in a separate file  
 extension BaseClass
 {    
     private func doMethod1(param:Int) -> String { return "BaseClass \(param)" }
 }

...

 // ---------- ClassA.swift ----------

 public class A:BaseClass
 {
    override public var method1:(Int) -> String { return doMethod1 }
 }

 // this extension can be in a separate file but not in the same
 // file as the BaseClass extension that defines its doMethod1 implementation
 extension A
 {
    private func doMethod1(param:Int) -> String 
    { 
       return "A \(param) added to \(super.method1(param))" 
    }
 }

...

 // ---------- ClassB.swift ----------
 public class B:A
 {
    override public var method1:(Int) -> String { return doMethod1 }
 }

 extension B
 {
    private func doMethod1(param:Int) -> String 
    { 
       return "B \(param) added to \(super.method1(param))" 
    }
 }

Each class's extension are able to use the same method names for the implementation because they are private and not visible to each other (as long as they are in separate files).

As you can see inheritance (using the variable name) works properly using super.variablename

 BaseClass().method1(123)         --> "BaseClass 123"
 A().method1(123)                 --> "A 123 added to BaseClass 123"
 B().method1(123)                 --> "B 123 added to A 123 added to BaseClass 123"
 (B() as A).method1(123)          --> "B 123 added to A 123 added to BaseClass 123"
 (B() as BaseClass).method1(123)  --> "B 123 added to A 123 added to BaseClass 123"

This answer it not aimed at the OP, other than the fact that I felt inspired to respond by his statement, "I tend to only put the necessities (stored properties, initializers) into my class definitions and move everything else into their own extension ...". I'm primarily a C# programmer, and in C# one can use partial classes for this purpose. For example, Visual Studio places the UI-related stuff in a separate source file using a partial class, and leaves your main source file uncluttered so you don't have that distraction.

If you search for "swift partial class" you'll find various links where Swift adherents say that Swift doesn't need partial classes because you can use extensions. Interestingly, if you type "swift extension" into the Google search field, its first search suggestion is "swift extension override", and at the moment this Stack Overflow question is the first hit. I take that to mean that problems with (lack of) override capabilities are the most searched-for topic related to Swift extensions, and highlights the fact that Swift extensions can't possibly replace partial classes, at least if you use derived classes in your programming.

Anyway, to cut a long-winded introduction short, I ran into this problem in a situation where I wanted to move some boilerplate / baggage methods out of the main source files for Swift classes that my C#-to-Swift program was generating. After running into the problem of no override allowed for these methods after moving them to extensions, I ended up implementing the following simple-minded workaround. The main Swift source files still contain some tiny stub methods that call the real methods in the extension files, and these extension methods are given unique names to avoid the override problem.

public protocol PCopierSerializable {

   static func getFieldTable(mCopier : MCopier) -> FieldTable
   static func createObject(initTable : [Int : Any?]) -> Any
   func doSerialization(mCopier : MCopier)
}

.

public class SimpleClass : PCopierSerializable {

   public var aMember : Int32

   public init(
               aMember : Int32
              ) {
      self.aMember = aMember
   }

   public class func getFieldTable(mCopier : MCopier) -> FieldTable {
      return getFieldTable_SimpleClass(mCopier: mCopier)
   }

   public class func createObject(initTable : [Int : Any?]) -> Any {
      return createObject_SimpleClass(initTable: initTable)
   }

   public func doSerialization(mCopier : MCopier) {
      doSerialization_SimpleClass(mCopier: mCopier)
   }
}

.

extension SimpleClass {

   class func getFieldTable_SimpleClass(mCopier : MCopier) -> FieldTable {
      var fieldTable : FieldTable = [ : ]
      fieldTable[376442881] = { () in try mCopier.getInt32A() }  // aMember
      return fieldTable
   }

   class func createObject_SimpleClass(initTable : [Int : Any?]) -> Any {
      return SimpleClass(
                aMember: initTable[376442881] as! Int32
               )
   }

   func doSerialization_SimpleClass(mCopier : MCopier) {
      mCopier.writeBinaryObjectHeader(367620, 1)
      mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } )
   }
}

.

public class DerivedClass : SimpleClass {

   public var aNewMember : Int32

   public init(
               aNewMember : Int32,
               aMember : Int32
              ) {
      self.aNewMember = aNewMember
      super.init(
                 aMember: aMember
                )
   }

   public class override func getFieldTable(mCopier : MCopier) -> FieldTable {
      return getFieldTable_DerivedClass(mCopier: mCopier)
   }

   public class override func createObject(initTable : [Int : Any?]) -> Any {
      return createObject_DerivedClass(initTable: initTable)
   }

   public override func doSerialization(mCopier : MCopier) {
      doSerialization_DerivedClass(mCopier: mCopier)
   }
}

.

extension DerivedClass {

   class func getFieldTable_DerivedClass(mCopier : MCopier) -> FieldTable {
      var fieldTable : FieldTable = [ : ]
      fieldTable[376443905] = { () in try mCopier.getInt32A() }  // aNewMember
      fieldTable[376442881] = { () in try mCopier.getInt32A() }  // aMember
      return fieldTable
   }

   class func createObject_DerivedClass(initTable : [Int : Any?]) -> Any {
      return DerivedClass(
                aNewMember: initTable[376443905] as! Int32,
                aMember: initTable[376442881] as! Int32
               )
   }

   func doSerialization_DerivedClass(mCopier : MCopier) {
      mCopier.writeBinaryObjectHeader(367621, 2)
      mCopier.serializeProperty(376443905, .eInt32, { () in mCopier.putInt32(aNewMember) } )
      mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } )
   }
}

Like I said in my introduction, this doesn't really answer the OP's question, but I'm hoping this simple-minded workaround might be helpful to others who wish to move methods from the main source files to extension files and run into the no-override problem.

Winter

Use POP (Protocol-Oriented Programming) to override functions in extensions.

protocol AProtocol {
    func aFunction()
}

extension AProtocol {
    func aFunction() {
        print("empty")
    }
}

class AClass: AProtocol {

}

extension AClass {
    func aFunction() {
        print("not empty")
    }
}

let cls = AClass()
cls.aFunction()
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!