在 swift 中使用协议作为数组类型和函数参数
Posted
技术标签:
【中文标题】在 swift 中使用协议作为数组类型和函数参数【英文标题】:Usage of protocols as array types and function parameters in swift 【发布时间】:2014-07-22 13:21:06 【问题描述】:我想创建一个可以存储符合特定协议的对象的类。对象应存储在类型化数组中。根据 Swift 文档协议,可以用作类型:
因为它是一种类型,所以你可以在许多允许其他类型的地方使用协议,包括:
作为函数、方法或初始化程序中的参数类型或返回类型 作为常量、变量或属性的类型 作为数组、字典或其他容器中项目的类型
但是以下会产生编译器错误:
Protocol 'SomeProtocol' 只能用作通用约束,因为它具有 Self 或关联的类型要求
你应该如何解决这个问题:
protocol SomeProtocol: Equatable
func bla()
class SomeClass
var protocols = [SomeProtocol]()
func addElement(element: SomeProtocol)
self.protocols.append(element)
func removeElement(element: SomeProtocol)
if let index = find(self.protocols, element)
self.protocols.removeAtIndex(index)
【问题讨论】:
在 Swift 中有一类特殊的协议,它不提供对实现它的类型的多态性。此类协议在其定义中使用 Self 或 associatedtype(Equatable 就是其中之一)。在某些情况下,可以使用类型擦除的包装器来使您的集合同态。以here 为例。 【参考方案1】:您在 Swift 中遇到了一个协议问题的变体,目前还没有好的解决方案。
另请参阅Extending Array to check if it is sorted in Swift?,它包含有关如何解决可能适合您的特定问题的建议(您的问题非常笼统,也许您可以使用这些答案找到解决方法)。
【讨论】:
我认为这是目前的正确答案。 Nate 的解决方案有效,但并没有完全解决我的问题。【参考方案2】:您想创建一个泛型类,其类型约束要求与它一起使用的类符合SomeProtocol
,如下所示:
class SomeClass<T: SomeProtocol>
typealias ElementType = T
var protocols = [ElementType]()
func addElement(element: ElementType)
self.protocols.append(element)
func removeElement(element: ElementType)
if let index = find(self.protocols, element)
self.protocols.removeAtIndex(index)
【讨论】:
你将如何实例化该类的对象? Hmmm... 这种方式会锁定您使用符合SomeProtocol
-- let protocolGroup: SomeClass<MyMemberClass> = SomeClass()
的单一类型
这样只能将MyMemberClass
类的对象添加到数组中?
或let foo = SomeClass<MyMemberClass>()
@snod 是的,这不是你要找的。问题是Equatable
一致性 - 没有它,您可以使用您的确切代码。也许提交错误/功能请求?【参考方案3】:
在 Swift 中有一类特殊的协议,它不提供对实现它的类型的多态性。此类协议在其定义中使用 Self
或 associatedtype
关键字(Equatable
就是其中之一)。
在某些情况下,可以使用类型擦除的包装器来使您的集合同态。下面是一个例子。
// This protocol doesn't provide polymorphism over the types which implement it.
protocol X: Equatable
var x: Int get
// We can't use such protocols as types, only as generic-constraints.
func ==<T: X>(a: T, b: T) -> Bool
return a.x == b.x
// A type-erased wrapper can help overcome this limitation in some cases.
struct AnyX
private let _x: () -> Int
var x: Int return _x()
init<T: X>(_ some: T)
_x = some.x
// Usage Example
struct XY: X
var x: Int
var y: Int
struct XZ: X
var x: Int
var z: Int
let xy = XY(x: 1, y: 2)
let xz = XZ(x: 3, z: 4)
//let xs = [xy, xz] // error
let xs = [AnyX(xy), AnyX(xz)]
xs.forEach print($0.x) // 1 3
【讨论】:
【参考方案4】:我发现的有限解决方案是将协议标记为仅类协议。这将允许您使用“===”运算符比较对象。 我知道这不适用于结构等,但在我的情况下已经足够了。
protocol SomeProtocol: class
func bla()
class SomeClass
var protocols = [SomeProtocol]()
func addElement(element: SomeProtocol)
self.protocols.append(element)
func removeElement(element: SomeProtocol)
for i in 0...protocols.count
if protocols[i] === element
protocols.removeAtIndex(i)
return
【讨论】:
如果addElement
被同一个对象多次调用,这是否允许protocols
中的重复条目?
是的,swift 中的数组可能包含重复的条目。如果您认为这可能发生在您的代码中,那么要么使用 Set 而不是数组,要么确保该数组不包含该对象。
如果你想避免重复,你可以在添加新元素之前调用removeElement()
。
我的意思是你如何控制你的阵列是悬而未决的,对吧?谢谢你的回答【参考方案5】:
解决方法很简单:
protocol SomeProtocol
func bla()
class SomeClass
init()
var protocols = [SomeProtocol]()
func addElement<T: SomeProtocol where T: Equatable>(element: T)
protocols.append(element)
func removeElement<T: SomeProtocol where T: Equatable>(element: T)
protocols = protocols.filter
if let e = $0 as? T where e == element
return false
return true
【讨论】:
您错过了重要的事情:OP 希望协议继承Equatable
协议。它有很大的不同。
@werediver 我不这么认为。他想将符合SomeProtocol
的对象存储在类型化数组中。 Equatable
一致性仅用于从数组中删除元素。我的解决方案是@almas 解决方案的改进版本,因为它可以与任何符合Equatable
协议的Swift 类型一起使用。【参考方案6】:
我认为您的主要目标是保存符合某种协议的对象集合,添加到该集合中并从中删除。这是您的客户端“SomeClass”中所述的功能。 Equatable 继承需要 self ,而此功能不需要。我们本可以在 Obj-C 中的数组中使用可以采用自定义比较器的“索引”函数来完成这项工作,但这在 Swift 中不受支持。所以最简单的解决方案是使用字典而不是数组,如下面的代码所示。我提供了 getElements() ,它将返回您想要的协议数组。所以任何使用 SomeClass 的人都不会知道使用了字典来实现。
因为在任何情况下,您都需要一些可区分的属性来分隔您的对象,我假设它是“名称”。当你创建一个新的 SomeProtocol 实例时,请确保你的 do element.name = "foo" 。如果未设置名称,您仍然可以创建实例,但不会将其添加到集合中,并且 addElement() 将返回“false”。
protocol SomeProtocol
var name:String? get set // Since elements need to distinguished,
//we will assume it is by name in this example.
func bla()
class SomeClass
//var protocols = [SomeProtocol]() //find is not supported in 2.0, indexOf if
// There is an Obj-C function index, that find element using custom comparator such as the one below, not available in Swift
/*
static func compareProtocols(one:SomeProtocol, toTheOther:SomeProtocol)->Bool
if (one.name == nil) return false
if(toTheOther.name == nil) return false
if(one.name == toTheOther.name!) return true
return false
*/
//The best choice here is to use dictionary
var protocols = [String:SomeProtocol]()
func addElement(element: SomeProtocol) -> Bool
//self.protocols.append(element)
if let index = element.name
protocols[index] = element
return true
return false
func removeElement(element: SomeProtocol)
//if let index = find(self.protocols, element) // find not suported in Swift 2.0
if let index = element.name
protocols.removeValueForKey(index)
func getElements() -> [SomeProtocol]
return Array(protocols.values)
【讨论】:
【参考方案7】:我在那篇博文中发现了一个not纯 Swift 解决方案: http://blog.inferis.org/blog/2015/05/27/swift-an-array-of-protocols/
诀窍是遵循NSObjectProtocol
,因为它引入了isEqual()
。
因此,您可以编写自己的函数来查找元素并将其删除,而不是使用 Equatable
协议及其默认用法 ==
。
这是您的find(array, element) -> Int?
函数的实现:
protocol SomeProtocol: NSObjectProtocol
func find(protocols: [SomeProtocol], element: SomeProtocol) -> Int?
for (index, object) in protocols.enumerated()
if (object.isEqual(element))
return index
return nil
注意:在这种情况下,符合SomeProtocol
的对象必须继承自NSObject
。
【讨论】:
以上是关于在 swift 中使用协议作为数组类型和函数参数的主要内容,如果未能解决你的问题,请参考以下文章