Swift扩展中的重写方法


133

我倾向于只将必需品(存储的属性,初始化器)放入我的类定义中,然后将其他所有内容移动到它们自己的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()

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

如果我从中删除继承NSObject并创建C根类,则编译器会抱怨说declarations in extensions cannot override yet,这是我已经读过的。但是NSObject,以根类为基础如何改变事物呢?

将这两个重写都A A A按预期的那样移入类声明产生器,仅移动B's产生器A B B,仅移动A's产生器C B C,对我来说,最后一个绝对是没有意义的:即使是静态类型化A产生A-output的输出也不再!

dynamic关键字添加到定义或覆盖似乎确实给了我“从类层次结构中的那个点向下”的期望行为。

让我们将示例更改为稍微有些结构化的东西,实际上是什么使我发布了这个问题:

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动态化。

将这两个重写都移到类声明中会使我们A A A再次获得成功,只有A或B仍会获得我们A B Adynamic再次解决了我的问题。

从理论上讲,我可以添加我曾经做过的dynamic所有override事情,但是我觉得自己在这里做错了其他事情。

extension像我一样使用s对代码进行分组真的错误吗?


这种方式使用扩展名是Swift的约定。甚至苹果公司也在标准库中做到这一点。
亚历山大-恢复莫妮卡


1
@AMomchilov您链接的文档谈论协议,我遗漏了什么吗?
Christian Schnorr

我怀疑这是对两者都起作用的相同机制
亚历山大-恢复莫妮卡

3
似乎对子类扩展中的重写方法重复了Swift调度。马特的回答是,这是一个错误(他引用了文档来证明这一点)。
jscs

Answers:


229

扩展不能/不应覆盖。

如Apple的Swift指南中所述,无法覆盖扩展中的功能(如属性或方法)。

扩展可以为类型添加新功能,但不能覆盖现有功能。

Swift开发人员指南

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

just这让我想起了艾萨克·阿西莫夫(Isaac Asimov)的“ 机器人三定律 ”🤖

扩展(语法糖)定义了接收自己参数的独立方法。需要调用的函数,即layoutSubviews取决于编译器知道何时编译代码的上下文。UIView继承自UIResponder,而UIResponder继承自NSObject,因此扩展名中的允许替代,但不允许这样做

因此,分组没有错,但是您应该在类中而不在扩展中覆盖。

指令说明

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

因此,我们可以了解一下为什么它允许您使用进行编译layoutSubviews

除了使用纯Swift框架(允许纯Swift运行时)外,所有Swift应用都在Objective-C运行时内执行。

我们发现,Objective-C运行时通常会调用两个类的main方法,load()initialize()在初始化应用程序进程中的类时自动调用它们。

关于dynamic修饰符

Apple开发人员库 (archive.org)

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

通过Objective-C运行时导入Swift API时,不能保证动态分配属性,方法,下标或初始化程序。Swift编译器仍然可以绕过Objective-C运行时来取消虚拟化或内联成员访问以优化代码的性能。😳

因此,dynamic可以将其应用于layoutSubviews->,UIView Class因为它由Objective-C表示,并且始终使用Objective-C运行时来访问该成员。

这就是为什么编译器允许您使用override和的原因dynamic


6
扩展不能仅覆盖类中定义的方法。它可以覆盖父类中定义的方法。
RJE

-Swift3-Well,这很奇怪,因为您还可以从所包含的框架中覆盖(并且这里所说的是Swizzling之类的)方法。即使这些框架是纯快速编写的。...也许框架也受objc约束,这就是为什么🤔–
farzadshbfn

@tymac我不明白。如果Objective-C运行时需要一些东西以实现Objective-C兼容性,为什么Swift编译器仍然允许覆盖扩展?将Swift扩展中的覆盖标记为语法错误如何损害Objective-C运行时?
亚历山大·瓦塞宁

1
如此令人沮丧,所以基本上,当您想使用项目中已经存在的代码来创建框架时,您将必须
继承

3
@AuRis您有参考吗?
ricardopereira 18'Apr

18

Swift的目标之一是静态调度,或者说减少动态调度。然而,Obj-C是一种非常动态的语言。您所看到的情况来自两种语言之间的联系以及它们的协同工作方式。它不应该真正编译。

关于扩展的主要要点之一是它们是用于扩展,而不是用于替换/覆盖。从名称和文档中都可以清楚地看出这是意图。的确,如果您从代码中删除到Obj-C的链接(NSObject作为超类删除),它将不会编译。

因此,编译器试图确定它可以静态分配的内容以及必须动态分配的内容,并且由于代码中的Obj-C链接而陷入了空白。之所以dynamic起作用,是因为它迫使所有对象都进行Obj-C链接,因此它始终是动态的。

因此,使用扩展名进行分组并没有错,这很好,但是在扩展名中覆盖是错误的。任何替代都应在主类本身中,并调出扩展点。


这也适用于变量吗?举例来说,如果要覆盖supportedInterfaceOrientationsUINavigationController(用于显示在不同方向不同意见的目的),你应该使用自定义类不是一个扩展?许多答案建议使用扩展名覆盖,supportedInterfaceOrientations但希望澄清。谢谢!
Crashalot

10

有一种方法可以使类签名和实现(在扩展中)完全分离,同时保持在子类中具有覆盖的能力。诀窍是使用变量代替函数

如果确保在单独的swift源文件中定义每个子类,则可以将计算变量用于覆盖,同时在扩展中将相应的实现保持整洁的组织。这将绕过Swift的“规则”,并使您的类的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))" 
   }
}

每个类的扩展都可以对实现使用相同的方法名称,因为它们是私有的,并且彼此之间不可见(只要它们在单独的文件中)。

如您所见,使用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"

2
我猜想这对我自己的方法有效,但是在类中覆盖System Framework方法时则无效。
Christian Schnorr

这使我为属性包装器条件协议扩展找到了正确的道路。谢谢!
克里斯·普林斯

1

这个答案不是针对OP,而是令我感到鼓舞的是他的回答:“我倾向于只将必需品(存储的属性,初始值设定项)放入我的类定义中,并将其他所有内容移入它们自己的扩展中。” ..”。我主要是C#程序员,在C#中,可以为此使用部分类。例如,Visual Studio使用局部类将与UI相关的内容放置在单独的源文件中,并使主源文件保持整洁,以免分散您的注意力。

如果搜索“ swift局部类”,您会找到各种链接,其中Swift信徒说Swift不需要局部类,因为您可以使用扩展。有趣的是,如果您在Google搜索字段中输入“ swift extension”,则其第一个搜索建议是“ swift extension Override”,此刻,此“堆栈溢出”问题是第一个匹配项。我的意思是,(缺乏)覆盖功能的问题是与Swift扩展相关的搜索量最大的话题,并强调了一个事实,即至少在您使用派生类的情况下,Swift扩展可能无法替换部分类。编程。

无论如何,为了简化冗长的介绍,我遇到了这个问题,我想将一些样板/行李方法从我的C#-to-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的其他人有所帮助。 -覆盖问题。


1

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

1
这假定程序员可以控制AClass的原始定义,使其可以依赖AProtocol。在要覆盖AClass中的功能的情况下,通常情况并非如此(即AClass可能是Apple提供的标准库类)。
乔纳森·伦纳德

请注意,如果您不想或无法修改类的原始定义,则可以(在某些情况下)将协议应用于扩展或子类。
shim
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.