Swift: перевірте, чи відповідає загальний тип протоколу


80

У мене є протокол, який я визначив так:

protocol MyProtocol {
   ...
}

У мене також є загальна структура:

struct MyStruct <T>  {
    ...
}

Нарешті, у мене є загальна функція:

func myFunc <T> (s: MyStruct<T>) -> T? {
   ...
}

Я хотів би протестувати всередині функції, якщо тип T відповідає MyProtocol. По суті, я хотів би мати можливість (~ псевдокод):

let conforms = T.self is MyProtocol

Але це видає помилку компілятора:

error: cannot downcast from 'T.Type' to non-@objc protocol type 'MyProtocol'
   let conforms = T.self is MyProtocol
                  ~~~~~~ ^  ~~~~~~~~~~

Я також пробував варіанти, як T.self is MyProtocol.self, T is MyProtocolі використовуючи ==замість is. Поки що я нікуди не дійшов. Будь-які ідеї?

Відповіді:


80

Я повинен сказати, що @Alex хоче перевірити, чи Tтип відповідає протоколу, а не s. А якийсь відповідач не бачив чітко.

TТип перевірки відповідає протоколу так:

if let _ = T.self as? MyProtocol.Type {
    //  T conform MyProtocol
}

або

if T.self is MyProtocol.Type {
    //  T conform MyProtocol
}

10
Що робити, якщо MyProtocol є CaseIterable (протокол з відповідним типом)? Він показує помилку: "CaseIterable" може використовуватися лише як загальне обмеження, оскільки воно має вимоги до власного або пов'язаного типу.
zgjie

75

Трохи пізно, але ви можете перевірити, чи щось відповідає протоколу as ?тестом:

if let currentVC = myViewController as? MyCustomProtocol {
    // currentVC responds to the MyCustomProtocol protocol =]
}

РЕДАКТУВАТИ: трохи коротше:

if let _ = self as? MyProtocol {
    // match
}

І за допомогою захисного пристрою:

guard let _ = self as? MyProtocol else {
    // doesn't match
    return
}

4
Для цього потрібен екземпляр T, але питання задає загальний тип. Таким чином Carlos відповідь краще: stackoverflow.com/a/52787263/1311272
Sajjon

Відповідь Карлоса Шагендо повинна бути прийнятою відповіддю IMO.
J.beenie

Це не відповідає на запитання
Джонатан.

36

Найпростіша відповідь: не роби цього. Замість цього використовуйте перевантаження та обмеження та визначайте все заздалегідь під час компіляції, а не динамічно тестуйте матеріали під час виконання. Перевірка типу виконання та генерики під час компіляції схожі на стейк та морозиво - і те, і інше приємно, але змішувати їх трохи дивно.

Розглянемо щось подібне:

protocol MyProtocol { }

struct MyStruct <T>  { let val: T }

func myFunc<T: MyProtocol>(s: MyStruct<T>) -> T? {
    return s.val
}

func myFunc<T>(s: MyStruct<T>) -> T? {
    return nil
}

struct S1: MyProtocol { }
struct S2 { }

let m1 = MyStruct(val: S1())
let m2 = MyStruct(val: S2())

myFunc(m1) // returns an instance of S1
myFunc(m2) // returns nil, because S2 doesn't implement MyProtocol

Недоліком є ​​те, що ви не можете встановити динамічно, якщо T підтримує протокол під час виконання:

let o: Any = S1()
let m3 = MyStruct(val: o)
myFunc(m3)  // will return nil even though o 
            // does actually implement MyProtocol

Але, чесно кажучи, вам справді потрібно це робити всередині вашої загальної функції? Якщо ви не впевнені, що це за фактичний тип, кращим варіантом може бути з’ясувати це заздалегідь, а не відкладати його пізніше і підштовхувати всередину загальної функції, щоб з’ясувати.


17
+1, хороша відповідь. Особливо сподобалося: " Перевірка типу виконання та загальні засоби під час компіляції схожі на стейк та морозиво - і те, і інше приємно, але змішувати їх трохи дивно ". 👍
Стюарт,

6
Так ... за винятком крайових випадків, коли перевантаження та обмеження просто не збираються робити цю роботу. Розглянемо метод розширення, який реалізує протокол JSONEncodable, що вимагає init(json: JSON) throws. Ми хочемо Arrayреалізувати JSONEncodable, але лише якщо його елементи також JSONEncodable. Ми не можемо поєднувати речення про спадкування з обмеженнями, тому ми повинні використовувати якусь перевірку типу всередині нашої реалізації initта, можливо, викинути помилку, якщо Elementце не так JSONEncodable. На жаль, це, здається, неможливо AFAICT.
Грегорі Хіглі

Я повинен додати, що загадка, наведена вище, може бути вирішена за допомогою проміжного типу як дурня, але це досить неелегантне рішення.
Грегорі Хіглі

@GregoryHigley, що має бути можливим вже зараз за умови умовної відповідності в Swift 4.1 ( swift.org/blog/conditional-conformance )
mj_jimenez

Це здорово, як ти можеш побудувати MyStructз натяком або без нього, <Type>і він може сказати, що робити. Для інших, які випробовують код, Swift 4 потребує _першого аргументу конструктора
snakeoil

13

Ви також можете використати відповідність шаблону регістру комутатора Swift , якщо ви хочете обробляти кілька випадків типу T:

func myFunc<T>(s: MyStruct<T>) -> T? {
    switch s {
    case let sType as MyProtocol:
        // do MyProtocol specific stuff here, using sType
    default:
        //this does not conform to MyProtocol
    ...
    }
}

Це також не відповідає на запитання, оскільки тестування в екземплярі відповідає протоколу, а не типу безпосередньо.
Джонатан.


5

Вам потрібно оголосити протокол як @objc:

@objc protocol MyProtocol {
    ...
} 

З книги "Швидка мова програмування" від Apple:

Ви можете перевірити відповідність протоколу лише в тому випадку, якщо ваш протокол позначений атрибутом @objc, як це показано для протоколу HasArea вище. Цей атрибут вказує на те, що протокол повинен піддаватися дії коду Objective-C, і описаний у розділі Використання Swift з какао та Objective-C. Навіть якщо ви не взаємодієте з Objective-C, вам потрібно позначити свої протоколи атрибутом @objc, якщо ви хочете мати можливість перевірити відповідність протоколу.

Зауважте також, що протоколи @objc можуть бути прийняті лише класами, а не структурами чи переліками. Якщо ви позначите свій протокол як @objc, щоб перевірити відповідність, ви зможете застосувати цей протокол лише до типів класів.


Навіть при цьому я все одно отримую ту ж помилку. @objc protocol MyProtocol {} struct MyStruct <T> {} func myFunc <T> (s: MyStruct<T>) -> T? { let conforms = T.self is MyProtocol }
Олексій

1
@Alex, вам потрібно побудувати екземпляр типу T, перш ніж ви зможете перевірити відповідність протоколу (як я знаю). Якщо вам потрібно, ніж тип T повинен бути лише типу, що відповідає MyProtocol, ви можете вказати це:func myFunc<T: MyProtocol>(...) -> T?
rabbitinspace

0

Для тестових випадків я перевіряю відповідність таким чином:

let conforms: Bool = (Controller.self as Any) is Protocol.Type

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.