unsafeBitCast to Unimplemented Class


(Saagar Jha) #1

Hello,

I’m having an issue migrating some old Objective-C code that looks like this:

@implementation Foo

- (void)load {
  // Swizzle one of Bar’s methods to call Foo’s baz method
}

- (void)baz {
  [self baz];
  if ([self isKindOfClass:NSClassFromString(@“Bar”)]) {
    Bar *bar = (Bar *)self; // I can’t migrate this
    // work with bar
  }
}

@end

I’m trying to cast self to a Bar at runtime, and use it to call Bar’s methods. Sounds like an easy to task for unsafeBitCast, right? The issue is that I don’t have access to the implementation of Bar’s class at compile time (this is a plugin, so it’s loaded by another application which contains Bar). In Objective-C I can create a header and stick a dummy interface for Bar in it; the cast will work if Bar exists at runtime. However, in Swift, unsafeBitCast requires me to use Bar.self, which does not exist. Is there any way to get this cast to work?

Thanks,
Saagar Jha


(Dave Abrahams) #2

Bar.self exists if you have a declaration of it exposed to swift, which
would be required for all the “work with bar” code above. If you don't
have Bar.self, it's because there's no declaration visible to your Swift
code. My usual workaround would be to declare an @objc protocol having
the bar APIs you want to use, and then cast self to an instance of that
@objc protocol. The right way to do that is by using
UnsafePointer.withMemoryRebound(to: ), e.g.

      var mutableSelf = self // can't get pointer to immutable value
      withUnsafePointer(to: &mutableSelf) { selfPtr in
        selfPtr.withMemoryRebound(to: BarProtocol, capacity: 1) { barPtr in
          let bar = barPtr.pointee
          // work with bar
        }
      }

HTH,

···

on Fri Feb 03 2017, Saagar Jha <swift-users-AT-swift.org> wrote:

Hello,

I’m having an issue migrating some old Objective-C code that looks like this:

@implementation Foo

- (void)load {
  // Swizzle one of Bar’s methods to call Foo’s baz method
}

- (void)baz {
  [self baz];
  if ([self isKindOfClass:NSClassFromString(@“Bar”)]) {
    Bar *bar = (Bar *)self; // I can’t migrate this
    // work with bar
  }
}

@end

I’m trying to cast self to a Bar at runtime, and use it to call Bar’s methods. Sounds like an easy
to task for unsafeBitCast, right? The issue is that I don’t have access to the implementation of
Bar’s class at compile time (this is a plugin, so it’s loaded by another application which contains
Bar). In Objective-C I can create a header and stick a dummy interface for Bar in it; the cast will
work if Bar exists at runtime. However, in Swift, unsafeBitCast requires me to use Bar.self, which
does not exist. Is there any way to get this cast to work?

--
-Dave


(Saagar Jha) #3

Thanks–your not only did you method work, it had the side effect of obviating the need for a Bridging Header. One more thing: what happens if self isn’t a Bar (crash, I’m guessing)? Is there a way to compare the type of self, other than using `is` (which has the same issue as unsafeBitCast in that I don’t have the declaration for it)?

Saagar Jha

···

On Feb 4, 2017, at 4:02 PM, Dave Abrahams via swift-users <swift-users@swift.org> wrote:

on Fri Feb 03 2017, Saagar Jha <swift-users-AT-swift.org <http://swift-users-at-swift.org/>> wrote:

Hello,

I’m having an issue migrating some old Objective-C code that looks like this:

@implementation Foo

- (void)load {
  // Swizzle one of Bar’s methods to call Foo’s baz method
}

- (void)baz {
  [self baz];
  if ([self isKindOfClass:NSClassFromString(@“Bar”)]) {
    Bar *bar = (Bar *)self; // I can’t migrate this
    // work with bar
  }
}

@end

I’m trying to cast self to a Bar at runtime, and use it to call Bar’s methods. Sounds like an easy
to task for unsafeBitCast, right? The issue is that I don’t have access to the implementation of
Bar’s class at compile time (this is a plugin, so it’s loaded by another application which contains
Bar). In Objective-C I can create a header and stick a dummy interface for Bar in it; the cast will
work if Bar exists at runtime. However, in Swift, unsafeBitCast requires me to use Bar.self, which
does not exist. Is there any way to get this cast to work?

Bar.self exists if you have a declaration of it exposed to swift, which
would be required for all the “work with bar” code above. If you don't
have Bar.self, it's because there's no declaration visible to your Swift
code. My usual workaround would be to declare an @objc protocol having
the bar APIs you want to use, and then cast self to an instance of that
@objc protocol. The right way to do that is by using
UnsafePointer.withMemoryRebound(to: ), e.g.

     var mutableSelf = self // can't get pointer to immutable value
     withUnsafePointer(to: &mutableSelf) { selfPtr in
       selfPtr.withMemoryRebound(to: BarProtocol, capacity: 1) { barPtr in
         let bar = barPtr.pointee
         // work with bar
       }
     }

HTH,

--
-Dave

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Dave Abrahams) #4

Thanks–your not only did you method work, it had the side effect of
obviating the need for a Bridging Header.

Uh, wait: this doesn't add up. If you needed a bridging header before,
surely it was so that you could get a declaration for Bar? If that's
the case, you shouldn't be using this protocol hack.

One more thing: what happens if self isn’t a Bar (crash, I’m
guessing)?

No, it should just be using ObjC method dispatch (a.k.a. duck-typing)
under the covers. If you have a method with the right signature, things
just work.

Is there a way to compare the type of self, other than using `is`
(which has the same issue as unsafeBitCast in that I don’t have the
declaration for it)?

I think you might be able to use something like

  class_getName(type(of: x))

···

on Sat Feb 04 2017, Saagar Jha <saagar-AT-saagarjha.com> wrote:

Saagar Jha

On Feb 4, 2017, at 4:02 PM, Dave Abrahams via swift-users <swift-users@swift.org> wrote:

on Fri Feb 03 2017, Saagar Jha <swift-users-AT-swift.org <http://swift-users-at-swift.org/>> > wrote:

Hello,

I’m having an issue migrating some old Objective-C code that looks like this:

@implementation Foo

- (void)load {
  // Swizzle one of Bar’s methods to call Foo’s baz method
}

- (void)baz {
  [self baz];
  if ([self isKindOfClass:NSClassFromString(@“Bar”)]) {
    Bar *bar = (Bar *)self; // I can’t migrate this
    // work with bar
  }
}

@end

I’m trying to cast self to a Bar at runtime, and use it to call Bar’s methods. Sounds like an easy
to task for unsafeBitCast, right? The issue is that I don’t have access to the implementation of
Bar’s class at compile time (this is a plugin, so it’s loaded by another application which contains
Bar). In Objective-C I can create a header and stick a dummy interface for Bar in it; the cast will
work if Bar exists at runtime. However, in Swift, unsafeBitCast requires me to use Bar.self, which
does not exist. Is there any way to get this cast to work?

Bar.self exists if you have a declaration of it exposed to swift, which
would be required for all the “work with bar” code above. If you don't
have Bar.self, it's because there's no declaration visible to your Swift
code. My usual workaround would be to declare an @objc protocol having
the bar APIs you want to use, and then cast self to an instance of that
@objc protocol. The right way to do that is by using
UnsafePointer.withMemoryRebound(to: ), e.g.

     var mutableSelf = self // can't get pointer to immutable value
     withUnsafePointer(to: &mutableSelf) { selfPtr in
       selfPtr.withMemoryRebound(to: BarProtocol, capacity: 1) { barPtr in
         let bar = barPtr.pointee
         // work with bar
       }
     }

HTH,

--
-Dave

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

<https://lists.swift.org/mailman/listinfo/swift-users>

--
-Dave


(Andrew Trick) #5

Thanks–your not only did you method work, it had the side effect of
obviating the need for a Bridging Header.

Uh, wait: this doesn't add up. If you needed a bridging header before,
surely it was so that you could get a declaration for Bar? If that's
the case, you shouldn't be using this protocol hack.

One more thing: what happens if self isn’t a Bar (crash, I’m
guessing)?

No, it should just be using ObjC method dispatch (a.k.a. duck-typing)
under the covers. If you have a method with the right signature, things
just work.

I don’t want to encourage reinterpreting a reference to `Bar` as a reference to `BarProtocol`, when `Bar` does not conform to the protocol according to the type system. Yes, it works for ObjC dispatch, and it’s done as an internal workaround in a couple places, but I would hesitate to say that’s it’s generally supported.

Is a missing declaration a use case that needs to be supported? Wouldn’t it be more proper to use selector based dispatch in those cases?

-Andy

···

On Feb 5, 2017, at 9:01 AM, Dave Abrahams via swift-users <swift-users@swift.org> wrote:
on Sat Feb 04 2017, Saagar Jha <saagar-AT-saagarjha.com <http://saagar-at-saagarjha.com/>> wrote:

Is there a way to compare the type of self, other than using `is`
(which has the same issue as unsafeBitCast in that I don’t have the
declaration for it)?

I think you might be able to use something like

class_getName(type(of: x))


(Dave Abrahams) #6

Is a missing declaration a use case that needs to be supported?

I couldn't say.

Wouldn’t it be more proper to use selector based dispatch in those
cases?

Example, please? I don't know what that means, though I probably should.

···

on Mon Feb 06 2017, Andrew Trick <atrick-AT-apple.com> wrote:

--
-Dave


(Andrew Trick) #7

I phrased that as a question because I'm the last person who should be giving advice here... What I had in mind is this:

if ([self isKindOfClass:NSClassFromString(@“Bar”)]) {
  self.perform(@selector(FakeBarProtocol.foo))
}

It's not type safe, but it's a lot better than outright lying about the reference's dynamic type.

Of course, the root problem is that Bar's declaration is unavailable, and that's not a normal, expected thing.

-Andy

···

On Feb 6, 2017, at 8:51 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Mon Feb 06 2017, Andrew Trick <atrick-AT-apple.com> wrote:

Is a missing declaration a use case that needs to be supported?

I couldn't say.

Wouldn’t it be more proper to use selector based dispatch in those
cases?

Example, please? I don't know what that means, though I probably should.

--
-Dave


(Dave Abrahams) #8

Presumably there's a way to express that in Swift, but if it uses
varargs I expect it's pretty darned inefficient.

···

on Mon Feb 06 2017, Andrew Trick <swift-users-AT-swift.org> wrote:

On Feb 6, 2017, at 8:51 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Mon Feb 06 2017, Andrew Trick <atrick-AT-apple.com> wrote:

Is a missing declaration a use case that needs to be supported?

I couldn't say.

Wouldn’t it be more proper to use selector based dispatch in those
cases?

Example, please? I don't know what that means, though I probably should.

--
-Dave

I phrased that as a question because I'm the last person who should be
giving advice here... What I had in mind is this:

if ([self isKindOfClass:NSClassFromString(@“Bar”)]) {
  self.perform(@selector(FakeBarProtocol.foo))
}

It's not type safe, but it's a lot better than outright lying about
the reference's dynamic type.

--
-Dave


(Saagar Jha) #9

Saagar Jha

Is a missing declaration a use case that needs to be supported?

I couldn't say.

Wouldn’t it be more proper to use selector based dispatch in those
cases?

Example, please? I don't know what that means, though I probably should.

--
-Dave

I phrased that as a question because I'm the last person who should be giving advice here... What I had in mind is this:

if ([self isKindOfClass:NSClassFromString(@“Bar”)]) {
self.perform(@selector(FakeBarProtocol.foo))
}

It's not type safe, but it's a lot better than outright lying about the reference's dynamic type.

Of course, the root problem is that Bar's declaration is unavailable, and that's not a normal, expected thing.

Yep, it’s not–that’s why I need to go through this trouble :slight_smile: My plugin is loaded at runtime, so the headers are the best I’ve got (though if you’ve got a way to perform a stricter check, I’m all ears!) Until then, all of these methods appear to be sugarcoating around perform(selector:)–is there any “preferred” way to do this?

Also, class_getName(_:slight_smile: seems to return a UnsafePointer<Int8>, is this just a C-style string? Should I use this over NSStringFromClass or isKindOfClass?

···

On Feb 6, 2017, at 10:57 PM, Andrew Trick <atrick@apple.com> wrote:

On Feb 6, 2017, at 8:51 PM, Dave Abrahams <dabrahams@apple.com> wrote:
on Mon Feb 06 2017, Andrew Trick <atrick-AT-apple.com> wrote:

-Andy


(Andrew Trick) #10

I phrased that as a question because I'm the last person who should be
giving advice here... What I had in mind is this:

if ([self isKindOfClass:NSClassFromString(@“Bar”)]) {
self.perform(@selector(FakeBarProtocol.foo))
}

It's not type safe, but it's a lot better than outright lying about
the reference's dynamic type.

Presumably there's a way to express that in Swift, but if it uses
varargs I expect it's pretty darned inefficient.

--
-Dave

Um, this is the untyped dispatch in Swift:

self.perform(@selector(FakeBarProtocol.foo))

It just wraps a string literal up and sends it to NSObject.perform.

I didn’t get around to rewriting the line above it in Swift...

if ([self isKindOfClass:NSClassFromString(@“Bar”)]) {

Which is now the next question...

Also, class_getName(_:slight_smile: seems to return a UnsafePointer<Int8>, is this just a C-style string? Should I use this over NSStringFromClass or isKindOfClass?

Yes, it returns “raw” C string as an UnsafePointer. I really don’t know these API's, but the other two you mention would seem to avoid Unsafe code. That’s a good thing.

Yep, it’s not–that’s why I need to go through this trouble :slight_smile: My plugin is loaded at runtime, so the headers are the best I’ve got (though if you’ve got a way to perform a stricter check, I’m all ears!) Until then, all of these methods appear to be sugarcoating around perform(selector:)–is there any “preferred” way to do this?

I would guess that the right way to do this is to declare an ObjC protocol that will be imported via a bridging header. Whatever types are defined by your plugin should just conform to that protocol.

Beyond that, I’ll have to appeal to others on the list for suggestions.

-Andy

···

On Feb 7, 2017, at 3:44 PM, Dave Abrahams via swift-users <swift-users@swift.org> wrote: