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

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

问题描述:

部分解决方案更新最后!

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

Attached is code that produces odd behavior. I copied it out of a swift playground so it should run in one fine.

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

I created a subclass in my project and passed it to my generic class as the concrete type. However, I quickly noticed that only the base class methods are called. This is shown with myBase and mySub below. Despite the generic class being instantiated as <mySub>, only the base methods are called. The print lines for the subclass are never shown.

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

Well, I found a simple way around that and that is to not inherit from NSObject. When I used swift native classes, the subclass methods are in fact called. These are secondBase and secondSub.

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

How do I pass a subclass into a generic class and get the actual subclass to receive calls when inheriting from NSObject?

为什么行为会有所不同?

And why would behavior be different?

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

部分解决方案更新:

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

Moving the protocol conformance from the base class to the sub class allows the sub class to behave correctly. Definitions become:

class A1: NSObject
class B1: A1, P

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

The problem is the base class can no longer be used directly at all when no functionality beyond it is needed. This is mostly an issue if the protocol being conformed to has an associated type. When this is true, you must have a concrete class that conforms to the protocol for use in generics.

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

One use case here is expecting a base class in the generics (with a protocol involving an associated type) which allows something to function without caring what actual sub class was passed in. This actually ends up being a poor man's form of type erasure in some cases. And you can still use the same generic with the subclass.

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

这是从此处的类似问题得出的:

This was derived from a similar question here: Generic Class does not forward delegate calls to concrete subclass

部分选项是:

  1. 删除NSObject并仅使用swift本机类
  2. 需要NSObject时,请尝试从NSObject的继承中分离出协议一致性.

更新以下想法:不起作用

UPDATE ON THE BELOW IDEA: Doesn't Work

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

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)

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

Still seems like a bug though.

我能够确认您的结果并将其作为错误提交, https://bugs.swift.org /browse/SR-10285 .

I was able to confirm your results and submitted it as a bug, https://bugs.swift.org/browse/SR-10617. Turns out this is a known issue! I was informed (by good old Hamish) that I was duplicating https://bugs.swift.org/browse/SR-10285.

在我提交的错误中,我为您的示例创建了一个简洁紧凑的示例,适合发送给Apple:

In my bug submission, I created a clean compact reduction of your example, suitable for sending to 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".仅此一项就足以提出错误报告.

On Xcode 9.2, we get "B". On Xcode 10.2, we get "A". That alone is enough to warrant a bug report.

在我的报告中,我列出了三种解决此问题的方法,所有这些方法都确认这是一个错误(因为它们应该都不起作用)

In my report I listed three ways to work around the issue, all of which confirm that this is a bug (because none of them should make any difference):

  • 使通用参数化类型的约束为A而不是P

  • make the generic parameterized type's constraint be A instead of P

,或将协议P标记为@objc

或者,没有从NSObject继承的内容

or, don't have A inherit from NSObject

更新:结果(来自Apple自己的发行说明)还有另一种方式:

UPDATE: And it turns out (from Apple's own release notes) there's yet another way:

  • 将A的init标记为@nonobjc
  • mark A's init as @nonobjc