swift subclasses used in generics don't get called when inheriting from NSObject

前端 未结 3 1914
面向向阳花
面向向阳花 2021-01-14 20:44

Partial Solution Update at the end!

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

相关标签:
3条回答
  • 2021-01-14 20:46

    This is a second way to avoid the problem.

    @matt originally suggested this but then deleted his answer. It is a good way to avoid the problem. His answer was simple. Mark the protocol with objc like this:

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

    This solves the above sample code and you now get the expected results. But doing this has side effects for swift. At least one of those is here:

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

    For me, it began a chain of having to make all my protocols objc compatible. That made the change not worth it for my code base. I was also using extensions.

    I decided to stay with my original answer at least until Apple fixes this bug or there is a less invasive solution.

    I thought this one should be documented in case it helps someone else facing this problem.

    0 讨论(0)
  • 2021-01-14 21:02

    This is not an answer so much as a way to avoid the problem.

    In most of my code, I did not have to conform to NSObjectProtocol only Equatable and/or Hashable. I have implemented those protocols on the objects that needed it.

    I then went through my code, removed all NSObject inheritance except on those classes which inherit from an Apple protocol or object that requires it (Like UITableViewDataSource).

    The classes that are required to inherit from NSObject are Generic but they are not typically handed into other Generic classes. Therefore the inheritance works fine. In my MVVM pattern these tend to be the intermediate classes that work with view controllers to make logic like table views reusable. I have a tableController class that conforms to the UITableView protocols and accepts 3 generic viewModel types allowing it to provide the table logic for 95% of my views with no modifications. And when it needs it, subclasses easily provide alternate logic.

    This is a better strategy as I am no longer randomly using NSObject for no reason.

    0 讨论(0)
  • 2021-01-14 21:07

    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.

    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()
    

    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):

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

    • or, mark the protocol P as @objc

    • or, don't have A inherit from NSObject


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

    • mark A's init as @nonobjc
    0 讨论(0)
提交回复
热议问题