从 NSObject 继承时,不会调用泛型中使用的 swift 子类

标签 swift generics inheritance

最后更新部分解决方案!

附件是产生奇怪行为的代码。我把它从 Swift 的 Playground 中复制出来,所以它应该可以一次正常运行。

我在项目中创建了一个子类,并将其作为具体类型传递给我的泛型类。然而,我很快注意到只调用了基类方法。这显示为 myBasemySub以下。尽管泛型类被实例化为 <mySub> ,仅调用基本方法。子类的打印行永远不会显示。

嗯,我找到了一个简单的方法来解决这个问题,那就是不继承 NSObject。当我使用 swift 原生类时,实际上调用了子类方法。它们是 secondaryBase 和 secondarySub。

从 NSObject 继承时,如何将子类传递给泛型类并让实际的子类接收调用?

为什么行为会有所不同?

import Foundation

// The Protocol
protocol P {
    init ()
    func doWork() -> String
}

// Generic Class
class G<T: P> {
    func doThing() -> String {
        let thing = T()
        return thing.doWork()
    }
}

// NSObject Base Class with Protocol
class A1: NSObject, P {
    override required init() {
        super.init()
    }

    func doWork() -> String {
        return "A1"
    }
}

// NSObject Sub Class
class B1: A1 {
    required init() {
        super.init()
    }

    override func doWork() -> String {
        return "B1"
    }
}

// Swift Base Class
class A2: P {
    required init() {
    }

    func doWork() -> String {
        return "A2"
    }
}

// Swift Sub Class
class B2: A2 {
    required init() {
        super.init()
    }

    override func doWork() -> String {
        return "B2"
    }
}

print ("Sub class failure with NSObject")

print ("Recieved: " + G<B1>().doThing() + " Expected: B1 - NSObject Sub Class Generic (FAILS)")
print ("\nSub class success with Swift Native")

print ("Recieved: " + G<B2>().doThing() + " Expected: B2 - Swift Sub Class Generic (SUCCEEDS)")
print("")


#if swift(>=5.0)
print("Hello, Swift 5.0")
#elseif swift(>=4.1)
print("Hello, Swift 4.1")
#elseif swift(>=4.0)
print("Hello, Swift 4.0")
#elseif swift(>=3.0)
print("Hello, Swift 3.x")
#else
print("Hello, Swift 2.2")
#endif

输出:

Sub class failure with NSObject
Recieved: A1 Expected: B1 - NSObject Sub Class Generic (FAILS)

Sub class success with Swift Native
Recieved: B2 Expected: B2 - Swift Sub Class Generic (SUCCEEDS)

Hello, Swift 5.0

部分解决方案更新:

将协议(protocol)一致性从基类移至子类可以使子类正常运行。定义变为:

class A1: NSObject
class B1: A1, P

问题是当不需要基类之外的功能时,根本无法再直接使用基类。如果所遵循的协议(protocol)具有关联的类型,这主要是一个问题。如果这是真的,您必须有一个符合在泛型中使用的协议(protocol)的具体类。

这里的一个用例是期望泛型中有一个基类(具有涉及关联类型的协议(protocol)),它允许某些东西运行而无需关心传入的实际子类。这实际上最终成为一种穷人的类型形式在某些情况下会被删除。您仍然可以对子类使用相同的泛型。

G<A1>()
G<B1>()

这源自这里的类似问题:Generic Class does not forward delegate calls to concrete subclass

部分选项是:

  1. 删除 NSObject 并仅使用 swift 原生类
  2. 当需要 NSObject 时,尝试将协议(protocol)一致性与 NSObject 的继承分开

更新以下想法:行不通

I am going to test if providing an additional layer changes the behavior. Basically have 3 layers, Base class inheriting from NSObject, base Protocol class adding the protocol but inheriting from base and then specific classes. If it can distinguish between the base protocol class and the specific subclass in that case, that would be a functional workaround in all use cases. (and may explain why Apple's NSManagedObject works fine)

不过看起来仍然是一个错误。

最佳答案

我能够确认您的结果并将其作为错误提交,https://bugs.swift.org/browse/SR-10617 。原来这是一个已知问题!我被告知(由好心的老哈米什)我正在复制https://bugs.swift.org/browse/SR-10285 .

在我提交的错误中,我对您的示例进行了干净紧凑的简化,适合发送给 Apple:

protocol P {
    init()
    func doThing()
}

class Wrapper<T:P> {
    func go() {
        T().doThing()
    }
}

class A : NSObject, P {
    required override init() {}
    func doThing() {
        print("A")
    }
}

class B : A {
    required override init() {}
    override func doThing() {
        print("B")
    }
}

Wrapper<B>().go()

在 Xcode 9.2 上,我们得到“B”。在 Xcode 10.2 上,我们得到“A”。仅此一点就足以保证错误报告。

在我的报告中,我列出了解决该问题的三种方法,所有这些方法都确认这是一个错误(因为它们应该都不会有任何区别):

  • 将泛型参数化类型的约束设置为 A 而不是 P

  • 或者,将协议(protocol) P 标记为 @objc

  • 或者,没有 A 继承自 NSObject


更新:事实证明(来自苹果自己的release notes)还有另一种方法:

  • 将 A 的 init 标记为 @nonobjc

关于从 NSObject 继承时,不会调用泛型中使用的 swift 子类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55945140/

相关文章:

Swift:候选人的类型不匹配

swift - 你能在 Swift 中为 UIRefreshControl 创建自定义 View 吗?

c# - 使用泛型转换为基类

java - 您能否指出 JLS 部分,其中指定继承的方法不会作用于子类重新定义的变量?

ios - NSCache 在 Swift 中为 UITableView 存储图像

ios - swift - 如何在导航 Controller 中制作尺寸高度 View ?

java - 如何在 Java 中创建泛型方法?

c# - 如何使用反射返回从泛型子类化的所有类,而不给出特定的泛型类型

java - JQL ORDER BY 子句和继承

c++ - 为什么编译器在这种情况下会选择不正确的函数重载?