符合 class 对通用协议功能的扩展

Conform class extension to generic protocol function

* 短版 *

如何使 class(扩展)符合通用协议功能?

* 长版 *

这是支持分页集合的数据结构的一小部分,

protocol Pageable { 
     //an object whose can be in a collection
}

protocol Page{ //a page of a collection that can be paginated

    associatedtype PageItemType

    func itemAt<PageItemType:Pageable>(index: Int) -> PageItemType  
}

//Bonus question
//class PagedCollection<PageType:Page, ItemType:Pageable> {
    //...
//}

这里是协议的实现 "real" 案例:

class Person : Pageable{}

class People {
    var people: [Person]?
}

//Mark: - Page

extension People: Page{ /*** error 1 ***/

    typealias PageItemType = Person

    func itemAt(index: Int) -> Person{
        let person : Person = self.people![index]
        return person
    }
}

获取如下错误 (1):

Type 'People' does not conform to protocol 'Page'

Protocol requires nested type 'PageItemType'

我也试过让它显式化,但我得到了一个不同的错误:

//Mark: - Page

extension People: Page{

    typealias PageItemType = Person

    func itemAt<PageItemType:Pageable>(index: Int) -> PageItemType{
        let person : Person = self.people![index]
        return person /*** error 2 ***/
    }
}

获取如下错误 (2):

Cannot convert return expression of type 'Person' to return type 'PageItemType'

所以:*如何让 itemAt 函数 return 成为 PageItemType 类型别名的有效类型?

* 奖金 *

奖金问题 价值 50 赏金(如果答案超过一行,我将打开一个新问题): 参考第一个代码片段PagedCollection

您似乎将关联类型与泛型函数混为一谈。

泛型函数允许您在调用站点(即调用函数时)提供一个类型来替换给定的泛型占位符。

关联类型允许符合协议的类型提供自己的类型来替换协议要求中的给定占位符类型。这是按类型 完成的,而不是在任何函数的调用站点。如果您希望对 associatedtype 执行一致性要求,您应该直接在其声明中这样做(即 associatedtype PageItemType : Pageable)。

如果我正确理解您的要求,您的 itemAt(index:) 函数应该是非泛型的(否则协议中的 associatedtype 是完全多余的)。它 return 的类型是由符合 Page 的类型的实现定义的,而不是由函数的调用者定义的。例如,您的 People class 定义 PageItemType 关联类型应该是 Person – 这就是 itemAt(index:) 应该 return。

protocol Pageable {/* ... */}

protocol Page {

    // any conforming type to Page will need to define a
    // concrete type for PageItemType, that conforms to Pageable
    associatedtype PageItemType : Pageable

    // returns the type that the implementation of the protocol defines
    // to be PageItemType (it is merely a placeholder in the protocol decleration)
    func itemAt(index: Int) -> PageItemType
}

class Person : Pageable {/* ... */}

class People {
    var people: [Person]?
}

extension People : Page {

    // explicitly satisfies the Page associatedtype requirement.
    // this can be done implicitly be the itemAt(index:) method,
    // so could be deleted (and annotate the return type of itemAt(index:) as Person)
    typealias PageItemType = Person

    // the itemAt(index:) method on People now returns a Person
    func itemAt(index: Int) -> PageItemType {

        // I would advise against force unwrapping here.
        // Your people array most likely should be non-optional,
        // with an initial value of an empty array
        // (do you really need to distinguish between an empty array and no array?)
        let person = self.people![index]
        return person
    }
}

关于 PagedCollection 的实现——因为 Page 协议中的 PageItemType 关联类型符合 Pageable,我认为不需要 ItemType泛型参数。这可以简单地通过给定 PageType 泛型参数的关联类型来访问。

举个例子:

class PagedCollection<PageType:Page> {

    // PageType's associatedtype, which will conform to Pageable.
    // In the case of the PageType generic parameter being People,
    // PageType.PageItemType would be of type Person
    var foo : PageType.PageItemType

    init(foo: PageType.PageItemType) {
        self.foo = foo
    }
}

// p.foo is of type Person, and is guarenteed to conform to Pageable
let p = PagedCollection<People>(foo: Person())

这只是我与 Hamish 的 chat 的格式化讨论。

我写在这里,因为聊天室可以存档,有时更直接的 QA 可以不同地传达内容

我:

protocol Provider { 
    associatedtype Input 
    associatedtype Output 
    func value(forKey _key: Input) -> Output{} 
}

对比

protocol Provider{ 
    func value<Input, Output>(forKey _key: Input) -> Output{} 
}

Hamish: 本质上的区别在于满足占位符的位置——关联类型在类型级别得到满足,而函数上的通用占位符在所述调用点得到满足功能。

我: 我理解那种程度的差异。但是由于这种差异是否有任何副产品 :D

哈米什:你什么意思?它们表达不同的东西——前者是一种协议,其中一致性类型只处理一种特定类型的输入和输出,后者是一种协议,其中一致性类型可以处理任何给定的输入和输出类型。

我: 前者是一个协议,conforming type只处理一种特定类型的输入输出。你是什么具体是什么意思? typeAlias 不能是我喜欢的任何东西吗?

Hamish:可以,但是每种类型只能满足一次——一旦我定义了

struct S : Provider { 
   func value(forKey: String) -> Int 
}

S 现在只能处理 String 输入和 Int 输出。 而对于后者,我会定义

struct S : Provider { 
    func value<Input, Value>(forKey _key: Input) -> Output{} 
} 

现在 S 必须能够处理 任何 输入和输出类型。