Swift:检查泛型类型是否符合协议


78

我有一个这样定义的协议:

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.selfT is MyProtocol,并使用==代替is。到目前为止,我还没到任何地方。有任何想法吗?

Answers:


78

我不得不说@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'只能用作通用约束,因为它具有Self或关联的类型要求。
zgjie

73

有点晚了,但是您可以使用test测试某些东西是否响应协议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
这需要of的实例T,但问题是关于通用TYPE的。因此,卡洛斯的回答是更好的:stackoverflow.com/a/52787263/1311272
Sajjon

答案应该是IMO的Carlos Chaguendo答案。
J.beenie

这没有回答问题
乔纳森。

35

最简单的答案是:不要这样做。改用重载和约束,并在编译时预先确定所有内容,而不是在运行时动态测试。运行时类型检查和编译时泛型就像牛排和冰淇淋一样,都不错,但混合起来有点怪异。

考虑这样的事情:

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,很好的答案。特别喜欢“运行时类型检查和编译时泛型,例如牛排和冰淇淋-两者都很好,但将它们混合起来有点怪异。 ”👍–
Stuart,

6
是的。除了极端情况下,过载和约束根本无法完成任务。考虑一种实现协议的扩展方法JSONEncodable,要求init(json: JSON) throws。我们要Array实现JSONEncodable,但前提是它的元素也必须实现JSONEncodable。我们不能约束相结合的遗产条款,因此我们必须使用某种类型的我国实施的内部检查init,也许抛出一个错误,如果Element不是JSONEncodable。可悲的是,这似乎不可能实现。
格雷戈里·希格利

我应该补充一点,上述难题可以通过使用中间类型作为thunk来解决,但这是一个非常优雅的解决方案。
Gregory Higley'3

@GregoryHigley应4.1(现在有可能与条件符合斯威夫特swift.org/blog/conditional-conformance
mj_jimenez

MyStruct无论有没有提示都可以构建,这很酷<Type>,它可以告诉该怎么做。对于尝试代码的其他人,Swift 4需要_在第一个构造函数参数上使用
snakeoil '18

13

如果您要处理多个类型的案例,也可以利用swift的switch case模式匹配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 {
    ...
} 

摘自苹果公司的《 The Swift Programming Language》一书:

仅当您的协议标记有@objc属性时,才可以检查协议一致性,如上面的HasArea协议所示。此属性指示该协议应公开给Objective-C代码,并在将Swift与Cocoa和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 }
Alex

1
@Alex,您需要先构造T类型的实例,然后才能检查协议一致性(据我所知)。如果您需要类型T,那么它必须仅具有不符合MyProtocol的类型,则可以指定它:func myFunc<T: MyProtocol>(...) -> T?
Rabbitinspace

2

一个现代的答案将是这样的:(Swift 5.1)

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

4
?? 那如何回答这个问题?
Peter Schorn

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.