[swift-evolution] [Pitch] Changing NSObject dispatch behavior

Michael Ilseman milseman at apple.com
Thu Dec 15 14:51:56 CST 2016


> On Dec 14, 2016, at 6:32 PM, Kevin Ballard via swift-evolution <swift-evolution at swift.org> wrote:
> 
> On Wed, Dec 14, 2016, at 05:54 PM, Brian King wrote:
>>> Please no. Just because I have to subclass NSObject doesn't mean I want to discard the performance benefits of static dispatch, and it especially doesn't mean I want to actually change the semantics of method resolution. Taking an existing Swift class and changing its base class to NSObject should not change how its methods are dispatched.
>> 
>> Subclassing NSObject already changes how dispatch happens. NSObject
>> extensions will use message dispatch for instance. I really don't
>> think that table -> message dispatch will result in a real life
>> performance impact, but I agree that consistency is valuable.
>> 
>> The static dispatch upgrade loss is disappointing. In practice
>> however, I don't think that this has ever had an impact on my code. If
>> performance is a consideration, most people just drop NSObject. If you
>> are using NSObject, you are probably using it because of a large
>> objective-c code base, in which case, I don't think the profiler is
>> really going to notice the few statically dispatched functions.
> 
> Obj-C compatibility certainly is one reason to use NSObject, but perhaps a bigger reason is because you're subclassing some framework-provided class, like UIView or UIViewController. Just because I'm subclassing UIViewController doesn't mean I want my controller's API to be subject to Obj-C method dispatch unnecessarily.
> 

In addition to UIView[Controller]s, pure Swift projects may also inherit from NSObject extensively in order to provide delegates, support NSCoding, etc. It would be counter-intuitive to me for methods on these otherwise pure Swift classes to not be vtable/de-virtualizable functions.

> And even in the cases where I'm subclassing NSObject for Obj-C compatibility, that doesn't mean that most of the calls to my object are coming from Obj-C. All that means is that at least one place my object is used is Obj-C, and 99% of the uses might be Swift. Or if I'm writing a library, it might mean that I simply want to preserve Obj-C compatibility in case a client wants it, even though I expect my clients to be primarily Swift (For example, this is how postmates/PMHTTP is written; Obj-C–compatible, but the expectation is most clients will probably be Swift).
> 
>>> Interaction with Obj-C runtime machinery stuff like KVO should be opt-in. In Obj-C it's ad-hoc, many classes support it for properties but many also don't, and very few properties have their KVO conformance documented. I don't view having to mark my properties as `dynamic` to participate in KVO to be a problem with Swift but rather a feature. It tells the reader that this property supports KVO.
>> 
>> This is an interesting point, and it would be an interesting semantic.
>> However in practice, the majority of NSObject code is imported from
>> obj-c, and it's generated interface does not follow this convention.
> 
> Just because framework classes don't follow this convention doesn't mean it's not a valuable convention to have in code written in Swift. There's a lot of things that Swift does better than Obj-C, and that you don't get when using an API imported from Obj-C, but that's not a reason to not do those things.
> 
>> Also, it's strange to conflate how something was dispatched and if it
>> supported KVO. I think property delegates will be the future here and
>> enable some exciting contracts if adopted.
>> 
>> Another approach is to just make it easier to opt into the full obj-c
>> machinery. The addition of a class or extension level `dynamic`
>> keyword would be great. If we could get buy in on this, then the
>> debate becomes if NSObject should default to `dynamic` or not.
> 

You would want a class-level `dynamic` that would also implicitly apply to any subclasses? This feels a little different than how Swift normally chooses defaults, could you elaborate more on why or if there’s anything else in the language analogous to this?

> I don't think we should ever make it possible to mark an entire class as `dynamic`. This just reintroduces the Obj-C problem, where many properties support KVO, but not all, and there's no indication on the property itself as to whether it supports it.
> 

I’m not familiar enough with these kinds of bugs. Kevin, do you think the existing behavior aligns with or runs counter to safe-by-default?

> -Kevin Ballard
> 
>> Brian
>> 
>>> 
>>> -Kevin Ballard
>>> 
>>> On Wed, Dec 14, 2016, at 03:15 PM, Brian King via swift-evolution wrote:
>>>> I wanted to follow up to a blog post I wrote about Message Dispatch in
>>>> Swift — https://www.raizlabs.com/dev/2016/12/swift-method-dispatch. I
>>>> mentioned some changes to NSObject that didn’t result in any
>>>> objections, so I thought it was time to see what the SE mailing list
>>>> thought.

That’s a very thoughtful and informative post, thank you for writing it! I haven’t read the whole thing, but relevant to this, here are a couple tweaks:

NSObject uses table dispatch for methods inside the initial declaration!
I don’t think NSObject does, but native Swift subclasses of NSObject do by default, just like any other native Swift class or subclass.

NSObject extensions use message dispatch
Is this also true of extensions to native Swift subclasses of NSObject? I would assume that they would be static dispatch like any other method in an extension of a native Swift class or subclass.


Above, I mentioned that methods defined inside the initial declaration of an NSObjectsubclass use table dispatch. I find this to be confusing, hard to explain

If anything, it seems consistent with how all native Swift classes operate. Knowing the entire transitive inheritance graph for a given class to know whether the default dispatch rules don’t apply seems more confusing and harder to explain. Could you elaborate?

What’s confusing might depend on one’s starting point. My perspective is from learning Swift and then learning Cocoa, and that’s why I see this behavior as being pretty consistent with Swift. But, maybe someone who knows Cocoa and is learning Swift might be confused?

>>>> 
>>>> I’ve read a few conversations on SE mailing list that have morphed
>>>> into abstract conversations about dynamic vs static dispatch. I want
>>>> to focus specifically on how Swift NSObject subclasses behave.
>>>> 
>>>> I think that there are 2 changes that will result in fewer bugs and
>>>> will not have a substantial impact on performance:
>>>> 
>>>> 
>>>> ## Remove Table Dispatch from NSObject
>>>> 
>>>> NSObject subclasses use table dispatch for the initial class
>>>> declaration block. I think that using message dispatch for NSObject
>>>> subclasses everywhere will result in a much more consistent developer
>>>> experience.
>>>> 
>>>> ## Block NSObject Visibility Optimizations
>>>> 
>>>> Swift upgrades method dispatch to final when the compiler can prove
>>>> that the method is not subclassed. I would like to see Swift be more
>>>> careful about the impact of these optimizations on message dispatch,
>>>> and consider message dispatch non-upgradable.
>>>> 

My understanding is that Swift already considers message dispatch to be "non-upgradable". It seems like what you’re asking for is more like an implicitly inferred `dynamic` on all methods for subclasses of Objective-C classes.

>>>> 
>>>> I thought it would help to frame this choice as a trade-off between
>>>> Swift’s goals of safe, fast, and expressive.
>>>> 
>>>> ## Safe
>>>> 
>>>> Always using message dispatch for NSObject subclasses will fix a class
>>>> of runtime errors in framework features that are designed around
>>>> message passing (e.g. KVO). Arguments against using dynamic features
>>>> like this are valid, but Cocoa frameworks still use dynamic features
>>>> and the above behaviors result in actual bugs. As a bonus, this will
>>>> resolve SR-584, where a table-dispatched method that is overridden by
>>>> a message dispatch method doesn’t behave correctly.
>>>> 
>>>> ## Fast
>>>> 
>>>> The above changes will result in slower dispatch in NSObject
>>>> subclasses. However, I don't think that these dispatch changes
>>>> actually have a tangible impact on performance. Most NSObject
>>>> subclasses sit on top of a lot of `objc_msgSend`, and if there is a
>>>> specific hot spot, it would still be optimizable via the final
>>>> keyword.
>>>> 

In the case of deriving from NSObject to be able to work with NSCoding, the only msgSending it sits on top of may be at relatively rare serialization points.

>>>> ## Expressive
>>>> 
>>>> Using table dispatch for NSObject without any source indication or
>>>> library documentation is not very expressive. I think it’s important
>>>> to weigh table dispatch behavior against all of the framework
>>>> documentation and developer experience that assume message dispatch.
>>>> This will also eliminate the need for a lot of `@objc` and `dynamic`
>>>> annotations that are often inconsistently applied depending on if they
>>>> are needed in the scope they are defined in (e.g. class vs extension).
>>>> 
>>>> 
>>>> If this idea shows promise, I’d be glad to formalize a Swift Evolution
>>>> Proposal and explore syntactic details. I think being able to flag a
>>>> class with `dynamic` and applying this flag to `NSObject` may be the
>>>> only syntactic change needed. However, it would be good to debate the
>>>> merit of the behavior change before the syntax.
>>>> 
>>>> 
>>>> Thanks!
>>>> 
>>>> 
>>>> Brian King
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20161215/9056102d/attachment.html>


More information about the swift-evolution mailing list