ios 在swift中使用协议作为数组类型和函数参数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24888560/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 01:29:48  来源:igfitidea点击:

Usage of protocols as array types and function parameters in swift

iosswiftgenericsswift-protocols

提问by snod

I want to create a class that can store objects conforming to a certain protocol. The objects should be stored in a typed array. According to the Swift documentation protocols can be used as types: 

我想创建一个可以存储符合某个协议的对象的类。对象应存储在类型化数组中。根据 Swift 文档协议可以用作类型: 

Because it is a type, you can use a protocol in many places where other types are allowed, including:

  • As a parameter type or return type in a function, method, or initializer
  • As the type of a constant, variable, or property
  • As the type of items in an array, dictionary, or other container

因为它是一种类型,所以您可以在许多允许其他类型的地方使用协议,包括:

  • 作为函数、方法或初始值设定项中的参数类型或返回类型
  • 作为常量、变量或属性的类型
  • 作为数组、字典或其他容器中项目的类型

However the following generates compiler errors:

但是,以下会产生编译器错误:

Protocol 'SomeProtocol' can only be used as a generic constraint because it has Self or associated type requirements

协议 'SomeProtocol' 只能用作通用约束,因为它具有 Self 或相关的类型要求

How are you supposed to solve this:

你应该如何解决这个问题:

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)
        }
    }
}

采纳答案by DarkDust

You've hit a variant of a problem with protocols in Swift for which no good solution exists yet.

您在 Swift 中遇到了协议问题的一个变体,目前还没有好的解决方案。

See also Extending Array to check if it is sorted in Swift?, it contains suggestions on how to work around it that may be suitable for your specific problem (your question is very generic, maybe you can find a workaround using these answers).

另请参阅扩展数组以检查它是否在 Swift 中排序?,它包含有关如何解决它的建议,这些建议可能适合您的特定问题(您的问题非常笼统,也许您可​​以使用这些答案找到解决方法)。

回答by Nate Cook

You want to create a generic class, with a type constraint that requires the classes used with it conform to SomeProtocol, like this:

您想创建一个泛型类,其类型约束要求与其一起使用的类符合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)
        }
    }
}

回答by werediver

In Swift there is a special class of protocols which doesn't provide polymorphism over the types which implement it. Such protocols use Selfor associatedtypekeywords in their definitions (and Equatableis one of them).

在 Swift 中有一类特殊的协议,它不提供实现它的类型的多态性。此类协议在其定义中使用Selforassociatedtype关键字(并且Equatable是其中之一)。

In some cases it's possible to use a type-erased wrapper to make your collection homomorphic. Below is an example.

在某些情况下,可以使用类型擦除包装器使您的集合同态。下面是一个例子。

// 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(
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
            }
        }
    }

}
.x) } // 1 3

回答by almas

The limited solution that I found is to mark the protocol as a class-only protocol. This will allow you to compare objects using '===' operator. I understand this won't work for structs, etc., but it was good enough in my case.

我发现的有限解决方案是将该协议标记为仅限类的协议。这将允许您使用“===”运算符比较对象。我知道这不适用于结构等,但在我的情况下已经足够了。

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 = 
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)
    }
}
as? T where e == element { return false } return true } } }

回答by bzz

The solution is pretty simple:

解决方案非常简单:

protocol SomeProtocol: NSObjectProtocol {

}

func find(protocols: [SomeProtocol], element: SomeProtocol) -> Int? {
    for (index, object) in protocols.enumerated() {
        if (object.isEqual(element)) {
            return index
        }
    }

    return nil
}

回答by Jitendra Kulkarni

I take it that your main aim is to hold a collection of objects conforming to some protocol, add to this collection and delete from it. This is the functionality as stated in your client, "SomeClass". Equatable inheritance requires self and that is not needed for this functionality. We could have made this work in arrays in Obj-C using "index" function that can take a custom comparator but this is not supported in Swift. So the simplest solution is to use a dictionary instead of an array as shown in the code below. I have provided getElements() which will give you back the protocol array you wanted. So anyone using SomeClass would not even know that a dictionary was used for implementation.

我认为您的主要目标是保存符合某种协议的对象集合,添加到该集合中并从中删除。这是您的客户“SomeClass”中所述的功能。Equatable 继承需要 self 并且此功能不需要。我们本可以使用“index”函数在 Obj-C 中的数组中完成这项工作,该函数可以采用自定义比较器,但 Swift 不支持此功能。所以最简单的解决方案是使用字典而不是数组,如下面的代码所示。我已经提供了 getElements() ,它会给你你想要的协议数组。所以任何使用 SomeClass 的人都不会知道字典被用于实现。

Since in any case, you would need some distinguishing property to separate your objets, I have assumed it is "name". Please make sure that your do element.name = "foo" when you create a new SomeProtocol instance. If the name is not set, you can still create the instance, but it won't be added to the collection and addElement() will return "false".

由于在任何情况下,您都需要一些独特的属性来分隔您的对象,因此我假设它是“名称”。创建新的 SomeProtocol 实例时,请确保您的 do element.name = "foo" 。如果未设置名称,您仍然可以创建实例,但不会将其添加到集合中,并且 addElement() 将返回“false”。

##代码##

回答by Kevin Delord

I found a notpure-pure Swift solution on that blog post: http://blog.inferis.org/blog/2015/05/27/swift-an-array-of-protocols/

我发现了一个上博客文章纯纯斯威夫特的解决方案: http://blog.inferis.org/blog/2015/05/27/swift-an-array-of-protocols/

The trick is to conform to NSObjectProtocolas it introduces isEqual(). Therefore instead of using the Equatableprotocol and its default usage of ==you could write your own function to find the element and remove it.

诀窍是遵守NSObjectProtocol它介绍的isEqual(). 因此Equatable==您可以编写自己的函数来查找元素并将其删除,而不是使用协议及其默认用法。

Here is the implementation of your find(array, element) -> Int?function:

这是您的find(array, element) -> Int?功能的实现:

##代码##

Note: In this case your objects conforming to SomeProtocolmust inherits from NSObject.

注意:在这种情况下,您符合的对象SomeProtocol必须继承自NSObject.