在Swift扩展中重写方法
我倾向于只将必需品(存储的属性,初始值设定项)放入我的类定义中,并将其他所有内容都放到它们自己的extension
,就像每个逻辑块的extension
一样,我将与// MARK:
分组在一起。
例如,对于一个UIView子类,我将最终得到一个与布局相关的东西的扩展,一个用于订阅和处理事件等等。 在这些扩展中,我不得不重写一些UIKit方法,例如layoutSubviews
。 我从来没有注意到这种方法的任何问题 – 直到今天。
以这个类层次结构为例:
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()
输出是ABC
。 这对我来说毫无意义。 我阅读了协议扩展静态调度,但这不是一个协议。 这是一个普通的类,我希望方法调用在运行时dynamic分配。 显然C
上的调用至less应该被dynamic调度并生成C
?
如果我从NSObject
删除inheritance,并将C
作为一个根类,编译器会抱怨说declarations in extensions cannot override yet
,我已经读过。 但是NSObject
作为一个根类是如何改变的呢?
把这两个覆盖转移到他们的类声明中,就像预期的那样产生了AAA
,只有B
才会产生ABB
,只有A
产生了CBC
,最后一个对我来说完全没有意义,即使是静态types为A
产生A
– 输出更多!
将dynamic
关键字添加到定义或覆盖似乎给我期望的行为“从类层次结构向下的那一点”…
让我们把我们的例子改为一些less一点的构造,究竟是什么让我发布这个问题:
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()
我们现在得到ABA
。 在这里,我不能使UIView的layoutSubviewsdynamic的任何手段。
把这两个覆盖到他们的类声明再次得到我们AAA
,只有A或只有B仍然得到我们的ABA
。 dynamic
再次解决了我的问题。
从理论上讲,我可以为所有被override
的动作添加dynamic
,但我觉得我在这里做了一些其他的错误。
像我这样使用extension
来分组代码真的是错误的吗?
扩展不能/不应该重写。
苹果Swift指南中记载的扩展function(如属性或方法)是不可能的。
扩展可以向types添加新的function,但是它们不能覆盖现有的function。
苹果开发者指南
编译器允许您在扩展中重写与Objective-C的兼容性。 但是这实际上违反了语言指令。
这让我想起了艾萨克·阿西莫夫的“ 机器人三定律 ”🤖
扩展( 语法糖 )定义了接收自己的参数的独立方法。 被调用的函数,即layoutSubviews
取决于编译器知道何时编译代码的上下文。 UIView从inheritance自NSObject的UIResponderinheritance, 所以扩展中的覆盖是允许的,但不应该是 。
所以分组没有任何问题,但是你应该在类中重写,而不是在扩展中。
指令说明
如果方法与Objective-C兼容,则只能override
超类方法,即在子类的扩展中load()
initialize()
。
因此,我们可以看看为什么它允许您使用layoutSubviews
进行编译。
所有Swift应用程序都在Objective-C运行时内执行,除了使用纯Swift框架时,Swift框架允许Swift-only运行时。
当我们发现Objective-C运行时通常会在初始化应用程序进程中的类时自动调用两个类的main方法load()
和initialize()
。
关于dynamic
修饰符
从iOS开发人员库
您可以使用dynamic
修饰符来要求通过Objective-C运行时dynamic分配对成员的访问。
当Swift API由Objective-C运行时导入时,不能保证dynamic分配属性,方法,下标或初始值设定项。 Swift编译器仍然可以通过虚拟化或内联成员访问来绕过Objective-C运行时来优化代码的性能。 😳
所以dynamic
可以应用到你的layoutSubviews
– > UIView Class
因为它是由Objective-C表示的,并且对这个成员的访问总是使用Objective-C运行时使用的。
这就是为什么编译器允许你使用override
和dynamic
。
Swift的目标之一是静态调度,或者说减lessdynamic调度。 Obj-C然而是一个非常dynamic的语言。 你所看到的情况是由两种语言之间的联系和他们一起工作的方式共同承担的。 它不应该真的编译。
关于扩展的一个要点是扩展,而不是replace/重写。 从名称和文档中可以明显看出这是意图。 事实上,如果你从你的代码中取出到Obj-C的链接(将NSObject
作为超类删除),它将不能编译。
所以,编译器正在试图决定它可以静态分派什么以及它需要dynamic分配什么,而且由于代码中的Obj-C链接,它正在缩小。 dynamic
“工作”的原因是因为它迫使Obj-C链接到一切,所以它总是dynamic的。
所以,使用扩展进行分组并不错,这很好,但是在扩展中重写是错误的。 任何重写都应该放在主类本身,并呼叫扩展点。
有一种方法可以实现类签名和实现的完全分离(在扩展中),同时保持在子类中覆盖的能力。 诀窍是使用variables来代替函数
如果确保在一个单独的swift源文件中定义每个子类,则可以使用计算variables进行覆盖,同时将相应的实现整理为扩展名。 这将绕开Swift的“规则”,并将您的class级的API /签名整齐地组织在一个地方:
// ---------- 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))" } }
每个类的扩展都可以使用相同的方法名称来实现,因为它们是私有的,而且彼此不可见(只要它们在单独的文件中)。
正如你可以看到inheritance(使用variables名)使用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"
这个答案不是针对OP的,除了我觉得受到启发的这个事实,他的话是这样的:“我倾向于只把必需品(存储的属性,初始化符)放到我的类定义中,并把其他所有东西都放到它们自己的扩展中。 ..“。 我主要是一个C#程序员,在C#中可以使用部分类为此目的。 例如,Visual Studio使用部分类将与UI相关的东西放在一个单独的源文件中,并且保持主源文件整洁,所以不会分散注意力。
如果你search“swift partial class”,你会发现各种链接,Swift的拥护者说Swift不需要部分类,因为你可以使用扩展。 有趣的是,如果您在Googlesearch字段中input“swift extension”,则其第一个searchbuild议是“swift extension override”,此时此堆栈溢出问题是第一个命中。 我认为这意味着(缺less)覆盖能力的问题是与Swift扩展相关的search最多的话题,并强调了Swift扩展不可能replace部分类的事实,至less如果你在你的类中使用派生类节目。
无论如何,为了减less冗长的介绍,我遇到了这个问题,我想从我的C#到Swift程序生成的Swift类的主要源文件中移除一些样板/行李方法。 在将这些方法移动到扩展后遇到不允许覆盖的问题后,我最终实现了以下简单的解决方法。 主要的Swift源文件仍然包含一些微小的存根方法,它们在扩展文件中调用真正的方法,并且这些扩展方法被赋予唯一的名称以避免覆盖问题。
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) } ) } }
就像我在我的介绍中所说的那样,这并不能真正回答OP的问题,但我希望这种简单的解决方法可能对其他人希望将方法从主源文件移动到扩展文件并运行到no覆盖问题。