Swift协议的通用类

Log*_*gan 4 generics protocols generic-programming swift

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

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变量?或者我在这里做错了什么?

谢谢!

Dan*_*all 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"
}
Run Code Online (Sandbox Code Playgroud)

至于你的第二个问题,我不认为Bundle(for class: AnyClass)初始化程序适用于泛型类,句点.如果有人找到了解决方法的话,我很高兴出错,但我的猜测是,因为编译器会生成泛型类型的具体变体,而且只有实际使用的那些,这会带来一些挑战:

  • 可以在导入并具有对通用类型的可见性和访问权限的任何模块中使用/生成泛型类型的特定特化.例如,如果一个不同的模块导入了你的代码并定义了一个public struct DifferentLayout: BasicSwiftClassLayout类型,那么变体BasicSwiftClass<DifferentLayout>你的 bundle的一部分,它定义了所有的逻辑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
    }
}
Run Code Online (Sandbox Code Playgroud)

创建泛型类型的数组

在回答有关如何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) }
Run Code Online (Sandbox Code Playgroud)

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