覆盖 Swift 扩展中的方法
Overriding methods in Swift extensions
我倾向于只将必需品(存储的属性、初始值设定项)放入我的 class 定义中,并将其他所有内容移动到它们自己的 extension
中,有点像每个逻辑的 extension
阻止我也将与 // MARK:
分组。
例如,对于 UIView 子class,我最终会得到一个与布局相关的扩展,一个用于订阅和处理事件等等。在这些扩展中,我不可避免地要重写一些 UIKit 方法,例如layoutSubviews
。直到今天我才发现这种方法有任何问题。
以这个class层级为例:
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()
输出为A B C
。这对我来说毫无意义。我读到有关静态分发协议扩展的信息,但这不是协议。这是一个常规 class,我希望在运行时动态调度方法调用。显然,对 C
的调用至少应该动态调度并产生 C
?
如果我从 NSObject
中删除继承并使 C
成为根 class,编译器会抱怨说 declarations in extensions cannot override yet
,我已经阅读过。但是,将 NSObject
作为根 class 会如何改变呢?
将两个覆盖移动到它们的 class 声明中会按预期生成 A A A
,仅移动 B
会生成 A B B
,仅移动 A
产生 C B C
,最后一个对我来说完全没有意义:即使是静态输入 A
的那个也不再产生 A
输出!
将 dynamic
关键字添加到定义或覆盖中似乎确实给了我想要的行为 'from that point in the class hierarchy downwards'...
让我们将我们的示例更改为结构稍微简单一些的东西,实际上是什么让我 post 这个问题:
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()
我们现在得到 A B A
。在这里,我无法通过任何方式使 UIView 的 layoutSubviews 动态化。
将两个覆盖移动到它们的 class 声明中再次得到我们 A A A
,只有 A 或只有 B 仍然得到我们 A B A
。 dynamic
又解决了我的问题
理论上我可以将 dynamic
添加到我曾经做过的所有 override
中,但我觉得我在这里做错了什么。
像我一样使用extension
来分组代码真的是错误的吗?
Swift 的目标之一是静态调度,或者说减少动态调度。然而,Obj-C 是一种非常动态的语言。您所看到的情况是由两种语言之间的 link 以及它们协同工作的方式所证实的。它不应该真正编译。
关于扩展的要点之一是它们用于扩展,而不是用于替换/覆盖。从名称和文档中都可以清楚地看出这是意图。事实上,如果你从你的代码中取出 link 到 Obj-C(删除 NSObject
作为超级 class)它不会编译。
因此,编译器正在尝试决定它可以静态分派什么以及它必须动态分派什么,并且由于您代码中的 Obj-C link 它正在陷入困境。 dynamic
'works' 的原因是因为它强制 Obj-C linking 在所有东西上所以它总是动态的。
因此,使用扩展进行分组并没有错,这很好,但是在扩展中覆盖是错误的。任何覆盖都应该在主 class 本身中,并调用扩展点。
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.
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 Apple Developer Library (archive.org)
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
.
有一种方法可以实现 class 签名和实现(在扩展中)的完全分离,同时保持在 subclasses 中进行覆盖的能力。诀窍是使用变量代替函数
如果您确保在单独的 swift 源文件中定义每个子 class,您可以使用计算变量进行覆盖,同时保持相应的实现在扩展中清晰地组织。这将绕过 Swift 的 "rules" 并将您的 class 的 API/signature 整齐地组织在一个地方:
// ---------- 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))"
}
}
每个 class 的扩展都能够使用相同的方法名称来实现,因为它们是私有的并且彼此不可见(只要它们在单独的文件中)。
如您所见,继承(使用变量名)使用 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,除了我受到他的陈述 "I tend to only put the necessities (stored properties, initializers) into my class definitions and move everything else into their own extension ..." 的启发而做出回应这一事实。我主要是一名 C# 程序员,在 C# 中,可以为此目的使用部分 classes。例如,Visual Studio 使用部分 class 将 UI-related 内容放在单独的源文件中,并使您的主要源文件保持整洁,因此您不会分心。
如果您搜索 "swift partial class",您会发现各种链接,其中 Swift 拥护者说 Swift 不需要部分 classes 因为您可以使用扩展.有趣的是,如果您在 Google 搜索字段中键入 "swift extension",它的第一个搜索建议是 "swift extension override",目前这个 Stack Overflow 问题是第一个命中。我认为这意味着(缺乏)覆盖功能的问题是与 Swift 扩展相关的最 searched-for 主题,并强调 Swift 扩展不可能取代部分 classes,至少如果您在编程中使用派生的 classes。
无论如何,为了缩短 long-winded 介绍,我 运行 在我想将一些样板/行李方法从 [=45 的主要源文件中移出的情况下解决了这个问题=] class是我的 C#-to-Swift 程序生成的。在 运行 解决了将这些方法移至扩展后不允许覆盖这些方法的问题后,我最终实施了以下 simple-minded 解决方法。主要 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 的问题,但我希望这个 simple-minded 解决方法可能对其他希望将方法从主要源文件移动到扩展文件和 运行 进入 no-override 问题。
使用 POP(面向协议的编程)覆盖扩展中的函数。
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()
只是想为 Objective-C 类 添加一点,两个不同的类别最终可能会覆盖相同的方法,在这种情况下......好吧......可能会发生意想不到的事情。
Objective-C 运行时不保证将使用哪个扩展,如 Apple here 所述:
If the name of a method declared in a category is the same as a method in the original class, or a method in another category on the same class (or even a superclass), the behavior is undefined as to which method implementation is used at runtime. This is less likely to be an issue if you’re using categories with your own classes, but can cause problems when using categories to add methods to standard Cocoa or Cocoa Touch classes.
对于纯 Swift 类,Swift 禁止这样做是件好事,因为这种过度动态的行为是难以检测和调查错误的潜在来源。
我倾向于只将必需品(存储的属性、初始值设定项)放入我的 class 定义中,并将其他所有内容移动到它们自己的 extension
中,有点像每个逻辑的 extension
阻止我也将与 // MARK:
分组。
例如,对于 UIView 子class,我最终会得到一个与布局相关的扩展,一个用于订阅和处理事件等等。在这些扩展中,我不可避免地要重写一些 UIKit 方法,例如layoutSubviews
。直到今天我才发现这种方法有任何问题。
以这个class层级为例:
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()
输出为A B C
。这对我来说毫无意义。我读到有关静态分发协议扩展的信息,但这不是协议。这是一个常规 class,我希望在运行时动态调度方法调用。显然,对 C
的调用至少应该动态调度并产生 C
?
如果我从 NSObject
中删除继承并使 C
成为根 class,编译器会抱怨说 declarations in extensions cannot override yet
,我已经阅读过。但是,将 NSObject
作为根 class 会如何改变呢?
将两个覆盖移动到它们的 class 声明中会按预期生成 A A A
,仅移动 B
会生成 A B B
,仅移动 A
产生 C B C
,最后一个对我来说完全没有意义:即使是静态输入 A
的那个也不再产生 A
输出!
将 dynamic
关键字添加到定义或覆盖中似乎确实给了我想要的行为 'from that point in the class hierarchy downwards'...
让我们将我们的示例更改为结构稍微简单一些的东西,实际上是什么让我 post 这个问题:
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()
我们现在得到 A B A
。在这里,我无法通过任何方式使 UIView 的 layoutSubviews 动态化。
将两个覆盖移动到它们的 class 声明中再次得到我们 A A A
,只有 A 或只有 B 仍然得到我们 A B A
。 dynamic
又解决了我的问题
理论上我可以将 dynamic
添加到我曾经做过的所有 override
中,但我觉得我在这里做错了什么。
像我一样使用extension
来分组代码真的是错误的吗?
Swift 的目标之一是静态调度,或者说减少动态调度。然而,Obj-C 是一种非常动态的语言。您所看到的情况是由两种语言之间的 link 以及它们协同工作的方式所证实的。它不应该真正编译。
关于扩展的要点之一是它们用于扩展,而不是用于替换/覆盖。从名称和文档中都可以清楚地看出这是意图。事实上,如果你从你的代码中取出 link 到 Obj-C(删除 NSObject
作为超级 class)它不会编译。
因此,编译器正在尝试决定它可以静态分派什么以及它必须动态分派什么,并且由于您代码中的 Obj-C link 它正在陷入困境。 dynamic
'works' 的原因是因为它强制 Obj-C linking 在所有东西上所以它总是动态的。
因此,使用扩展进行分组并没有错,这很好,但是在扩展中覆盖是错误的。任何覆盖都应该在主 class 本身中,并调用扩展点。
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.
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 Apple Developer Library (archive.org)
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
.
有一种方法可以实现 class 签名和实现(在扩展中)的完全分离,同时保持在 subclasses 中进行覆盖的能力。诀窍是使用变量代替函数
如果您确保在单独的 swift 源文件中定义每个子 class,您可以使用计算变量进行覆盖,同时保持相应的实现在扩展中清晰地组织。这将绕过 Swift 的 "rules" 并将您的 class 的 API/signature 整齐地组织在一个地方:
// ---------- 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))"
}
}
每个 class 的扩展都能够使用相同的方法名称来实现,因为它们是私有的并且彼此不可见(只要它们在单独的文件中)。
如您所见,继承(使用变量名)使用 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,除了我受到他的陈述 "I tend to only put the necessities (stored properties, initializers) into my class definitions and move everything else into their own extension ..." 的启发而做出回应这一事实。我主要是一名 C# 程序员,在 C# 中,可以为此目的使用部分 classes。例如,Visual Studio 使用部分 class 将 UI-related 内容放在单独的源文件中,并使您的主要源文件保持整洁,因此您不会分心。
如果您搜索 "swift partial class",您会发现各种链接,其中 Swift 拥护者说 Swift 不需要部分 classes 因为您可以使用扩展.有趣的是,如果您在 Google 搜索字段中键入 "swift extension",它的第一个搜索建议是 "swift extension override",目前这个 Stack Overflow 问题是第一个命中。我认为这意味着(缺乏)覆盖功能的问题是与 Swift 扩展相关的最 searched-for 主题,并强调 Swift 扩展不可能取代部分 classes,至少如果您在编程中使用派生的 classes。
无论如何,为了缩短 long-winded 介绍,我 运行 在我想将一些样板/行李方法从 [=45 的主要源文件中移出的情况下解决了这个问题=] class是我的 C#-to-Swift 程序生成的。在 运行 解决了将这些方法移至扩展后不允许覆盖这些方法的问题后,我最终实施了以下 simple-minded 解决方法。主要 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 的问题,但我希望这个 simple-minded 解决方法可能对其他希望将方法从主要源文件移动到扩展文件和 运行 进入 no-override 问题。
使用 POP(面向协议的编程)覆盖扩展中的函数。
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()
只是想为 Objective-C 类 添加一点,两个不同的类别最终可能会覆盖相同的方法,在这种情况下......好吧......可能会发生意想不到的事情。
Objective-C 运行时不保证将使用哪个扩展,如 Apple here 所述:
If the name of a method declared in a category is the same as a method in the original class, or a method in another category on the same class (or even a superclass), the behavior is undefined as to which method implementation is used at runtime. This is less likely to be an issue if you’re using categories with your own classes, but can cause problems when using categories to add methods to standard Cocoa or Cocoa Touch classes.
对于纯 Swift 类,Swift 禁止这样做是件好事,因为这种过度动态的行为是难以检测和调查错误的潜在来源。