关于swift2:覆盖Swift扩展中的方法

Overriding methods in Swift extensions

我倾向于将必需品(存储属性、初始值设定项)放入类定义中,并将其他所有东西移动到它们自己的extension,类似于每个逻辑块的extension,我也会将其与// MARK:分组。

例如,对于一个uiview子类,我最终会得到一个布局相关内容的扩展,一个用于订阅和处理事件等。在这些扩展中,我不可避免地必须重写一些uikit方法,例如layoutSubviews。直到今天,我才注意到这种方法有任何问题。

以这个类层次结构为例:

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

输出为A B C。这对我来说毫无意义。我读过静态调度协议扩展,但这不是协议。这是一个常规类,我希望在运行时动态调度方法调用。显然,对C的调用至少应该动态调度并生成C

如果我从NSObject中删除继承并使C成为根类,编译器会抱怨说declarations in extensions cannot override yet,我已经读过了。但是,把NSObject作为根类是如何改变事情的呢?

将两个覆盖移动到它们的类声明中会按预期生成A A A,仅移动B会生成A B B,仅移动A会生成C B C,最后一个对我来说完全没有意义:即使是静态类型的A也不会再生成A输出!

dynamic关键字添加到定义或重写中似乎会给我提供所需的行为"从类层次结构中的那个点向下"…

让我们把我们的例子改成一个结构稍差的例子,是什么让我提出了这个问题:

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

我们现在得到了A B A。在这里,我不能以任何方式使uiview的layoutSubview动态。

将这两个覆盖移动到它们的类声明中会再次得到我们的A A A,只有A或B仍然得到我们的A B Adynamic再次解决了我的问题。

理论上,我可以把dynamic加到所有override中去,但我觉得我在这里做了其他的错误。

像我一样使用EDOCX1[0]对代码进行分组真的是错误的吗?


扩展不能/不应该重写。

如苹果的Swift指南所述,不可能覆盖扩展中的功能(如属性或方法)。

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

苹果开发者指南

编译器允许您在扩展中重写以与Objective-C兼容。但它实际上违反了语言指令。

?这让我想起艾萨克·阿西莫夫的"机器人三定律"??

扩展(语法糖)定义接收自己参数的独立方法。调用的函数,即layoutSubviews取决于编译器在编译代码时知道的上下文。uiview继承自uiresponder,uiresponder继承自nsObject,因此扩展中的重写是允许的,但不应允许。

所以分组没有什么问题,但是您应该在类中重写,而不是在扩展中重写。

指示注释

如果方法与Objective-C兼容,则在子类的扩展中只能使用override超类方法,即load()initialize()

因此,我们可以看看为什么它允许您使用layoutSubviews进行编译。

所有Swift应用程序都在Objective-C运行时内执行,但使用纯Swift-Only框架时除外,该框架只允许使用Swift-Only运行时。

我们发现,在应用程序进程中初始化类时,objective-c运行时通常会自动调用两个类主方法load()initialize()

关于dynamic修饰语

来自iOS开发者库

可以使用dynamic修饰符要求通过Objective-C运行时动态调度对成员的访问。

当由Objective-C运行时导入Swift API时,无法保证对属性、方法、下标或初始值设定项进行动态调度。Swift编译器仍然可以取消或内联成员访问以优化代码的性能,从而绕过Objective-C运行时。???

因此,dynamic可以应用于layoutSubviews->UIView Class,因为它由objective-c表示,对该成员的访问总是使用objective-c运行时。

这就是为什么编译器允许您使用overridedynamic


Swift的目标之一是静态调度,或者更确切地说是减少动态调度。但是obj-c是一种非常动态的语言。你所看到的情况是由这两种语言和它们一起工作的方式之间的联系所证实的。它不应该真正编译。

关于扩展的一个要点是它们是用于扩展的,而不是用于替换/重写。从名字和文档中都可以清楚地看出,这就是目的。实际上,如果从代码中去掉到obj-c的链接(删除NSObject作为超类),它就不会编译。

因此,编译器正试图决定它可以静态地调度什么,必须动态地调度什么,并且由于代码中的obj-c链接,它正在经历一个缺口。dynamic之所以"有效",是因为它迫使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的问题,但我希望这种简单的解决方法可能对那些希望将方法从主源文件移动到扩展文件并遇到无覆盖问题的人有所帮助。