【问题标题】:Swift Generics - Attempting to make a generic protocol concrete fails when attempting to use specialised sub-protocol as variableSwift Generics - 尝试使用专用子协议作为变量时,尝试使通用协议具体化失败
【发布时间】:2019-10-14 14:02:06
【问题描述】:

我想知道为什么我的SomeResourceRepository 仍然是通用的,尽管它仅在一种情况下定义,即当我设置ResourceType = SomeResource 时,XCode 使用 where 子句将其格式化如下。下面的代码显示了我正在尝试实现的确切设置,写在 Playground 中。

我正在尝试为任何给定的ResourceType 定义一个通用协议,这样ResourceTypeRepository 协议就会自动需要相同的功能集,而无需复制粘贴大部分GenericRepository 来手动填写我创建的每个存储库的资源类型。我需要它作为协议的原因是因为我希望以后能够模拟它以进行测试。所以我会在实际应用程序的其他地方提供上述协议的实现。

我对下面代码的解释是它应该可以工作,因为SomeResourceLocalRepositorySomeResourceRemoteRepository 都是具体的,因为我通过在“SomeResourceRepository”上定义它们来消除关联类型,这只是定义ResourceType == SomeResource.

import Foundation

struct SomeResource: Identifiable {
    let id: String
    let name: String
}

struct WhateverResource: Identifiable {
    let id: UUID
    let count: UInt
}

protocol GenericRepository: class where ResourceType: Identifiable {
    associatedtype ResourceType

    func index() -> Array<ResourceType>
    func show(id: ResourceType.ID) -> ResourceType?
    func update(resource: ResourceType)
    func delete(id: ResourceType.ID)
}

protocol SomeResourceRepository: GenericRepository where ResourceType == SomeResource {}
protocol SomeResourceLocalRepository: SomeResourceRepository {}
protocol SomeResourceRemoteRepository: SomeResourceRepository {}

class SomeResourceLocalRepositoryImplementation: SomeResourceLocalRepository {
    func index() -> Array<SomeResource> {
        return []
    }

    func show(id: String) -> SomeResource? {
        return nil
    }

    func update(resource: SomeResource) {
    }

    func delete(id: String) {
    }
}

class SomeResourceService {
    let local: SomeResourceLocalRepository

    init(local: SomeResourceLocalRepository) {
        self.local = local
    }
}

// Some Dip code somewhere
// container.register(.singleton) { SomeResourceLocalRepositoryImplementation() as SomeResourceLocalRepository }

错误:

error: Generic Protocols.xcplaygroundpage:45:16: error: protocol 'SomeResourceLocalRepository' can only be used as a generic constraint because it has Self or associated type requirements
let local: SomeResourceLocalRepository
           ^

error: Generic Protocols.xcplaygroundpage:47:17: error: protocol 'SomeResourceLocalRepository' can only be used as a generic constraint because it has Self or associated type requirements
    init(local: SomeResourceLocalRepository) {

我可能必须找到另一种方法来完成此操作,但这很乏味且很烦人,因为我们会产生大量重复的代码,并且当我们决定更改存储库的 API 时,我们将不得不手动更改它适用于所有协议,因为我们在此解决方法中不遵循通用的“父”协议。

我已阅读How to pass protocol with associated type as parameter in Swift 和在此问题的答案中找到的相关问题,以及Specializing Generic Protocol 和其他人。

我觉得这应该可行,但它没有。最终目标是一个可用于依赖注入的具体协议,例如container.register(.singleton) { ProtocolImplementation() as Protocol }Dip - A simple Dependency Injection Container 相同,但是当协议的接口可以像上面那样明确地通用时,无需复制粘贴。

【问题讨论】:

标签: swift generics dependency-injection swift-protocols


【解决方案1】:

由于 swift 提供了一种声明泛型协议的方法(使用 associatedtype 关键字),因此在没有另一个泛型约束的情况下声明泛型协议属性是不可能的。所以最简单的方法是声明资源服务类泛型 - class SomeResourceService&lt;Repository: GenericRepository&gt;

但是这个解决方案有一个很大的缺点 - 你需要在任何涉及这个服务的地方限制泛型。

您可以通过将local 声明为具体的泛型类型来从服务声明中删除泛型约束。但是如何从泛型协议过渡到具体的泛型类呢?

有办法。您可以定义一个符合GenericRepository 的包装泛型类。它并没有真正实现它的方法,而是传递给它包装的一个对象(这是真正的GenericRepository)。

class AnyGenericRepository<ResourceType: Identifiable>: GenericRepository {
  // any usage of GenericRepository must be a generic argument
  init<Base: GenericRepository>(_ base: Base) where Base.ResourceType == ResourceType {
    // we cannot store Base as a class property without putting it in generics list
    // but we can store closures instead
    indexGetter = { base.index() }
    // and same for other methods or properties
    // if GenericRepository contained a generic method it would be impossible to make
  }

  private let indexGetter: () -> [ResourceType] {
    indexGetter()
  }

  // ... other GenericRepository methods
}

所以现在我们有一个具体的类型,它包装了真正的GenericRepository。您可以在SomeResourceService 领养它而无需任何警报。

class SomeResourceService {
  let local: AnyGenericRepository<SomeResource>
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-07
    • 2018-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多