Swift协议的通用类

问题描述 投票:4回答:1

我有一个像这样的基本协议:

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

我的班级是这样的:

public class BasicSwiftClass<Layout: BasicSwiftClassLayout> {}

我已经定义了2个不同的结构,以便我可以用2个不同的笔尖启动这个类:

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

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

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

  1. 我想使用Layout变量来检索此类启动的nib名称。因此,如果我要将此类作为:let myView = BasicSwiftClass<NormalLayout>启动,我希望能够为类中的NormalLayout检索nibName(“NormalLayoutNibName”)。我会想做像let myNib = Layout.nibName这样的东西,但它只是告诉我Instance member nibName cannot be used on type Layout所以我该如何去检索nibName?
  2. 当我没有将通用类添加到我的班级而且它只是public class BasicSwiftClass时,classForCoder只是MyProject.BasicSwiftClass。现在我已经添加了通用行为,classForCoder被返回为MyProject.BasicSwiftClass<Layout.NormalLayout>,在调用let bundle = Bundle(for: classForCoder)时不再返回正确的包我是否需要覆盖classForCoder变量?或者我在这里做错了什么?

谢谢!

swift generics protocols generic-programming
1个回答
5
投票

由于您的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)初始化程序适用于泛型类,句点。如果有人找到了解决方法的话,我很高兴出错,但我的猜测是,因为编译器会生成泛型类型的具体变体,而且只有实际使用的那些,这会带来一些挑战:

  • 可以在导入并具有对通用类型的可见性和访问权限的任何模块中使用/生成泛型类型的特定特化。例如,如果一个不同的模块导入了你的代码并定义了一个public struct DifferentLayout: BasicSwiftClassLayout类型,变量BasicSwiftClass<DifferentLayout>是你的包的一部分,它定义了BasicSwiftClass的所有逻辑,但在编译时没有那个变化,或者另一个模块,这导致编译器创建该专业化?
  • 您没有在技术上定义专用类型BasicSwiftClass<OtherLayout>,编译器基于确定它将在何处使用。所以再次,它可以模糊这个真正属于哪个捆绑。

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

建议的替代方法:

如果使协议需要类型的一致性,则可以获取布局的包而不是BasicSwiftClass。例如。:

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

创建泛型类型的数组

在回答您关于如何为BasicSwiftClass<Layout>创建具有不同值的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($0.view) }

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

© www.soinside.com 2019 - 2024. All rights reserved.