覆盖Swift扩展中的方法

时间:2016-07-05 21:44:05

标签: swift swift2 swift-extensions

我倾向于只将必需品(存储属性,初始值设定项)放入我的类定义中,并将其他所有内容移动到自己的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生成A B B,仅移动A生成C B C ,最后一个对我来说完全没有意义:即使是A静态输入的那个也不再产生A - 输出!

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对我这样的代码进行分组是否真的错了?

6 个答案:

答案 0 :(得分:178)

扩展程序不能/不应该覆盖。

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

  

扩展程序可以为类型添加新功能,但它们无法覆盖现有功能。

Apple Developer Guide

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

这让我想起了Isaac Asimov" Three Laws of Robotics"

扩展( syntactic sugar )定义了接收自己参数的独立方法。调用的函数即layoutSubviews取决于编译器在编译代码时所知道的上下文。 UIView继承自UIResponder,它继承自NSObject ,因此允许扩展中的覆盖,但不应该

所以分组没什么问题,但是你应该在类中覆盖而不是在扩展名中。

指令备注

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

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

所有Swift应用程序都在Objective-C运行时内执行,除了使用纯Swift-only框架时允许仅Swift运行时。

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

关于dynamic修饰符

来自iOS Developer Library

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

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

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

这就是编译器允许您使用overridedynamic的原因。

答案 1 :(得分:16)

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

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

因此,编译器正在尝试确定它可以静态分派什么以及它有什么动态调度,并且由于代码中的Obj-C链接而导致它陷入差距。 dynamic'工作原因'是因为它强迫Obj-C连接所有东西,所以它总是动态的。

因此,使用扩展程序进行分组并不是错误的,这很好,但是在扩展程序中覆盖它是错误的。任何覆盖应该在主类本身,并呼叫扩展点。

答案 2 :(得分:6)

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

如果确保在单独的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"

答案 3 :(得分:1)

这个答案不是以OP为目标,除了我感到受到鼓舞以回应他的陈述的事实,"我倾向于只将必需品(存储属性,初始化器)放入我的类定义中并移动所有内容否则进入他们自己的扩展..."。我主要是一个C#程序员,在C#中,我可以使用部分类来达到这个目的。例如,Visual Studio使用部分类将与UI相关的内容放在单独的源文件中,并使您的主源文件保持整洁,这样您就不会分心。

如果您搜索" swift partial class"你会发现各种链接,其中Swift的拥护者说Swift不需要部分类,因为你可以使用扩展。有趣的是,如果你键入" swift extension"进入谷歌搜索领域,它的第一个搜索建议是" swift扩展覆盖",目前这个Stack Overflow问题是第一次点击。我认为这意味着(缺少)覆盖功能的问题是与Swift扩展相关的搜索最多的主题,并强调了Swift扩展可能无法替换部分类的事实,至少如果您使用派生类在你的编程中。

无论如何,为了缩短冗长的介绍简介,我遇到了这个问题,我想把一些样板/行李方法移出Swift类的主要源文件,我的C#-to-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的问题,但我希望这种简单易懂的解决方法可能对其他希望从主要源文件到扩展文件并遇到无覆盖问题。

答案 4 :(得分: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()

答案 5 :(得分:0)

仅想补充一点,对于Objective-C类,两个单独的类别最终可能会覆盖相同的方法,在这种情况下……嗯……可能会发生意外的事情。

如Apple here所述,Objective-C运行时不保证将使用哪个扩展名:

如果在一个类别中声明的方法的名称与原始类中的方法的名称相同,或者在同一类(甚至是超类)的另一个类别中的方法的名称相同,则该行为对于哪个方法的实现未定义在运行时使用。如果您在自己的类中使用类别,则不太可能出现问题,但是在使用类别向标准Cocoa或Cocoa Touch类中添加方法时,可能会引起问题。

对于纯Swift类,Swift禁止这样做是一件好事,因为这种过度动态的行为可能导致难以检测和调查错误。