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

时间:2019-05-02 03:01:05

标签: swift generics inheritance

部分解决方案更新最后!

附加的代码会产生奇怪的行为。我将其复制到一个快速的操场上,因此可以很好地运行。

我在项目中创建了一个子类,并将其作为具体类型传递给我的通用类。但是,我很快注意到仅调用了基类方法。下方显示为myBasemySub。尽管将通用类实例化为<mySub>,但仅调用了基本方法。子类的打印行从不显示。

好吧,我发现了一种简单的解决方法,那就是不继承自NSObject。当我使用快速本机类时,实际上会调用子类方法。这些是secondBase和secondSub。

当从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

部分解决方案更新

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

class A1: NSObject
class B1: A1, P

问题是,当不需要除此以外的功能时,根本不能再直接使用基类。如果要遵循的协议具有关联的类型,这主要是一个问题。如果是这样,您必须有一个具体的类,该类符合用于泛型的协议。

这里的一个用例是期望泛型中的基类(协议涉及关联的类型),该基类允许某些功能起作用,而不必关心传入的实际子类是什么。这实际上是穷人的类型在某些情况下删除。而且,您仍然可以将相同的泛型与子类一起使用。

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

这是从这里的一个类似问题得出的:Generic Class does not forward delegate calls to concrete subclass

部分选项是:

  1. 删除NSObject并仅使用Swift本机类
  2. 需要NSObject时,请尝试将协议一致性与NSObject的继承性分开

更新以下想法:不起作用

  

我将测试是否提供额外的图层可以改变行为。基本上有3层,从NSObject继承的基类,从协议继承但从基类和 then 特定类继承的基本Protocol类。如果在这种情况下可以区分基本协议类和特定子类,那么这将是所有用例的功能解决方法。 (并且可以解释为什么苹果的NSManagedObject可以正常工作)

尽管如此,它似乎仍然是一个错误。

3 个答案:

答案 0 :(得分:1)

我能够确认您的结果并将其作为错误https://bugs.swift.org/browse/SR-10617提交。原来这是一个已知问题!很好的老哈米什(Hamish)告诉我,我正在复制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

  • ,或将协议P标记为@objc

  • 或者,没有从NSObject继承的


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

  • 将A的init标记为@nonobjc

答案 1 :(得分:0)

这不仅仅是解决问题的一种方法。

在我的大多数代码中,我不必仅遵循NSObjectProtocol Equatable和/或Hashable。我已经在需要它的对象上实现了那些协议。

然后,我遍历代码,除去了从Apple协议或需要它的对象(如UITableViewDataSource)继承的那些类上的所有NSObject继承除外

从NSObject继承的必需类是泛型,但通常不会将它们传递给其他泛型类。因此,继承工作正常。在我的MVVM模式中,这些往往是与视图控制器一起使用的中间类,以使诸如表视图之类的逻辑可重用。我有一个tableController类,它符合UITableView协议,并接受3种通用的viewModel类型,允许它为95%的视图提供表逻辑,而无需进行任何修改。并且在需要时,子类可以轻松提供备用逻辑。

这是一个更好的策略,因为我不再无缘无故地使用NSObject。

答案 2 :(得分:0)

这是避免问题的第二种方法。

@matt最初建议这样做,但随后删除了他的答案。这是避免此问题的好方法。他的回答很简单。像这样用objc标记协议:

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

这解决了上面的示例代码,您现在可以获得预期的结果。但是这样做会带来迅速的副作用。至少有一个在这里:

How to use @objc protocol with optional and extensions at the same time?

对我来说,这开始使我的所有协议都兼容objc。这对于我的代码库来说是不值得的更改。我也在使用扩展程序。

我决定至少保留原来的答案,直到Apple修复此错误或侵入性较小的解决方案为止。

我认为应该对此进行记录,以防其他人遇到此问题。