首页 文章

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

提问于
浏览
118

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

因为它是一种类型,所以可以在允许其他类型的许多地方使用协议,包括:作为函数,方法或初始值设定项中的参数类型或返回类型作为常量,变量或属性的类型As数组,字典或其他容器中的项类型

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

协议'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)
        }
    }
}

7 回答

  • 8

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

    你已经遇到了Swift协议问题的一个变种,但尚未找到好的解决方案 .

    另请参阅Extending Array to check if it is sorted in Swift?,它包含有关如何解决它的建议,可能适合您的特定问题(您的问题非常通用,也许您可以找到使用这些答案的解决方法) .

  • 2

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

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

    // 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
    
  • 0

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

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

    解决方案非常简单:

    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
            }
        }
    }
    
  • 11

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

    因为在任何情况下,你需要一些区分属性来分隔你的objets,我认为它是“名字” . 创建新的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)
        }
    }
    
  • 29

    我在博客文章中找到了一个纯粹的纯粹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 继承 .

相关问题