Reputation: 1956
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;
}
Upvotes: 8
Views: 2804
Reputation: 326
More compact cast to bool:
let result = controlDelegate.perform(NSSelectorFromString("methodThatReturnsBOOL")) != nil
Upvotes: 0
Reputation: 5543
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.
Upvotes: 0
Reputation: 2383
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)>
Upvotes: 3
Reputation: 11112
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")
}
Upvotes: 1
Reputation: 1956
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.
Upvotes: 10