Обходной путь для соединения * общего * протокола с Objective-C? - PullRequest
0 голосов
/ 27 июня 2018

Допустим, у нас есть следующий API Objective-C:

- (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 голосов
/ 28 июня 2018

Хорошо, после более подробного изучения я определил, что это действительно ошибка компилятора, и подал отчет об этом: 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)
}

Не самая красивая вещь, но, вероятно, предпочтительнее, чем поискать протокол с помощью манипуляций со строками.

0 голосов
/ 27 июня 2018

Я знаю, что это не красиво и "Swifty", но получить Objective-C Protocol в соответствии с необходимостью для перехода в foo можно с помощью NSProtocolFromString следующим образом:

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

, где String(reflecting:) - это полезный способ получить полное имя типа, подходящее для разрешения с помощью NSProtocolFromString.

Я бы сказал, что сбой, с которым вы столкнулись, - это ошибка.

...