Swift协议的通用类

时间:2017-12-18 18:23:18

标签: swift generics protocols generic-programming

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

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 BasicSwiftClassclassForCoder只是MyProject.BasicSwiftClass。现在我已添加了通用行为,classForCoder将作为MyProject.BasicSwiftClass<Layout.NormalLayout>返回,在调用let bundle = Bundle(for: classForCoder)时不再返回正确的包我是否需要覆盖classForCoder变量?或者我在这里做错了什么?

  3. 谢谢!

1 个答案:

答案 0 :(得分: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>会成为您的包的一部分,它会定义{{1}的所有逻辑,但是在编译时没有那个变化,或者其他模块导致编译器创建那个特化?

  • 您没有在技术上定义专用类型BasicSwiftClass,编译器基于确定它的使用位置。所以再次,它可以模糊这个真正属于哪个捆绑。

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

建议的替代方法:

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

Bundle(for:)

创建泛型类型

的数组

在回答有关如何为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>数组的问题时,根据您需要如何使用从数组中检索的元素,有不同的方法来解决此问题。最终,Swift要求集合(包括数组)包含所有相同类型的元素,或者可以作为相同的常见类型进行寻址,因此所有不同的方法都基于将每个Layout实例转换,包装或转换为某些公共类型,公开您要在每个实例上调用的常用功能。

最简单的解决方案可能是创建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) } ,并将该集合键入为该超类类型的实例数组。但是,如果您可以在非通用协议中表达共同要求,那么这应该让您保持最简单的路径。