7

I am trying to get the result of a method of an existing Objective C class, called using performSelector

let result = controlDelegate.performSelector("methodThatReturnsBOOL") as? Bool

I need to cast this result to Bool type of Swift.

The code mentioned above, gives a warning

"Cast from 'Unmanaged!' to unrelated type 'Bool' always fails"

and the result is always "false", even when the method returns YES.

Any suggestions for converting the result to Bool ?

Signature for methodThatReturnsBOOL

- (BOOL)methodThatReturnsBOOL
{
    return YES;
}
UditS
  • 1,936
  • 17
  • 37
  • Is it possible to post the signature of `methodThatReturnsBOOL` ? – Alladinian Oct 08 '15 at 14:07
  • You cannot use performSelector() with a method that returns BOOL. From the documentation: *" For methods that return anything other than an object, use NSInvocation."* NSInvocation however is not available in Swift (as far as I know). – Martin R Oct 08 '15 at 14:22
  • @Alladinian : Question updated (it's just a simple Objective C method that returns YES/NO) – UditS Oct 08 '15 at 15:12
  • @MartinR : Yes, I looked into NSInvocation and it's not available in Swift (yet). Although, performSelector() is working fine, it's just a matter of how to capture the returned BOOL value. Any other suggestion on how to achieve this ? – UditS Oct 08 '15 at 15:17
  • Why do you need performSelector at all? – Martin R Oct 08 '15 at 16:48
  • 1
    We have a delegate of type **id** in an existing Objective C class. We need to `performSelector` on this delegate through a Swift class, which is in turn a subclass of existing Objective C class. – UditS Oct 09 '15 at 14:08

5 Answers5

10

It's been a long time since this has remained unanswered so I'm adding what I have learned along the way.

To convert a BOOL value returned by an Objective C method you can simply cast it using,

if let result = controlDelegate.performSelector("methodThatReturnsBOOL") {
    print("true")
} else {
    print("false")
}

Here you can also assign the value true/false to a Swift Bool, if required.

Note : I tried casting Unmanaged<AnyObject> directly to Bool using takeRetainedValue() as suggested by many answers on SO, but that doesn't seem to work in this scenario.

UditS
  • 1,936
  • 17
  • 37
2

You can't do what you want nicely in Swift. My issue with the accepted solution is that it takes advantage of the idea that 0x0 just so happens to be nil. This isn't actually guaranteed by Swift and Objective-C specifications. The same applies to boolean values since 0x0 being false and 0x1 being true is just an arbitrary implementation decision. Aside from being technically incorrect, it's also just awful code to understand. Without thinking about what a nil pointer is on most platforms (32/64 bits of zeros), what was suggested makes zero sense.

After talking to an engineer at WWDC '19 for a while, he suggested that you can actually use valueFor(forKey:) with the key being the function name/selector description. This works since the Obj-C runtime will actually execute any function with the given name/key in order to evaluate the expression. This is still a bit hacky since it requires knowledge of the Objective-C runtime, however it is guaranteed to be platform and implementation independent because valueFor(forKey:) returns an Any? which can be cast into an Int or a Bool without any trouble at all. By using the built in casts instead of speculating on what 0x0 or 0x1 means, we avoid the whole issue of interpreting a nil pointer.

Example:

@objc func doThing() -> Bool{
    return true
}

...

let target = someObjectWithDoThing
let selectorCallResult = target.value(forKey: "doThing")

let intResult = selectorCallResult as? Int //Optional<Int(1)>
let boolResult = selectorCallResult as? Bool //Optional<Bool(true)>
Allison
  • 2,213
  • 4
  • 32
  • 56
1

This is the solution in Swift 3, as the methods are a bit different. This method also checks if Objective-C object responds to selector, to avoid crashing.

import ObjectiveC

let selector = NSSelectorFromString("methodThatReturnsBOOL")

guard controlDelegate.responds(to: selector) else {
    return
}

if let result = controlDelegate.perform(selector) {
    print("true")
}
else {
    print("false")
}
Legoless
  • 10,942
  • 7
  • 48
  • 68
0

Similarly to my answer here this can be done with @convention(c) instead:

let selector: Selector = NSSelectorFromString("methodThatReturnsBOOL")
let methodIMP: IMP! = controlDelegate.method(for: selector)
let boolResult: Bool = unsafeBitCast(methodIMP,to:(@convention(c)(Any?,Selector)->Bool).self)(controlDelegate,selector)

This^ particular syntax is available since Swift 3.1, also possible with one extra variable in Swift 3.

Kamil.S
  • 5,205
  • 2
  • 22
  • 51
0

More compact cast to bool:

let result = controlDelegate.perform(NSSelectorFromString("methodThatReturnsBOOL")) != nil
Ku6ep
  • 316
  • 2
  • 8