Overriding methods in Swift extensions
我倾向于将必需品(存储属性、初始值设定项)放入类定义中,并将其他所有东西移动到它们自己的
例如,对于一个uiview子类,我最终会得到一个布局相关内容的扩展,一个用于订阅和处理事件等。在这些扩展中,我不可避免地必须重写一些uikit方法,例如
以这个类层次结构为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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() |
输出为
如果我从
将两个覆盖移动到它们的类声明中会按预期生成
将
让我们把我们的例子改成一个结构稍差的例子,是什么让我提出了这个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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() |
我们现在得到了
将这两个覆盖移动到它们的类声明中会再次得到我们的
理论上,我可以把
像我一样使用EDOCX1[0]对代码进行分组真的是错误的吗?
扩展不能/不应该重写。
如苹果的Swift指南所述,不可能覆盖扩展中的功能(如属性或方法)。
Extensions can add new functionality to a type, but they cannot override existing functionality.
苹果开发者指南
编译器允许您在扩展中重写以与Objective-C兼容。但它实际上违反了语言指令。
?这让我想起艾萨克·阿西莫夫的"机器人三定律"??
扩展(语法糖)定义接收自己参数的独立方法。调用的函数,即
所以分组没有什么问题,但是您应该在类中重写,而不是在扩展中重写。
指示注释
如果方法与Objective-C兼容,则在子类的扩展中只能使用
因此,我们可以看看为什么它允许您使用
所有Swift应用程序都在Objective-C运行时内执行,但使用纯Swift-Only框架时除外,该框架只允许使用Swift-Only运行时。
我们发现,在应用程序进程中初始化类时,objective-c运行时通常会自动调用两个类主方法
来自iOS开发者库
可以使用
当由Objective-C运行时导入Swift API时,无法保证对属性、方法、下标或初始值设定项进行动态调度。Swift编译器仍然可以取消或内联成员访问以优化代码的性能,从而绕过Objective-C运行时。???
因此,
这就是为什么编译器允许您使用
Swift的目标之一是静态调度,或者更确切地说是减少动态调度。但是obj-c是一种非常动态的语言。你所看到的情况是由这两种语言和它们一起工作的方式之间的联系所证实的。它不应该真正编译。
关于扩展的一个要点是它们是用于扩展的,而不是用于替换/重写。从名字和文档中都可以清楚地看出,这就是目的。实际上,如果从代码中去掉到obj-c的链接(删除
因此,编译器正试图决定它可以静态地调度什么,必须动态地调度什么,并且由于代码中的obj-c链接,它正在经历一个缺口。
所以,使用扩展进行分组并没有错,这很好,但是在扩展中重写是错误的。任何重写都应该在主类本身中,并调用扩展点。
有一种方法可以实现类签名和实现(在扩展中)的清晰分离,同时保持在子类中具有覆盖的能力。诀窍是用变量代替函数
如果确保在单独的swift源文件中定义每个子类,则可以使用计算变量进行重写,同时保持相应的实现在扩展中干净地组织。这将绕开斯威夫特的"规则",使你的班级的API/签名整齐地组织在一个地方:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // ---------- 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)" } } |
…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // ---------- 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))" } } |
…
1 2 3 4 5 6 7 8 9 10 11 12 13 | // ---------- 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))" } } |
每个类的扩展都能够为实现使用相同的方法名,因为它们是私有的,彼此不可见(只要它们在不同的文件中)。
如您所见,继承(使用变量名)使用super.variable name正常工作。
1 2 3 4 5 | 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" |
使用pop(面向协议的编程)覆盖扩展中的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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() |
这个答案并不是针对操作,而是我感觉受到他的陈述的启发而做出回应,"我倾向于将必需品(存储属性、初始值设定项)放入我的类定义中,并将其他所有东西移到它们自己的扩展中…"。我主要是一个C程序员,在C中,可以使用部分类来实现这个目的。例如,Visual Studio使用分部类将与UI相关的内容放在单独的源文件中,并保持主源文件的整洁,这样就不会分散您的注意力。
如果你搜索"Swift部分类",你会找到各种链接,其中Swift的追随者说Swift不需要部分类,因为你可以使用扩展。有趣的是,如果你在谷歌搜索栏中输入"swift extension",它的第一个搜索建议是"swift extension override",此时这个堆栈溢出问题是第一个被点击的问题。我认为这意味着(缺少)重写功能的问题是最容易搜索到与swift扩展相关的主题,并且强调了这样一个事实:swift扩展不可能替换部分类,至少如果在编程中使用派生类的话。
总之,为了缩短冗长的介绍,我遇到了这个问题,在这种情况下,我想将一些样板/行李方法从我的C到Swift程序生成的Swift类的主源文件中移出。在将这些方法移到扩展之后,遇到了不允许覆盖这些方法的问题,我最终实现了以下简单的变通方法。主要的Swift源文件仍然包含一些调用扩展文件中实际方法的小存根方法,这些扩展方法被赋予唯一的名称以避免覆盖问题。
1 2 3 4 5 6 | public protocol PCopierSerializable { static func getFieldTable(mCopier : MCopier) -> FieldTable static func createObject(initTable : [Int : Any?]) -> Any func doSerialization(mCopier : MCopier) } |
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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) } } |
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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) } ) } } |
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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) } } |
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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) } ) } } |
正如我在引言中所说,这并不能真正回答OP的问题,但我希望这种简单的解决方法可能对那些希望将方法从主源文件移动到扩展文件并遇到无覆盖问题的人有所帮助。