NSProxy dynamic casting to Swift or ObjC class behaves differently


(Yavuz Nuzumlalı) #1

Hi all,

swift_dynamicCastClassUnconditional
<https://github.com/apple/swift/blob/3d2b5bcc5350e1dea2ed8a0a95cd12ff5c760f24/stdlib/public/runtime/Casting.cpp#L508>
and
swift_dynamicCastObjCClassUnconditional
<https://github.com/apple/swift/blob/2daa1400cf79a2965eb07034b48ef7fae02459fd/stdlib/public/runtime/SwiftObject.mm#L1155>
methods
behave differently while verifying casting.

swift_dynamicCastObjCClassUnconditional
<https://github.com/apple/swift/blob/2daa1400cf79a2965eb07034b48ef7fae02459fd/stdlib/public/runtime/SwiftObject.mm#L1155>
method
calls *-isKindOfClass:* method before falling back to *object_getClass*
function.

swift_dynamicCastClassUnconditional
<https://github.com/apple/swift/blob/3d2b5bcc5350e1dea2ed8a0a95cd12ff5c760f24/stdlib/public/runtime/Casting.cpp#L508>
method
calls *swift_dynamicCastClass* method which calls *_swift_getClassOfAllocated
<https://github.com/apple/swift/blob/82509cbd7451e72fb99d22556ad259ceb335cb1f/stdlib/public/runtime/Private.h#L80>*
method
which calls directly *object_getClass* function.

This causes problems if underlying object is an NSProxy subclass.

NSProxy class does not implement *-isKindOfClass:* method, so calls to this
method are forwarded to the real object through *-forwardInvocation:*
method, which causes *-isKindOfClass:* method to return answer according to
the real object's class.

However, *object_getClass* function directly accesses to the metadata of
the given object, so it returns NSProxy subclass.

I think this is a conflicting behavior, and I think
swift_dynamicCastClassUnconditional
<https://github.com/apple/swift/blob/3d2b5bcc5350e1dea2ed8a0a95cd12ff5c760f24/stdlib/public/runtime/Casting.cpp#L508>
method
should also verify first using *-isKindOfClass:* method, in order to
provide consistency.

What do you think?

Best


(Joe Groff) #2

This is intentional, since NSProxy instances are generally expected to be standins for the proxied object. Important Cocoa idioms break down if the "real" class is exposed instead of the class a proxy pretends to be.

-Joe

···

On Jun 8, 2016, at 12:44 AM, Yavuz Nuzumlalı via swift-dev <swift-dev@swift.org> wrote:

Hi all,

swift_dynamicCastClassUnconditional and swift_dynamicCastObjCClassUnconditional methods behave differently while verifying casting.

swift_dynamicCastObjCClassUnconditional method calls -isKindOfClass: method before falling back to object_getClass function.

swift_dynamicCastClassUnconditional method calls swift_dynamicCastClass method which calls _swift_getClassOfAllocated method which calls directly object_getClass function.

This causes problems if underlying object is an NSProxy subclass.

NSProxy class does not implement -isKindOfClass: method, so calls to this method are forwarded to the real object through -forwardInvocation: method, which causes -isKindOfClass: method to return answer according to the real object's class.

However, object_getClass function directly accesses to the metadata of the given object, so it returns NSProxy subclass.

I think this is a conflicting behavior, and I think swift_dynamicCastClassUnconditional method should also verify first using -isKindOfClass: method, in order to provide consistency.


(Jordan Rose) #3

For a little more detail, Swift relies on a bit more information about layout of both instances and classes than Objective-C does, so an NSProxy stand-in wouldn't work for a Swift type. (And by "wouldn't work" I mean "would crash your program".) However, for an Objective-C type, all accesses Swift does will always go through the Objective-C runtime, so it's safe to use an Objective-C-style proxy, and indeed some Cocoa APIs expect this.

This is probably something we could stand to document more explicitly, but I'm not sure where.

Jordan

···

On Jun 8, 2016, at 10:52, Joe Groff via swift-dev <swift-dev@swift.org> wrote:

On Jun 8, 2016, at 12:44 AM, Yavuz Nuzumlalı via swift-dev <swift-dev@swift.org> wrote:

Hi all,

swift_dynamicCastClassUnconditional and swift_dynamicCastObjCClassUnconditional methods behave differently while verifying casting.

swift_dynamicCastObjCClassUnconditional method calls -isKindOfClass: method before falling back to object_getClass function.

swift_dynamicCastClassUnconditional method calls swift_dynamicCastClass method which calls _swift_getClassOfAllocated method which calls directly object_getClass function.

This causes problems if underlying object is an NSProxy subclass.

NSProxy class does not implement -isKindOfClass: method, so calls to this method are forwarded to the real object through -forwardInvocation: method, which causes -isKindOfClass: method to return answer according to the real object's class.

However, object_getClass function directly accesses to the metadata of the given object, so it returns NSProxy subclass.

I think this is a conflicting behavior, and I think swift_dynamicCastClassUnconditional method should also verify first using -isKindOfClass: method, in order to provide consistency.

This is intentional, since NSProxy instances are generally expected to be standins for the proxied object. Important Cocoa idioms break down if the "real" class is exposed instead of the class a proxy pretends to be.


(Yavuz Nuzumlalı) #4

Hmm, interesting.

So, we can say that NSProxy class, which is the base structure for
implementing proxy pattern, will not work for a Swift type, right? And I
simply can't use proxy pattern if real object can be a Swift type.

In my use case, I have a framework, and I want to switch developer's some
delegate object with my NSProxy standin, listen delegate method myself,
then forward to the developer's original delegate object.

It was working without any issues for delegate objects which are
Objective-C classes. But, it fails for Swift types because dynamic casting
does not work anymore.

So, do you have any suggestions about how can I handle this interception
functionality without NSProxy?

I don't want to do swizzling :slight_smile:

Thanks for the explanations!

···

On Wed, 8 Jun 2016 at 21:13, Jordan Rose <jordan_rose@apple.com> wrote:

On Jun 8, 2016, at 10:52, Joe Groff via swift-dev <swift-dev@swift.org> > wrote:

On Jun 8, 2016, at 12:44 AM, Yavuz Nuzumlalı via swift-dev < > swift-dev@swift.org> wrote:

Hi all,

swift_dynamicCastClassUnconditional and
swift_dynamicCastObjCClassUnconditional methods behave differently while
verifying casting.

swift_dynamicCastObjCClassUnconditional method calls -isKindOfClass:
method before falling back to object_getClass function.

swift_dynamicCastClassUnconditional method calls swift_dynamicCastClass
method which calls _swift_getClassOfAllocated method which calls directly
object_getClass function.

This causes problems if underlying object is an NSProxy subclass.

NSProxy class does not implement -isKindOfClass: method, so calls to this
method are forwarded to the real object through -forwardInvocation: method,
which causes -isKindOfClass: method to return answer according to the real
object's class.

However, object_getClass function directly accesses to the metadata of the
given object, so it returns NSProxy subclass.

I think this is a conflicting behavior, and I think
swift_dynamicCastClassUnconditional method should also verify first using
-isKindOfClass: method, in order to provide consistency.

This is intentional, since NSProxy instances are generally expected to be
standins for the proxied object. Important Cocoa idioms break down if the
"real" class is exposed instead of the class a proxy pretends to be.

For a little more detail, Swift relies on a bit more information about
layout of both instances and classes than Objective-C does, so an NSProxy
stand-in wouldn't work for a Swift type. (And by "wouldn't work" I mean
"would crash your program".) However, for an Objective-C type, all accesses
Swift does will always go through the Objective-C runtime, so it's safe to
use an Objective-C-style proxy, and indeed some Cocoa APIs expect this.

This is probably something we could stand to document more explicitly, but
I'm not sure where.

Jordan


(Brent Royal-Gordon) #5

In my use case, I have a framework, and I want to switch developer's some delegate object with my NSProxy standin, listen delegate method myself, then forward to the developer's original delegate object.

It was working without any issues for delegate objects which are Objective-C classes. But, it fails for Swift types because dynamic casting does not work anymore.

So, do you have any suggestions about how can I handle this interception functionality without NSProxy?

Make your proxy object explicitly conform to the the delegate protocol you're trying to monitor, and have it call through to the original delegate. Boilerplate-y? Yeah, totally. But Swift simply isn't designed for what you're trying to do.

···

--
Brent Royal-Gordon
Architechies


(Yavuz Nuzumlalı) #6

And +1 for documentation. Do you have any guide about how dynamic casting
actually works? It would perfectly fit there.

And if there is no public guide, I think it would be great to have one
because as far as I see from the code, there are many different scenarios
and different behaviors for each scenario, and implementations are
implicitly affected by that.

I had to follow stack trace on crash, tried to find the method
corresponding to the ObjC type case, find them in source code and compare :slight_smile:

···

On Wed, 8 Jun 2016 at 22:17, Yavuz Nuzumlalı <manuyavuz@gmail.com> wrote:

Hmm, interesting.

So, we can say that NSProxy class, which is the base structure for
implementing proxy pattern, will not work for a Swift type, right? And I
simply can't use proxy pattern if real object can be a Swift type.

In my use case, I have a framework, and I want to switch developer's some
delegate object with my NSProxy standin, listen delegate method myself,
then forward to the developer's original delegate object.

It was working without any issues for delegate objects which are
Objective-C classes. But, it fails for Swift types because dynamic casting
does not work anymore.

So, do you have any suggestions about how can I handle this interception
functionality without NSProxy?

I don't want to do swizzling :slight_smile:

Thanks for the explanations!

On Wed, 8 Jun 2016 at 21:13, Jordan Rose <jordan_rose@apple.com> wrote:

On Jun 8, 2016, at 10:52, Joe Groff via swift-dev <swift-dev@swift.org> >> wrote:

On Jun 8, 2016, at 12:44 AM, Yavuz Nuzumlalı via swift-dev < >> swift-dev@swift.org> wrote:

Hi all,

swift_dynamicCastClassUnconditional and
swift_dynamicCastObjCClassUnconditional methods behave differently while
verifying casting.

swift_dynamicCastObjCClassUnconditional method calls -isKindOfClass:
method before falling back to object_getClass function.

swift_dynamicCastClassUnconditional method calls swift_dynamicCastClass
method which calls _swift_getClassOfAllocated method which calls directly
object_getClass function.

This causes problems if underlying object is an NSProxy subclass.

NSProxy class does not implement -isKindOfClass: method, so calls to this
method are forwarded to the real object through -forwardInvocation: method,
which causes -isKindOfClass: method to return answer according to the real
object's class.

However, object_getClass function directly accesses to the metadata of
the given object, so it returns NSProxy subclass.

I think this is a conflicting behavior, and I think
swift_dynamicCastClassUnconditional method should also verify first using
-isKindOfClass: method, in order to provide consistency.

This is intentional, since NSProxy instances are generally expected to be
standins for the proxied object. Important Cocoa idioms break down if the
"real" class is exposed instead of the class a proxy pretends to be.

For a little more detail, Swift relies on a bit more information about
layout of both instances and classes than Objective-C does, so an NSProxy
stand-in wouldn't work for a Swift type. (And by "wouldn't work" I mean
"would crash your program".) However, for an Objective-C type, all accesses
Swift does will always go through the Objective-C runtime, so it's safe to
use an Objective-C-style proxy, and indeed some Cocoa APIs expect this.

This is probably something we could stand to document more explicitly,
but I'm not sure where.

Jordan