解决方案,以将*通用*协议桥接到Objective-C?

时间:2018-06-26 23:12:15

标签: swift objective-c-runtime

假设我们具有以下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来手动调用网桥,并希望通过Protocol对其进行某种操作来到达as!对象。可以吗?

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

嗯,那很有希望。当我在通用参数上尝试使用它时?

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

哦,快来。

我怀疑这可能是编译器中的错误,并且我计划测试一些事情以确定是否是这种情况,但是由于Swift来源需要地质年代才能进行编译,因此我想我会在此发布此内容,我在等待。任何人都对这里发生的事情有任何见解和/或解决方法?

2 个答案:

答案 0 :(得分:2)

我知道它确实不漂亮而且“敏捷”,但是可以使用Protocol来实现将Objective-C foo适当地传递到NSProtocolFromString的目的:

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

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

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

答案 1 :(得分:2)

好的,在进一步研究之后,我确定它确实是编译器错误,并且有filed a report on it: SR-8129。似乎正在发生的事是,Swift编译器错误地假设proto始终是具体class类型的元类型,因此它通过发出对swift_getObjCClassFromMetadata的调用来执行桥接遇到协议元类型时崩溃。将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)
}

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