将*通用*协议桥接到 Objective-C 的解决方法?

Workaround to bridge a *generic* protocol to Objective-C?

假设我们有以下 Objective-C API:

- (id)foo:(Protocol *)proto;

导入 Swift 为:

func foo(_ proto: Protocol) -> Any

是的,这是为我们提供代理对象的原因之一。这些在 Swift 中使用起来往往很烦人,所以假设我们想围绕这个东西做一个包装,让它更友好一些。首先我们定义几个 Objective-C 兼容的协议:

@objc protocol Super {}
@objc protocol Sub: Super {}

现在,我们定义一个函数,它接受一个符合Super的协议并将其传递给foo(),然后我们以Sub作为参数调用它,看看是否有效:

func bar<P: Super>(proto: P.Type) {
    let proxy = foo(proto)

    // do whatever with the proxy
}

bar(proto: Sub.self)

好吧,这无法编译。给出的错误信息是:

error: cannot convert value of type 'P.Type' to expected argument type 'Protocol'

这里有一些(主要)编译的东西:

func bar<P: Super>(proto: P.Type) {
    // when called with 'Sub.self' as 'proto':

    print(type(of: proto))    // Sub.Protocol
    print(type(of: Sub.self)) // Sub.Protocol
    print(proto == Sub.self)  // true

    let proxy1 = foo(Sub.self) // compiles, runs, works
    let proxy2 = foo(proto) // error: cannot convert value of type 'P.Type' to expected argument type 'Protocol'
}

好的,它几乎在所有方面都与 Sub.self 相同,只是我不能将它传递给需要 Objective-C 协议的东西。嗯。

问题在于,尽管 Sub 符合 Super 意味着它必须是 Objective-C 协议,但编译器并未意识到这一点。我们可以解决这个问题并手动桥接它吗?好吧,让我们看看Protocol的界面,看看有什么我们可以...

OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
@interface Protocol : NSObject
@end

哦。嗯。

好吧,Protocol 是一个完整的 NSObject 子类这一事实表明,这可能是我最喜欢的功能的全部成果,神奇的 Swift<-> Objective-C 对事物执行非平凡转换的桥梁,而不是显而易见的事情。好吧,这至少给了我一个想法;我应该能够通过强制转换为 AnyObject 来手动调用桥,并希望通过 as!ing 或其他方式以这种方式获得 Protocol 对象。有用吗?

print(Sub.self as AnyObject)  // <Protocol: 0x012345678>

好吧,这很有希望。当我在我的通用参数上尝试它时?

print(proto as AnyObject) // Terminated due to signal: SEGMENTATION FAULT (11)

哦,来

我怀疑这可能是编译器中的一个错误,我打算测试一些东西以确定是否是这种情况,但是由于 Swift 源代码需要一个地质时代来编译,我想我我在等待的时候 post 在这里。有人对这里发生的事情有任何见解 and/or 解决方法吗?

我知道它不漂亮 "Swifty",确切地说,但是可以使用 NSProtocolFromString如下:

let proxy2 = foo(NSProtocolFromString(String(reflecting: proto))!)

其中 String(reflecting:) 是获取适合通过 NSProtocolFromString 解析的完全限定类型名称的有用方法。

我会说你遇到的崩溃是一个错误。

好的,在进一步调查之后,我确定它确实是一个编译器错误,并且 filed a report on it: SR-8129。似乎正在发生的是 Swift 编译器错误地假设 proto 将始终是具体 class 类型的元类型,因此它通过发出对 [=13 的调用来执行桥接=],它在遇到协议元类型时崩溃。当 Sub.self 显式转换为 AnyObject 时,编译器会发出 Swift._bridgeAnythingToObjectiveC<A>(A) -> Swift.AnyObject,这似乎是动态确定对象的类型并相应地桥接它。

考虑到这一点,解决方法就显而易见了:首先将 proto 转换为 Any,以破坏与泛型关联的类型信息并强制编译器发出 Swift._bridgeAnythingToObjectiveC<A>(A) -> Swift.AnyObject。事实上,这似乎有效:

func bar<P: Super>(proto: P.Type) {
    foo(proto as Any as AnyObject as! Protocol)
}

不是最漂亮的东西,但可能比通过字符串操作查找协议更可取。