部分解决方案更新最后!
附加的代码会产生奇怪的行为。我将其复制到一个快速的操场上,因此可以很好地运行。
我在项目中创建了一个子类,并将其作为具体类型传递给我的通用类。但是,我很快注意到仅调用了基类方法。下方显示为myBase
和mySub
。尽管将通用类实例化为<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
部分选项是:
更新以下想法:不起作用
我将测试是否提供额外的图层可以改变行为。基本上有3层,从NSObject继承的基类,从协议继承但从基类和 then 特定类继承的基本Protocol类。如果在这种情况下可以区分基本协议类和特定子类,那么这将是所有用例的功能解决方法。 (并且可以解释为什么苹果的NSManagedObject可以正常工作)
尽管如此,它似乎仍然是一个错误。
答案 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)还有另一种方法:
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修复此错误或侵入性较小的解决方案为止。
我认为应该对此进行记录,以防其他人遇到此问题。