[Pitch] Changing NSObject dispatch behavior


(Brian King) #1

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.

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.

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.

## 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


(Lily Ballard) #2

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.

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.

-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.

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.

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.

## 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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Brian King) #3

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.

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.
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.

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.

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.

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.

## 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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Rod Brown) #4

I’m in agreement that ‘dynamic’ is probably not what you want without a declaration.

There are currently some bugs in how dispatch works for overrides in Swift extensions, and I’d like to see any methods that conform to an @objc protocol being given an implicit @objc, but true dynamic? No.

The only difference between table and method dispatch is the Objective-C message dispatch system. Unless users want to work around things by manually handling with some of the complex machinery in Obj-C, which is rare, then there is no reason behind doing so, and all it comes down to is a relatively large performance hit for nothing. And to be honest, with very few exceptions, if you’re using that ultra-dynamic machinery, you’re probably Doing It Wrong in the first place. If you need this functionality, dynamic still exists. But Swift is Table Dispatch by default for a reason: you’re paying a massive penalty for something you’ll rarely if ever use, and it should be opt in.

···

On 15 Dec 2016, at 10:15 am, Brian King via swift-evolution <swift-evolution@swift.org> 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.

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.

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.

## 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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Lily Ballard) #5

> 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.

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.

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.

-Kevin Ballard

···

On Wed, Dec 14, 2016, at 05:54 PM, Brian King wrote:

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.
>>
>> 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.
>>
>>
>> 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.
>>
>> ## 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@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution


(Michael Ilseman) #6

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

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.

···

On Dec 14, 2016, at 6:32 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:
On Wed, Dec 14, 2016, at 05:54 PM, Brian King wrote:

On Wed, Dec 14, 2016, at 03:15 PM, Brian King via swift-evolution wrote:

## 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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Brian King) #7

First off, I'm still learning out how to debate at this speed, sorry for
the slow replies!

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.

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

It's interesting, your library has very few non-final methods, because most
of your classes are `final`. it's great to have this option, and as a
result, it is very easy to write performant code. It is very wordy to write
runtime friendly code. I think I value these optimizations less than you,
to me it feels like it's pre-mature optimization, especially given how easy
it is to opt into Static dispatch.

> > 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.

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.

This would just be short hand for adding the `dynamic` keyword in front of
every variable and method, so shy of the already existing computed property
getter case, all of the properties should just work.

···

On Wed, Dec 14, 2016 at 9:32 PM, Kevin Ballard <kevin@sb.org> wrote:

On Wed, Dec 14, 2016, at 05:54 PM, Brian King wrote:

-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.
> >>
> >> 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.
> >>
> >>
> >> 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.
> >>
> >> ## 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@swift.org
> >> https://lists.swift.org/mailman/listinfo/swift-evolution
> > _______________________________________________
> > swift-evolution mailing list
> > swift-evolution@swift.org
> > https://lists.swift.org/mailman/listinfo/swift-evolution


(Brian King) #8

OK, this is great, a little detail just clicked that I had not fully
thought through. My suggestion of using message dispatch only for NSObject
isn't really possible since `dynamic` doesn't support Swift types yet. So
any form of consistency argument is out the window.

But what is clear from the general tone is that dynamic behavior is to be
opted into, and that's not changing. I can PR `commonly_proposed.md`, to
save future runtime happy developers from treading into these waters if
interested. I am interested in ways to make dynamic easier to use, but I'm
not sure flagging a declaration is helpful enough to warrant the syntax.

Thanks all, I have a blog post to update, and I'm going to get back to
playing around with SR-584 -- the bug that began it all.

Brian

···

On Thu, Dec 15, 2016 at 10:00 PM, Joe Groff <jgroff@apple.com> wrote:

> On Dec 15, 2016, at 6:48 PM, Brian King <brianaking@gmail.com> wrote:
>
> Thanks Joe, that makes a lot of sense. But is the only metric for "the
best possible strategy" performance? The best possible metric for NSObject
could be to use message dispatch, because consistency with the frameworks
and with it's legacy.

The performance cost of objc_msgSend isn't that high, but it only supports
dispatching ObjC-compatible methods. We'll need something similar to
objc_msgSend for Swift method dispatch in its full generality, but vtables
are easier to implement (and do have a much lower startup and dirty memory
cost, and slightly lower per-method-call cost, so there are good reasons to
use vtables when possible). The dispatch mechanism is orthogonal to the
language semantics, and is an implementation detail. We don't want methods
to be arbitrarily overridable at runtime by default, for reasons Kevin
articulated well, and when the method implementation is bound at compile
time, it shouldn't really matter how it's dispatched.

-Joe


(Freak Show) #9

I’m in agreement that ‘dynamic’ is probably not what you want without a declaration.

I hold a completely opposite viewpoint. Dynamic is always what I want and table based is premature optimization. Late binding is important. I found far too often when working in C++ (yes I am really flipping old) I would come across code where a developer hadn't chosen to make a method virtual and I found myself just up a creek with regards to extending that code. Ditto people declaring stuff final in Java for no good reason.

The important thing to consider is that the future is generally unknowable and if you don't know the future you cannot make good choices about what should and should not be dynamic. So the conservative thing is to make everything dynamic until you cannot afford it - which...is usually never.

The only difference between table and method dispatch is the Objective-C message dispatch system. Unless users want to work around things by manually handling with some of the complex machinery in Obj-C, which is rare, then there is no reason behind doing so, and all it comes down to is a relatively large performance hit for nothing. And to be honest, with very few exceptions, if you’re using that ultra-dynamic machinery, you’re probably Doing It Wrong in the first place.

I do this sort of thing routinely and I assure you I am not Doing It Wrong.

I don't get why there is so much emphasis on performance in this day and age. I haven't run into a performance problem I could trace to speed of ObjectiveC's method dispatching since the early 90's.

Second - I have quite a lot of code that relies on this sort of dynamism. I've been a very serious C++ developer and a very serious Smalltalk programmer at various times in my career and I value the flexibility of late binding far above performance.

If you need this functionality, dynamic still exists. But Swift is Table Dispatch by default for a reason: you’re paying a massive penalty for something you’ll rarely if ever use, and it should be opt in.

Strongly disagree.

···

On Dec 29, 2016, at 13:28, Rod Brown via swift-evolution <swift-evolution@swift.org> wrote:

On 15 Dec 2016, at 10:15 am, Brian King via swift-evolution <swift-evolution@swift.org> 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.

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.

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.

## 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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Dave Abrahams) #10

By that measure there should be no encapsulation; we should make
everything public, because somebody might need it someday.

Cheers,

···

on Thu Dec 29 2016, Freak Show <swift-evolution@swift.org> wrote:

On Dec 29, 2016, at 13:28, Rod Brown via swift-evolution <swift-evolution@swift.org> wrote:

I’m in agreement that ‘dynamic’ is probably not what you want without a declaration.

I hold a completely opposite viewpoint. Dynamic is always what I want
and table based is premature optimization. Late binding is important.
I found far too often when working in C++ (yes I am really flipping
old) I would come across code where a developer hadn't chosen to make
a method virtual and I found myself just up a creek with regards to
extending that code. Ditto people declaring stuff final in Java for
no good reason.

The important thing to consider is that the future is generally
unknowable and if you don't know the future you cannot make good
choices about what should and should not be dynamic. So the
conservative thing is to make everything dynamic until you cannot
afford it - which...is usually never.

--
-Dave


(Freak Show) #11

I don't know what you mean by "encapsulation" but I will explain what I think it means.

From Wikipedia https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)
In programming languages <https://en.wikipedia.org/wiki/Programming_languages>, encapsulation is used to refer to one of two related but distinct notions, and sometimes to the combination[1] <https://en.wikipedia.org/wiki/Encapsulation_(object-oriented_programming)#cite_note-1>[2] <https://en.wikipedia.org/wiki/Encapsulation_(object-oriented_programming)#cite_note-Dale-2> thereof:
A language mechanism for restricting direct access to some of the object <https://en.wikipedia.org/wiki/Object_(computer_science)>'s components.[3] <https://en.wikipedia.org/wiki/Encapsulation_(object-oriented_programming)#cite_note-3>[4] <https://en.wikipedia.org/wiki/Encapsulation_(object-oriented_programming)#cite_note-Pierce-4>
A language construct that facilitates the bundling of data with the methods <https://en.wikipedia.org/wiki/Method_(computer_programming)> (or other functions) operating on that data.[5] <https://en.wikipedia.org/wiki/Encapsulation_(object-oriented_programming)#cite_note-5>[6] <https://en.wikipedia.org/wiki/Encapsulation_(object-oriented_programming)#cite_note-6>

Objective C and Smalltalk have encapsulation. iVars are always private. Methods are always callable and "private implementation" is provided by convention (put private methods in private extensions/categories). They do not have compiler enforced access control.

I'm not a fan of compiler enforced access control. It has never helped me out but it has gotten in my way a number of times. There are plenty of ways to do information/implementation hiding without relying on the compiler to play nanny.

My views on what I like in programming languages are largely informed by my years of doing C++, Smalltalk, Java, and Objective C (in that order).

Two of those languages I found rigid, frustrating, and limited and I avoid working in them as much as possible. Two of them I find malleable and expressive and seek out opportunities to work in them.

Which is which I leave as an exercise to the reader but the ones I prefer are NOT Java or C++ (although I'm fine with using C++ for limited sized high performance libraries like audio processing units).

Cheers,
-Todd Blanchard

···

On Dec 29, 2016, at 16:34, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
By that measure there should be no encapsulation; we should make
everything public, because somebody might need it someday.


(Goffredo Marocchi) #12

I’m in agreement that ‘dynamic’ is probably not what you want without a declaration.

I hold a completely opposite viewpoint. Dynamic is always what I want and table based is premature optimization.

Succinctly stated, unless the compiler can guarantee that conversion to table based dispatch or static dispatch has absolutely no consequence on the code behaviour or the user has explicitly asked for such an optimisation to happen or fully dynamic should be the default.

I cannot understand why one should like code to go back to having behaviour being decided by the pointer/reference type instead of what the instance created actually holds, but this will not change anytime soon.

···

Sent from my iPhone

On 29 Dec 2016, at 23:50, Freak Show via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 29, 2016, at 13:28, Rod Brown via swift-evolution <swift-evolution@swift.org> wrote:

Late binding is important. I found far too often when working in C++ (yes I am really flipping old) I would come across code where a developer hadn't chosen to make a method virtual and I found myself just up a creek with regards to extending that code. Ditto people declaring stuff final in Java for no good reason.

The important thing to consider is that the future is generally unknowable and if you don't know the future you cannot make good choices about what should and should not be dynamic. So the conservative thing is to make everything dynamic until you cannot afford it - which...is usually never.

The only difference between table and method dispatch is the Objective-C message dispatch system. Unless users want to work around things by manually handling with some of the complex machinery in Obj-C, which is rare, then there is no reason behind doing so, and all it comes down to is a relatively large performance hit for nothing. And to be honest, with very few exceptions, if you’re using that ultra-dynamic machinery, you’re probably Doing It Wrong in the first place.

I do this sort of thing routinely and I assure you I am not Doing It Wrong.

I don't get why there is so much emphasis on performance in this day and age. I haven't run into a performance problem I could trace to speed of ObjectiveC's method dispatching since the early 90's.

Second - I have quite a lot of code that relies on this sort of dynamism. I've been a very serious C++ developer and a very serious Smalltalk programmer at various times in my career and I value the flexibility of late binding far above performance.

If you need this functionality, dynamic still exists. But Swift is Table Dispatch by default for a reason: you’re paying a massive penalty for something you’ll rarely if ever use, and it should be opt in.

Strongly disagree.

On 15 Dec 2016, at 10:15 am, Brian King via swift-evolution <swift-evolution@swift.org> 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.

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.

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.

## 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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Rod Brown) #13

I’m in agreement that ‘dynamic’ is probably not what you want without a declaration.

I hold a completely opposite viewpoint. Dynamic is always what I want and table based is premature optimization. Late binding is important. I found far too often when working in C++ (yes I am really flipping old) I would come across code where a developer hadn't chosen to make a method virtual and I found myself just up a creek with regards to extending that code. Ditto people declaring stuff final in Java for no good reason.

The important thing to consider is that the future is generally unknowable and if you don't know the future you cannot make good choices about what should and should not be dynamic. So the conservative thing is to make everything dynamic until you cannot afford it - which...is usually never.

Then that’s great. If you’d prefer to work with a more open, overridable and intercetable platform which is dynamic by default, Apple has your back: Obj-C. It’s not deprecated. It’s actually the language they’re still writing iOS, OS X, tvOS and watchOS higher level frameworks in still, so you’re covered. Who is forcing you to use Swift, a language where compiler optimisations like this a stated goal?

The only difference between table and method dispatch is the Objective-C message dispatch system. Unless users want to work around things by manually handling with some of the complex machinery in Obj-C, which is rare, then there is no reason behind doing so, and all it comes down to is a relatively large performance hit for nothing. And to be honest, with very few exceptions, if you’re using that ultra-dynamic machinery, you’re probably Doing It Wrong in the first place.

I do this sort of thing routinely and I assure you I am not Doing It Wrong.

Ok, so you do it a lot. That means definitively it’s the best way?

Obj-C was designed to constantly be interfering with method dispatch? You can do it, it’s there, message forwarding, those types of mechanisms, but if you’re relying on that for your day-to-day work, you’re in for a load of trouble, and you’re hacking around frameworks using the language rather than working with the frameworks. Apple don’t design their frameworks first and foremost for us to get in there and start using method swizzling or the like - in fact they specifically discourage it. Just because you do it and you think you’re smart doesn’t make it good design.

There are rare instances where frameworks and implementations use some of the really dynamic stuff, like CoreData, or KVO. That’s great. I’m not arguing there isn’t a use for it. I’m saying you pay a massive hit, 90% of the time for no reason.

I don't get why there is so much emphasis on performance in this day and age. I haven't run into a performance problem I could trace to speed of ObjectiveC's method dispatching since the early 90’s.

Do a time profile of a modern Obj-C application. You’ll find that 15% of the time the application is running, it’s stuck in the middle of objc_msgSend. That’s a massive overhead in anyone’s books. Why is this important?

1. More CPU use equals more battery drain. Considering the vast majority of Apple’s platforms and the direction of computing as a whole is dependent on batteries rather than plugs in walls, this is important.

2. Moore’s Law won’t continue. We’re getting to the end of the road in what CPU advances have given us, and all those core multiplications just ends in more locks - we need to be more efficient because in 40 years of computing, we haven’t worried about making our software better, we just waited for CPU’s to catch up. News Flash: They won’t. Time for us to stop justifying ourselves and do something about making ourselves better at using the processing power we have.

3. Swift wasn’t designed just to replace Obj-C, a relatively high level language. It was designed to scale from system programming to the higher levels as well. Obj-C is good for basic UI development because you rarely tax the limits and are rarely worried about getting performance out of it.

4. Because performance in the language and its frameworks benefits everyone by giving more flexibility to their users.

···

On 30 Dec 2016, at 10:50 am, Freak Show <freakshow42@mac.com> wrote:

On Dec 29, 2016, at 13:28, Rod Brown via swift-evolution <swift-evolution@swift.org> wrote:

Second - I have quite a lot of code that relies on this sort of dynamism. I've been a very serious C++ developer and a very serious Smalltalk programmer at various times in my career and I value the flexibility of late binding far above performance.

If you need this functionality, dynamic still exists. But Swift is Table Dispatch by default for a reason: you’re paying a massive penalty for something you’ll rarely if ever use, and it should be opt in.

Strongly disagree.

On 15 Dec 2016, at 10:15 am, Brian King via swift-evolution <swift-evolution@swift.org> 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.

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.

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.

## 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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #14

Extensions use objc_msgSend dispatch only because they can't be in the main vtable, since an extension may be defined in a separate compilation unit, and we haven't implemented a Swift-native equivalent to msgSend that handles Swift's calling convention variants.

-Joe

···

On Dec 15, 2016, at 12:51 PM, Michael Ilseman via swift-evolution <swift-evolution@swift.org> wrote:

  • 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.


(Lily Ballard) #15

The existing behavior is safe-by-default. You have to opt-in to a
property supporting KVO, and the opt-in nature of this means that the
property is self-documenting as to whether it supports KVO. Of course,
it's possible to write a `dynamic` property that actually doesn't do KVO
properly (e.g. because it's a computed property that uses another
backing property, and you modify the backing property directly), but
since 99.9% of `dynamic` properties are only dynamic in order to support
KVO, I have no concerns about someone writing a `dynamic` property that
doesn't actually do KVO properly.

-Kevin Ballard

···

On Thu, Dec 15, 2016, at 12:51 PM, Michael Ilseman wrote:

On Dec 14, 2016, at 6:32 PM, Kevin Ballard via swift-evolution <swift- >> evolution@swift.org> wrote:

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?


(Charles Srstka) #16

The problem Kevin describes is still here; ‘dynamic’ itself is quite orthogonal to KVO as a concept. A method being declared ‘dynamic’ is no guarantee that it actually supports KVO, and likewise, a method does not need to be marked ‘dynamic’ in order to support KVO. You can, for example, either call willChangeValue() and didChangeValue() in your willSet and didSet accessors for a stored property, or for a computed property you can simply implement the proper accessor methods to describe the dependencies, as in the example below.

import Foundation

class Watcher: NSObject {
  var kvoContext = 0
  
  init(watched: Watched) {
    super.init()
    watched.addObserver(self, forKeyPath: "foo", options: [], context: &kvoContext)
  }
  
  override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if context == &kvoContext {
      print("foo changed; now it's \((object as! Watched).foo)")
    } else {
      super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }
  }
}

class Watched: NSObject {
  // not a single ‘dynamic’ member in here:

  class func keyPathsForValuesAffectingFoo() -> Set<String> { return ["bar"] }
  var foo: Int {
    get { return self.bar }
    set(foo) { self.bar = foo }
  }
  
  var bar: Int = 0 {
    willSet { self.willChangeValue(forKey: "bar") }
    didSet { self.didChangeValue(forKey: "bar") }
  }
}

let watched = Watched()
let watcher = Watcher(watched: watched)

watched.bar = 5 // outputs: "foo changed; now it's 5”

- - - - - -

All ‘dynamic’ does for you vis a vis KVO conformance is to automatically add the willChangeValue() and didChangeValue() calls, *for a stored property.* For a computed property, that doesn’t help you, if the dependencies aren’t properly set up. For example, if we replace the Watched class with:

class Watched: NSObject {
  dynamic var foo: Int {
    get { return self.bar }
    set(foo) { self.bar = foo }
  }
  
  dynamic var bar: Int = 0 {
    willSet { self.willChangeValue(forKey: "bar") }
    didSet { self.didChangeValue(forKey: "bar") }
  }
}

- - - - - -

and then change the “bar” property, no notifications will be fired, even though the value of ‘foo’ has indeed changed. So to a client of the class who can only see its interface and not the source code, ‘dynamic’ is useless on its own for determining KVO conformance.

Bottom line: we really do need a new KVO-replacement system for Swift, one that could actually advertise itself as part of the interface instead of relying on documentation (especially since Apple isn’t interested in writing it anymore; the number of classes and methods that just say “No overview available.” in the documentation viewer in Sierra is really quite astounding). Unfortunately, this is probably additive, and thus probably won’t be heard until the next phase of swift-evolution.

Charles

···

On Dec 15, 2016, at 2:51 PM, Michael Ilseman via swift-evolution <swift-evolution@swift.org> wrote:

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?


(Brian King) #17

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'm not entirely sure how it should behave to be honest. Having the
`dynamic` version of `final` would be a nice feature though. Since final
stops inheritance as well, it could be considered as influencing the entire
class hierarchy! I think this could be a large lever and effect the entire
hierarchy, or a smaller lever and just effect the declaration. I was
thinking that it would influence all the subclasses as well.

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?

It's interesting hearing this called 'safe by default'. To me it's unsafe
by default since you can use KVO and it can compile and not work, and only
1 paragraph in the Swift book, or experience will teach you this.

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:

Thanks for the feedback! I'm going to update the post soon and will add
that to the list.

···

On Thu, Dec 15, 2016 at 3:51 PM, Michael Ilseman <milseman@apple.com> wrote:


(Lily Ballard) #18

>
>> 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?

The problem Kevin describes is still here; ‘dynamic’ itself is quite orthogonal to KVO as a concept. A method being declared ‘dynamic’ is no guarantee that it actually supports KVO, and likewise, a method does not need to be marked ‘dynamic’ in order to support KVO. You can, for example, either call willChangeValue() and didChangeValue() in your willSet and didSet accessors for a stored property, or for a computed property you can simply implement the proper accessor methods to describe the dependencies, as in the example below.

import Foundation

class Watcher: NSObject {
  var kvoContext = 0
  
  init(watched: Watched) {
    super.init()
    watched.addObserver(self, forKeyPath: "foo", options: [], context: &kvoContext)
  }
  
  override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if context == &kvoContext {
      print("foo changed; now it's \((object as! Watched).foo)")
    } else {
      super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }
  }
}

class Watched: NSObject {
  // not a single ‘dynamic’ member in here:

  class func keyPathsForValuesAffectingFoo() -> Set<String> { return ["bar"] }
  var foo: Int {
    get { return self.bar }
    set(foo) { self.bar = foo }
  }
  
  var bar: Int = 0 {
    willSet { self.willChangeValue(forKey: "bar") }
    didSet { self.didChangeValue(forKey: "bar") }
  }
}

let watched = Watched()
let watcher = Watcher(watched: watched)

watched.bar = 5 // outputs: "foo changed; now it's 5”

- - - - - -

All ‘dynamic’ does for you vis a vis KVO conformance is to automatically add the willChangeValue() and didChangeValue() calls, *for a stored property.* For a computed property, that doesn’t help you, if the dependencies aren’t properly set up. For example, if we replace the Watched class with:

class Watched: NSObject {
  dynamic var foo: Int {
    get { return self.bar }
    set(foo) { self.bar = foo }
  }
  
  dynamic var bar: Int = 0 {
    willSet { self.willChangeValue(forKey: "bar") }
    didSet { self.didChangeValue(forKey: "bar") }
  }
}

- - - - - -

and then change the “bar” property, no notifications will be fired, even though the value of ‘foo’ has indeed changed. So to a client of the class who can only see its interface and not the source code, ‘dynamic’ is useless on its own for determining KVO conformance.

The problem with that code isn't that `dynamic` doesn't work for computed properties. It does; if you mutate the `foo` property, you'll get the KVO notifications. The problem is you have one property that depends on another and you didn't set up the KVO machinery properly using automaticallyNotifiesObservers(forKey:) or automaticallyNotifiesObserversOf<key>() (incidentally in Swift you can write the latter as a `static let`, since that becomes a class method in Obj-C).

So yes, `dynamic` by itself doesn't mean that the property supports KVO. But there are very few reasons to use `dynamic` outside of supporting KVO, so it's a pretty good signal that the property does support it. And conversely, not having `dynamic` doesn't mean that it doesn't support KVO, though if it does have manual KVO support using will/didChangeValue(forKey:) then it should be documented as such.

···

On Thu, Dec 15, 2016, at 02:29 PM, Charles Srstka wrote:

> On Dec 15, 2016, at 2:51 PM, Michael Ilseman via swift-evolution <swift-evolution@swift.org> wrote:

Bottom line: we really do need a new KVO-replacement system for Swift, one that could actually advertise itself as part of the interface instead of relying on documentation (especially since Apple isn’t interested in writing it anymore; the number of classes and methods that just say “No overview available.” in the documentation viewer in Sierra is really quite astounding). Unfortunately, this is probably additive, and thus probably won’t be heard until the next phase of swift-evolution.

Charles


(Lily Ballard) #19

> >
> >> 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?
>
> The problem Kevin describes is still here; ‘dynamic’ itself is quite orthogonal to KVO as a concept. A method being declared ‘dynamic’ is no guarantee that it actually supports KVO, and likewise, a method does not need to be marked ‘dynamic’ in order to support KVO. You can, for example, either call willChangeValue() and didChangeValue() in your willSet and didSet accessors for a stored property, or for a computed property you can simply implement the proper accessor methods to describe the dependencies, as in the example below.
>
> import Foundation
>
> class Watcher: NSObject {
> var kvoContext = 0
>
> init(watched: Watched) {
> super.init()
> watched.addObserver(self, forKeyPath: "foo", options: [], context: &kvoContext)
> }
>
> override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
> if context == &kvoContext {
> print("foo changed; now it's \((object as! Watched).foo)")
> } else {
> super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
> }
> }
> }
>
> class Watched: NSObject {
> // not a single ‘dynamic’ member in here:
>
> class func keyPathsForValuesAffectingFoo() -> Set<String> { return ["bar"] }
> var foo: Int {
> get { return self.bar }
> set(foo) { self.bar = foo }
> }
>
> var bar: Int = 0 {
> willSet { self.willChangeValue(forKey: "bar") }
> didSet { self.didChangeValue(forKey: "bar") }
> }
> }
>
> let watched = Watched()
> let watcher = Watcher(watched: watched)
>
> watched.bar = 5 // outputs: "foo changed; now it's 5”
>
> - - - - - -
>
> All ‘dynamic’ does for you vis a vis KVO conformance is to automatically add the willChangeValue() and didChangeValue() calls, *for a stored property.* For a computed property, that doesn’t help you, if the dependencies aren’t properly set up. For example, if we replace the Watched class with:
>
> class Watched: NSObject {
> dynamic var foo: Int {
> get { return self.bar }
> set(foo) { self.bar = foo }
> }
>
> dynamic var bar: Int = 0 {
> willSet { self.willChangeValue(forKey: "bar") }
> didSet { self.didChangeValue(forKey: "bar") }
> }
> }
>
> - - - - - -
>
> and then change the “bar” property, no notifications will be fired, even though the value of ‘foo’ has indeed changed. So to a client of the class who can only see its interface and not the source code, ‘dynamic’ is useless on its own for determining KVO conformance.

The problem with that code isn't that `dynamic` doesn't work for computed properties. It does; if you mutate the `foo` property, you'll get the KVO notifications. The problem is you have one property that depends on another and you didn't set up the KVO machinery properly using automaticallyNotifiesObservers(forKey:) or automaticallyNotifiesObserversOf<key>() (incidentally in Swift you can write the latter as a `static let`, since that becomes a class method in Obj-C).

Oops, I mean keyPathsForValuesAffectingValue(forKey:) and keyPathsForValuesAffecting<Key>().

That said, your code as written actually sends 2 KVO change notices for "bar", and you do still need to implement automaticallyNotifiesObservers… to disable the automatic KVO notice for "bar".

-Kevin Ballard

···

On Thu, Dec 15, 2016, at 02:33 PM, Kevin Ballard wrote:

On Thu, Dec 15, 2016, at 02:29 PM, Charles Srstka wrote:
> > On Dec 15, 2016, at 2:51 PM, Michael Ilseman via swift-evolution <swift-evolution@swift.org> wrote:

So yes, `dynamic` by itself doesn't mean that the property supports KVO. But there are very few reasons to use `dynamic` outside of supporting KVO, so it's a pretty good signal that the property does support it. And conversely, not having `dynamic` doesn't mean that it doesn't support KVO, though if it does have manual KVO support using will/didChangeValue(forKey:) then it should be documented as such.

> Bottom line: we really do need a new KVO-replacement system for Swift, one that could actually advertise itself as part of the interface instead of relying on documentation (especially since Apple isn’t interested in writing it anymore; the number of classes and methods that just say “No overview available.” in the documentation viewer in Sierra is really quite astounding). Unfortunately, this is probably additive, and thus probably won’t be heard until the next phase of swift-evolution.
>
> Charles
>


(Charles Srstka) #20

The problem with that code isn't that `dynamic` doesn't work for computed properties. It does; if you mutate the `foo` property, you'll get the KVO notifications. The problem is you have one property that depends on another and you didn't set up the KVO machinery properly using automaticallyNotifiesObservers(forKey:) or automaticallyNotifiesObserversOf<key>() (incidentally in Swift you can write the latter as a `static let`, since that becomes a class method in Obj-C).

You’ll only get the notifications if you mutate ‘foo’ directly. This, however, is fairly useless, because if you are watching ‘foo’, you want to be notified every time the value changes, not just when someone hits one particular accessor. Code relying on observation of ‘foo’ in the example I provided would be prone to breaking in mysterious and possibly horrible ways.

So yes, `dynamic` by itself doesn't mean that the property supports KVO. But there are very few reasons to use `dynamic` outside of supporting KVO, so it's a pretty good signal that the property does support it. And conversely, not having `dynamic` doesn't mean that it doesn't support KVO, though if it does have manual KVO support using will/didChangeValue(forKey:) then it should be documented as such.

Use of the ‘dynamic’ keyword enables all manner of runtime hackery which someone may be employing. The trick to automatically add KVO conformance to accessors is probably the most common, but it’s hardly the only one. One also might want to declare things ‘dynamic’ when working with Objective-C frameworks not under one’s control which might assume the ability to do metaprogramming on your classes—I know it’s commonplace to use ‘dynamic’ all over the place wherever Core Data is involved. Long story short, ‘dynamic’ does not guarantee KVO conformance in any way, shape, or form.

···

On Dec 15, 2016, at 4:33 PM, Kevin Ballard <kevin@sb.org> wrote:

On Dec 15, 2016, at 4:35 PM, Kevin Ballard <kevin@sb.org> wrote:

Oops, I mean keyPathsForValuesAffectingValue(forKey:) and keyPathsForValuesAffecting<Key>().

That said, your code as written actually sends 2 KVO change notices for "bar", and you do still need to implement automaticallyNotifiesObservers… to disable the automatic KVO notice for "bar".

Only when the accessor is called from Objective-C, in which the message-send is *always* dynamic and the presence of the ‘dynamic’ keyword is fairly academic. The example, which was simplified for the purpose of clarity, was to illustrate how things work with respect to Swift’s vtable dispatch system. A real-world example could shut off the automatic notifications, or declare the property as @nonobjc, or similar.

Charles