UditS
UditS

Reputation: 1956

Convert Unmanaged<AnyObject>! to Bool in Swift

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

Answers (5)

Ku6ep
Ku6ep

Reputation: 326

More compact cast to bool:

let result = controlDelegate.perform(NSSelectorFromString("methodThatReturnsBOOL")) != nil

Upvotes: 0

Kamil.S
Kamil.S

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

Allison
Allison

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

Legoless
Legoless

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

UditS
UditS

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

Related Questions