Overriding Methods in Swift Extensions

I try only to put the necessary things (stored properties, initializers) in my class definitions and move everything else into my own extension , sort of like extension for each logical block that I would group with // MARK: as well.

For a UIView subclass, for example, I get an extension for layout related materials, one for subscribing and handling events, etc. In these extensions, I inevitably have to override some UIKit methods, for example. layoutSubviews . I have never noticed any problems with this approach - until today.

Take this class hierarchy, for example:

 public class C: NSObject { public func method() { print("C") } } public class B: C { } extension B { override public func method() { print("B") } } public class A: B { } extension A { override public func method() { print("A") } } (A() as A).method() (A() as B).method() (A() as C).method() 

The output signal is ABC . It makes little sense to me. I read about static messages about protocol extensions, but this is not a protocol. This is a regular class, and I expect method calls to be dynamically dispatched at runtime. Obviously, a call to C should be at least dynamically sent and produce C ?

If I remove the inheritance from NSObject and make C root class, the compiler complains about saying declarations in extensions cannot override yet , which I already read about. But how to make NSObject as a root class change things?

Moving both overrides into their class declaration creates AAA , as expected, only B produces ABB , only moving A calls CBC , the last of which makes absolutely no sense to me: even the one that is statically printed on A gives A -output more!

Adding the dynamic keyword to a definition or override seems to give me the desired behavior "from this point in the class hierarchy down" ...

Let's change our example to something less constructed that actually made me post this question:

 public class B: UIView { } extension B { override public func layoutSubviews() { print("B") } } public class A: B { } extension A { override public func layoutSubviews() { print("A") } } (A() as A).layoutSubviews() (A() as B).layoutSubviews() (A() as UIView).layoutSubviews() 

Now we get ABA . Here I cannot make UIView layoutSubviews dynamic in any way.

Moving both overrides into their class declaration again returns us AAA , only A or only B still gets us ABA . dynamic solves my problems again.

In theory, I could add dynamic to all the override I've ever done, but feel like I'm doing something else wrong.

Is it really wrong to use extension to group code like me?

+109
swift swift2 swift-extensions
Jul 05 '16 at 21:44
source share
5 answers

Extensions cannot / should not override.

You cannot override functionality (for example, properties or methods) in extensions, as described in the Apple Swift Guide.

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

Apple Developer Guide

The compiler allows you to override the extension for compatibility with Objective-C. But this actually violates the language directive.

"It 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, i.e. layoutSubviews depends on the context that the compiler knows when the code compiles. UIView inherits from UIResponder, which inherits from NSObject, so overriding in the extension is allowed, but should not be.

So there is nothing wrong with grouping, but you must override in the class, not in the extension.

Directory notes

You can override the superclass method, that is, load() initialize() in the extension of the subclass, if the method is compatible with Objective-C.

Therefore, we can take a look at why it allows you to compile using layoutSubviews .

All Swift applications run inside the Objective-C runtime, except when using pure Swift-only environments that only allow the Swift runtime.

As we found out, the Objective-C runtime usually calls the two main class methods load() and initialize() automatically when the classes are initialized in your application processes.

Regarding dynamic modifier

From the iOS Developer Library

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

When the Swift API is imported by the Objective-C runtime, there are no guarantees of dynamic dispatch for properties, methods, indexes, or initializers. The Swift compiler can still use virtual or native element access to optimize the performance of your code, bypassing the Objective-C runtime. 😳

Thus, dynamic can be applied to your layoutSubviews UIView Class since it is represented by Objective-C, and access to this member is always used using the Objective-C runtime.

This is why the compiler allows you to use override and dynamic .

+183
Jul 08 '16 at 20:10
source share

One of the goals of Swift is static scheduling, or rather reducing dynamic scheduling. Obj-C, however, is a very dynamic language. The situation you are seeing is confirmed by the connection between the two languages ​​and how they work together. It should not compile.

One of the highlights of extensions is that they are intended to be extensions, not replace / override. From the name and documentation it is clear that this is the intention. Indeed, if you pull the Obj-C link from your code (remove NSObject as a superclass), it will not compile.

So, the compiler is trying to decide what it can statically send and what it needs to dynamically send, and it fails through a space due to the Obj-C link in your code. The reason dynamic “works” is because it forces Obj-C to bind everything, so it is always dynamic.

Thus, you should not use extensions for grouping, which is fine, but incorrectly redefined in extensions. Any overrides should be in the main class and call extension points.

+16
Jul 08 '16 at 15:17
source share

There is a way to achieve a clear separation of the signature and implementation of the class (in extensions), while maintaining the ability to override subclasses. The trick is to use variables instead of functions

If you necessarily define each subclass in a separate fast source file, you can use the computed variables for overrides, while maintaining the appropriate implementation, which would be neatly organized in extensions. This will cost Swift the “rules” and make your API / class 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 extension can use the same method names for implementation, since they are private and not visible to each other (if they are in separate files).

As you can see, inheritance (using the variable name) works correctly 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" 
+7
Jul 09 '16 at 1:08
source share

This answer, he did not aim at the OP, except that I felt inspired by the answer of his statement: "I try to only put the necessary things (stored properties, initializers) in my class definitions and move everything else into their own extension ...". I am primarily a C # programmer, and in C # you can use partial classes for this purpose. For example, Visual Studio puts UI-related material in a separate source file using a partial class and leaves your main source file uncluttered, so you don't have this distraction.

If you are looking for a “quick partial class”, you will find various links in which Swift members say that Swift does not need partial classes because you can use extensions. Interestingly, if you type “quick extension” into the Google search field, its first search sentence is “quick redefinition of the extension,” and at the moment this stack overflow question is the first hit. I suggest that this means that problems with (lack of) redefinition capabilities are the most viewed topics related to Swift extensions, and the fact that Swift extensions cannot replace partial classes, at least if you use derived classes in your programming.

In any case, in order to shorten the short introduction, I encountered this problem in a situation where I wanted to move some template / baggage methods from the main source files for Swift classes, which my C # -to-Swift program generates. Having run the problem without redefinition resolved for these methods after moving them to extensions, I ended up implementing the following simple approach. The main Swift source files still contain some tiny stub methods that call real methods in the extension files, and these extension methods are given unique names to avoid overriding problems.

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

As I said in my opening remarks, this does not actually answer the OP question, but I hope this simplified workaround may be useful for others who want to transfer methods from the main source files to extension files and run into the problem without redefinition.

+1
Jan 24 '17 at 0:01
source share

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() 
+1
Nov 23 '17 at 3:28
source share



All Articles