通用 类 带有协议 Swift

Generic Classes With Protocols Swift

我有这样的基本协议:

public protocol BasicSwiftClassLayout { var nibName: String { get } }

我的class是这样的:

public class BasicSwiftClass<Layout: BasicSwiftClassLayout> {}

我定义了 2 个不同的结构,这样我就可以用 2 个不同的 nib 启动这个 class:

public struct NormalLayout: BasicSwiftClassLayout { public let nibName = "NormalLayoutNibName" }

public struct OtherLayout: BasicSwiftClassLayout { public let nibName = "OtherLayoutNibName }

基于此我现在有 2 个问题。

  1. 我想使用 Layout 变量来检索启动此 class 的笔尖名称。因此,如果我将此 class 启动为: let myView = BasicSwiftClass<NormalLayout> 我希望能够在 class ("NormalLayoutNibName") 中检索 NormalLayout 的 nibName。我想做类似 let myNib = Layout.nibName 的事情,但它只是告诉我 Instance member nibName cannot be used on type Layout 那么我该如何检索 nibName?

  2. 当我没有将通用 class 添加到我的 class 中而它只是 public class BasicSwiftClass 时,classForCoder 只是 MyProject.BasicSwiftClass。现在我添加了通用行为,classForCoder 返回为 MyProject.BasicSwiftClass<Layout.NormalLayout>,调用 let bundle = Bundle(for: classForCoder) 时不再返回正确的包我是否需要覆盖 classForCoder 多变的?或者我在这里做错了什么?

谢谢!

由于您的 BasicSwiftClass 没有符合 BasicSwiftClassLayout 类型的 实例 ,而只是知道该类型本身,你应该使 nibName 需求静态,这样它就可以在类型本身而不是实例上调用,像这样:

public protocol BasicSwiftClassLayout {
    static var nibName: String { get }
}

public struct NormalLayout: BasicSwiftClassLayout {
   public static let nibName = "NormalLayoutNibName"
}

public struct OtherLayout: BasicSwiftClassLayout {
   public static let nibName = "OtherLayoutNibName"
}

至于你的第二个问题,我认为 Bundle(for class: AnyClass) 初始值设定项不适用于通用 classes,句号。如果有人找到了一种方法,我很高兴在这个问题上是错误的,但我的猜测是因为 编译器 生成泛型类型的具体变体,而且只有那些实际上是使用,这会带来一些挑战:

  • 泛型的特定特化可以在导入泛型并具有可见性和访问泛型类型的任何模块中使用/生成。例如,如果一个不同的模块导入了您的代码并定义了一个 public struct DifferentLayout: BasicSwiftClassLayout 类型,那么变体 BasicSwiftClass<DifferentLayout> 是否会成为 您的 包的一部分,它定义了所有逻辑BasicSwiftClass,但在编译时没有那个变体,或者其他模块导致编译器创建那个特化?

  • 您没有从技术上定义专用类型 BasicSwiftClass<OtherLayout>,编译器根据确定将在何处使用它来定义。因此,它真正属于哪个捆绑包可能会变得模糊。

我猜想如果有一种方法可以引用 通用类型 本身,即非专用 BasicSwiftClass<Layout> 类型引用,那么 Bundle(for:)会正常工作。但是没有办法像那样引用泛型类型,因为它更像是编译器用于生成具体特化的模板,而不是运行时存在的实际类型。

建议的替代方法:

如果您使协议需要 class 类型以实现一致性,您可以获得布局而不是 BasicSwift 类的包。例如:

public protocol BasicSwiftClassLayout: class {
    static var nibName: String { get }
}

final public class NormalLayout: BasicSwiftClassLayout {
   public static let nibName = "NormalLayoutNibName"
}

public class BasicSwiftClass<Layout: BasicSwiftClassLayout> {
    func loadFromNib() {
        let view =  Bundle(for: Layout.self).loadNibNamed(Layout.nibName, owner: self, options: nil)?.first
    }
}

创建泛型数组

针对您关于如何为 Layout 制作具有不同值的 BasicSwiftClass<Layout> 数组的问题,根据您需要如何使用从阵列。最终,Swift 要求集合(包括数组)包含的元素都是相同类型或可以作为相同的通用类型处理,因此所有不同的方法都基于转换、包装或强制转换每个 BasicSwiftClass<Layout> 实例到一些公共类型,公开您要在每个实例上调用的公共功能。

最简单的解决方案可能是创建一个 BasicSwiftClass<Layout> 遵循的协议,它公开了您需要的功能。例如,如果您只关心从每个实例获取一个视图以添加到视图层次结构中,您可以这样做:

public protocol ViewRepresentable {
    var view: UIView { get }
}

extension BasicSwiftClass<Layout>: ViewRepresentable {
   public var view: UIView { // load the nib, connect outlets and do other setup, then return the appropriate UIView }
}

let array: [ViewRepresentable] = [BasicSwiftClass<IPadLayout>, BasicSwiftClass<NormalLayout>]
array.forEach { self.containerView.addSubview([=12=].view) }

如果您需要更专业的东西或在内部保留某些类型信息等,您可能需要使用包装器或类型橡皮擦。您也可以尝试让 BasicSwiftClass<Layout> 从具有您需要的基本功能的非泛型 superclass 继承,并将集合键入为该 superclass 类型的实例数组。但是,如果您可以在非通用协议中表达共同需求,那应该会让您走上最简单的道路。