【问题标题】:swift subclasses used in generics don't get called when inheriting from NSObject从 NSObject 继承时,不会调用泛型中使用的 swift 子类
【发布时间】:2019-09-20 13:03:42
【问题描述】:

最后更新部分解决方案!

附加的是产生奇怪行为的代码。我从一个快速的操场上复制了它,所以它应该可以很好地运行。

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

好吧,我找到了一个简单的方法,那就是不从 NSObject 继承。当我使用 swift 本机类时,实际上调用了子类方法。它们是 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 继承的基类,添加协议但从基类和然后特定类继承的基协议类。如果它可以在这种情况下区分基本协议类和特定子类,那么这将是所有用例中的一种功能性解决方法。 (并且可以解释为什么 Apple 的 NSManagedObject 可以正常工作)

不过看起来还是个 bug。

【问题讨论】:

  • 无论是否从NSObject继承基类,我都看不到子类方法调用行为的任何问题。
  • 对我来说,当使用在线 swift playground 和 swift 5.0 运行代码时,这会正确打印(如您所料)。也许您需要再试一次或重新启动 Xcode 或其他东西。投票结束。
  • 现在这很有趣。可以试试早期版本的 swift 吗?
  • 这发生在 swift 游乐场和我的两个项目中。 Playground 是 swift 5。项目是 swift 4.2(我认为,会仔细检查,肯定不会转换为 5)。重新启动 Xcode。将重新启动我的计算机。 Xcode:版本 10.2.1 (10E1001)
  • 重启没有帮助。关于下一步尝试什么的任何想法?

标签: swift generics inheritance


【解决方案1】:

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

在我的 bug 提交中,我创建了一个干净的紧凑缩减您的示例,适合发送给 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 继承的 A


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

  • 将A的init标记为@nonobjc

【讨论】:

  • 我已经将我的一个版本发送给苹果寻求代码帮助。如果他们继续“不明白”出了什么问题,我会使用它。不过这很棒!谢谢!
  • 我还没有提交错误报告。我正在使用我的开发者帐户中的代码帮助。如果您想提交错误报告,我可以。我确实更新了我上面的代码,使其更清晰,自我记录,并更容易地显示 NSObject 子类和 swift 之间的比较。如果你这样做,如果他们需要更多信息,我也会很乐意评论。
【解决方案2】:

这与其说是一个答案,不如说是一种避免问题的方法。

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

然后我检查了我的代码,删除了那些继承自 Apple 协议或需要它的对象(如 UITableViewDataSource)的类的所有 NSObject 继承except

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

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

【讨论】:

    【解决方案3】:

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

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

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

    这解决了上面的示例代码,您现在得到了预期的结果。但是这样做对 swift 有副作用。至少其中一个在这里:

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

    对我来说,它开始了必须使我的所有协议 objc 兼容的链条。这使得我的代码库的更改不值得。我也在使用扩展。

    我决定至少在 Apple 修复此错误或有侵入性较小的解决方案之前保持我原来的答案。

    我认为这个应该记录下来,以防它帮助其他人面临这个问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多