Metatypes are different than obj-c protocols, until they are not

It took me a while to wrap my head around this, so I think the code will show the situation better:

@objc(OBJCProto) protocol Proto {}

let objcProtocol = NSProtocolFromString("OBJCProto")

ObjectIdentifier(Proto.self) == ObjectIdentifier(objcProtocol!) // false
ObjectIdentifier(Proto.self as Any.Type) == ObjectIdentifier(objcProtocol!) // false
ObjectIdentifier(Proto.self as AnyObject) == ObjectIdentifier(objcProtocol!) // true
ObjectIdentifier(Proto.self as Protocol) == ObjectIdentifier(objcProtocol!) // true

NSStringFromProtocol(objcProtocol!) // OBJCProto
NSStringFromProtocol(Proto.self) // OBJCProto
String(reflecting: Proto.self) // __C.OBJCProto

String(reflecting: objcProtocol) // <Protocol 0x...>
String(reflecting: Proto.self) // __C.OBJCProto
String(reflecting: Proto.self as Any.Type) // __C.OBJCProto
String(reflecting: Proto.self as AnyObject) // <Protocol 0x...>
String(reflecting: Proto.self as Protocol) // <Protocol 0x...>

Metatypes are treated as different from protocols, unless the bridging kicks in when you use it as an AnyObject. This inconsistency caused me issues when I attempted to write a method that converted a metatype (or obj-c protocol) to a string using reflection, because the place the type was coming from was altering the result:

func identifier(forBridgedProtocol proto: Any) -> String {
    if let objcProtocol = object as? Protocol {
        return NSStringFromProtocol(objcProtocol)
    } else if let swiftMetatype = object as? Any.Type {
        return String(reflecting: swiftMetatype)
    } else {
        crash("Bridge identifiers must be metatypes -- got \(proto) of type \(type(of: proto))")

The fix was to always cast to AnyObject first:

    // We NEED to use this as an AnyObject to force Swift to convert metatypes
    // to their Objective-C counterparts. If we don't do this, they are treated as
    // different objects and we get different results.
    let object = proto as AnyObject

I was expecting String(reflecting: Proto.self) to return OBJCProto (the actual Obj-C name of the protocol), and for ObjectIdentifier(Proto.self) == ObjectIdentifier(objcProtocol!) to be true regardless of how I pass the metatype into it. I've found this inconsistency to be pretty confusing (especially since you don't know when the bridge is kicking in)

should add ! here, maybe a typo

Terms of Service

Privacy Policy

Cookie Policy