Swift Objc interop - Passing a Bool to a objc function erases the type

(Mo) #1

I have a Swift API that is exposed to Objc using a protocol marked as "@objc". the API is expected to take "Any?", however, when I pass a Bool looks like the type is erased (only if marked with "@objc" ) and looks like internally it creates an objc object with the CoreFoundation __kCFBooleanFalse/True. See simplified example below (also here: https://gist.github.com/mofarajmandi/fced89cc0c37ee1429513e1c191e667a):

import Foundation

protocol SwiftProtocol {
    func passAnyAround(_ input: Any?)
}

@objc public protocol ObjcProtocol {
    func passAnyAround(_ input: Any?)
}

class SwiftClassA: SwiftProtocol {
    func passAnyAround(_ input: Any?) {
        print("---------------")
        dump(input)
        let isBool = input is Bool
        print(isBool)
        let isInt = input is Int
        print(isInt)
        print(type(of: input))
        print("---------------")
    }
}

@objc class ObjcClassA: NSObject, ObjcProtocol {
    @objc func passAnyAround(_ input: Any?) {
        print("---------------")
        dump(input)
        let isBool = input is Bool
        print(isBool)
        let isInt = input is Int
        print(isInt)
        print(type(of: input))
        print("---------------")
    }
}

let boolValue = false

// retains the Bool type
let swiftA: SwiftProtocol = SwiftClassA()
swiftA.passAnyAround(boolValue)

// This version earases the type
let objcA: ObjcProtocol = ObjcClassA()
objcA.passAnyAround(boolValue)

// version2 (not declaring the object as of type ObjcProtocol) retains the Bool type
let objcB = ObjcClassA()
objcB.passAnyAround(boolValue)

I could relate to why this would be the case due to internal implementation & objc limitations, but to confirm, is this the expected behaviour or bug?
If yes, why is version#2 (not declaring the variable of type explicitly as of type ObjcProtocol) still works?

Regardless, I'd appreciate any recommendations to supporting Objc users and not losing the Bool type (at least for Swift users)
Notice that this is simplified case, I'd still like to retain the Bool type even if the Bool value is embedded in a Dictionary

(Quinn “The Eskimo!”) #2

is this the expected behaviour or bug?

This makes sense to me. Imagine if ObjcProtocol was implemented by an Objective-C class. The equivalent to Any would be id, which means the method signature looks like this:

- (void)passAnyAround:(id _Nullable)input

There’s no way to put a proper Boolean (Bool in Swift, BOOL in Objective-C) into the id type, so instead the compiler creates an NSNumber from the Boolean and passes that.

why is version#2 … still works?

Because in that case the compiler isn’t going through the protocol, but instead is calling the Swift class directly, and Swift’s Any? will hold a Boolean just fine.

Regardless, I'd appreciate any recommendations to supporting Objc
users and not losing the Bool type …

It’s hard to offer concrete advice without knowing more about the context. In general I recommend that you avoid Any completely, because it runs counter to Swift’s core concept of using types to enforce compile-time correctness. If you can explain more about your bigger picture goals there may be better options.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like
(Mo) #3

This makes sense. Thanks for confirmation @eskimo. In this case, and this particular hypothetical argument of the API needs to particularly accept Any, and the argument is subsequently passed into a JSCore so type safety is less of a concern in this case. I'd likely provide a different (more limiting) flavor of the API to support Objc users.