【问题标题】:How to use generic type in function parameter from generic class in Swift如何在 Swift 中的泛型类的函数参数中使用泛型类型
【发布时间】:2019-08-29 10:06:36
【问题描述】:

我有一个类可以使用函数生成 SQLite 查询,这些函数可以将一个连接到另一个看起来像一个“本机”SQL 语句。

这是当前工作的协议:

/// Protocol for any object directly related with a database table.
public protocol Table {

    /// Generic enum implementing the table columns.
    associatedtype Columns: (RawRepresentable & CodingKey & CaseIterable & Hashable)

    /// The name of the table that will be represented in the entity. 
    /// Could be declarated as let in the struct that implements this protocol.
    static var tablename: String { get }
}

这里是一个实现 Table 协议的结构

public struct TMAEVersion : Table {

    /// Properties
    public var statusMobile: String?
    public var version: String?

    /// Overriding the default name to account for the special name of the table
    public var tablename: String = "TMAEVersion"

    /// Table columns keys
    public enum CodingKeys : String, CodingKey, CaseIterable {
        case statusMobile = "status_mobile"
        case version = "Version"
    }

    public typealias Columns = CodingKeys
}

这是当前使用 Table 协议实现功能的 Query 类:

public class Query<T> {

    // MARK: Properties

    public var columns = [String]()
    public var table: String = ""
    public var literal: String = ""

    fileprivate var showLogs: Bool = true

    // MARK: Init

    public init(literal: String) {
        self.literal = literal
    }

    /// Empty init for normal queries that don't take a literal
    public init(showingLogs: Bool? = nil) {
        if let showingLogs = showingLogs { showLogs = showingLogs }
    }
}

public extension Query where T: Table {

    // MARK: Select

    func generateSelect(_ distinct: Bool? = nil , _ columns: [String], from tablename: String) -> Query {
        let statement = Select(distinct: distinct ?? false, columns: columns)
        self.columns = statement.columns
        self.table = tablename
        self.literal += statement.sentence
        return self
    }

    func select(distinct: Bool? = nil, _ columns: CodingKey...) -> Query {
        return generateSelect(distinct ?? false, columns.map { $0.stringValue }, from: T.tablename)
    }

    func select(distinct: Bool? = nil, _ columns: T.Columns...) -> Query {
        return generateSelect(distinct ?? false, columns.map { $0.stringValue }, from: T.tablename)
    }

    /// Note: Comparator and Operator are enums containing cases like:
    /// - Comparator: equal, diff, greaterThan...
    /// - Operator: and, or...
    func generateWhere(_ col: String, _ comp: Comparator, _ val: Any, _ op: Operator?) -> Query {
        let statement = Where(column: col, value: val, comparator: comp, operator: op)
        self.literal += statement.sentence
        return self
    }

    func `where`(_ lc: CodingKey, _ comp: Comparator, _ rc: CodingKey) -> Query {
        return generateWhere(column, comp, value, nil)
    }
}

工作示例:

public func currentVersion() -> String? {
    return Query<TMAEVersion>()
            .select(.Version)
            .order(by: .Version)
            .execute().first?
            .Version
}

我想避免做Query&lt;SomeTable&gt;() 的需要,所以我尝试的是这个(并且它是出现错误的地方):

func select<T: Table>(distinct: Bool?, columns: [T.Columns]) -> Query {
    // Code...
}

错误显示:“函数签名中未使用通用参数'T'”。

我知道这样做可以解决问题,但是我需要避免参数from: T.Type,我不知道该怎么办。

func select<T: Table>(from: T.Type, distinct: Bool?, columns: [T.Columns]) -> Query

除了我尝试在 init() 函数中传递 Table 协议,但它需要有一个属性,所以......问题仍然存在。

有什么想法吗?

已编辑:添加示例和实现

【问题讨论】:

  • @JoakimDanielson 它目前在我的代码中返回-&gt; Query。工作功能就像Query&lt;SomeTable&gt;().select(.field1, .filed2).where(.field3, .equal, 1).execute()
  • 那么您在类 Query 中有一个返回 Query 实例的函数?那么它不应该被声明为静态的吗?我必须说,很难理解你想用这段代码完成什么。
  • 完全不清楚为什么 Table 是一个协议,更不用说为什么它是一个通用协议。请展示您正在尝试做的具体示例。什么是特定的查询和表?然后你想概括什么,为什么?
  • @JoakimDanielson 到目前为止,我已经完成了生成连接函数的 SQLite 查询,如示例中所示。但是我现在想要完成的是在没有 的情况下生成它们,只是将它传递给参数,或者如果可能的话,以某种方式传递给构造函数。
  • @matt Table 是一个协议,因为我创建了许多需要符合表名和列的结构。我用一些例子编辑了这篇文章。

标签: swift generics protocols swift-protocols


【解决方案1】:

如果我理解正确,你的语法是这样的:

let sentence = Query<SomeTable>().select(.field1, .field2)

你想要这样的语法:

let sentence = Query().select(.field1, .field2)

您的代码中有很多小错误,我认为您实际上的意思是您想要这种语法(selectstatic 方法):

let sentence = Query.select(.field1, .field2)

为了做到这一点,列需要知道他们的表。正如您所写的那样,拥有两个具有相同 Columns 类型的不同表是合法的,然后这是模棱两可的。 (注意,上面的语法肯定是不可能的,因为没有办法知道枚举.field1属于什么,但我们可以更接近)。

首先,我们需要一个知道其表的 ColumnIdentifier:

public protocol ColumnIdentifier: RawRepresentable & CodingKey & CaseIterable & Hashable {
    associatedtype TableType: Table
}

接下来,Table 需要断言它的 ColumnIdentifer 属于它。这将防止多个 Table 类型引用相同的 ColumnIdentifier。

public protocol Table {
    associatedtype Columns: ColumnIdentifier where Columns.TableType == Self
    static var tablename: String { get }
}

然后查询看起来像(稍微简化):

struct Query<T: Table> {
    static func select<C: ColumnIdentifier>(_ columns: C...) -> Query
        where C.TableType == T
    {
        return Query()
    }
}

以 Table 实现为例:

struct SomeTable: Table {
    enum Columns: String, ColumnIdentifier {
        case field1
        case field2
        typealias TableType = SomeTable
    }

    static var tablename: String { "table" }
}

请注意,我认为没有任何方法可以避免 typealias TableType = SomeTable。将一种类型嵌套在另一种类型中不会以任何方式连接它们。你不能说“我的包含类型”或类似的话。

这种方法将防止表交叉链接其他表的列标识符。例如:

struct OtherTable: Table {
    typealias Columns = SomeTable.Columns
    static var tablename: String { "otherTable" }
}
// 'Table' requires the types 'OtherTable' and 'SomeTable.Columns.TableType' (aka 'SomeTable') be equivalent

有了所有这些,您就可以获得(接近)您所描述的语法:

let sentence = Query.select(SomeTable.Columns.field1, .field2)

请注意,您在此处某处仍需要SomeTable。否则你不知道枚举.field1 是从哪里来的。

就我个人而言,我不会这样做。我会使用from 版本。简单明了。

public protocol Table {
    associatedtype Columns: ColumnIdentifier
    static var tablename: String { get }
}

public protocol ColumnIdentifier: RawRepresentable & CodingKey & CaseIterable & Hashable {}

struct Query<T: Table> {
    static func select(from: T.Type = T.self, columns: T.Columns...) -> Query
    {
        return Query()
    }
}

struct SomeTable: Table {
    enum Columns: String, ColumnIdentifier {
        case field1
        case field2
    }
}

let sentence = Query.select(from: SomeTable.self, columns: .field1, .field2)

注意from: T.Type = T.self 的小技巧。这意味着“当返回类型已知时,您不需要包含它。”因此,例如,这将在没有 from 的情况下工作:

func f() -> Query<SomeTable> {
    return Query.select(columns: .field1, .field2)
}

【讨论】:

  • 非常感谢您的回答。这个解决方案的问题是它不能连接函数,这是我代码的当前功能。实现static func select(from: T.Type = T.self, columns: T.Columns...) -&gt; Query 对于select 语句是可以的,但这意味着我必须对where 语句、inner join 语句等执行相同的操作,这并不完全实用。
猜你喜欢
  • 2017-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多