簡體   English   中英

如何在Swift中的泛型類中的函數參數中使用泛型

[英]How to use generic type in function parameter from generic class in Swift

我有一個類,該類使用可以相互連接以看起來像“本機” SQL語句的函數來生成SQLite查詢。

這是當前工作的協議:

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

這是當前正在使用表協議實現該功能的查詢類:

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<SomeTable>()的需要,所以我嘗試的是這個操作(錯誤出現的地方):

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協議外,它還需要具有一個屬性,因此...問題仍然存在。

有任何想法嗎?

編輯:添加了示例和實現

如果我理解正確,則您具有以下語法:

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

並且您需要以下語法:

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

您的代碼中有很多小錯誤,我想您實際上是想使用這種語法( select是一個靜態方法):

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

為此,列需要知道其表。 在撰寫本文時,擁有兩個具有相同Columns類型的不同表是合法的,這是不明確的。 (請注意,上面的語法絕對是不可能的,因為無法知道枚舉.field1屬於什么,但是我們可以.field1 )。

因此,首先,我們需要一個知道其表的ColumnIdentifier:

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

接下來,Table需要斷言其ColumnIdentifer屬於它。 這將防止多種表類型引用同一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()
    }
}

並以表格實現為例:

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: T.Type = T.self 這意味着“當返回類型已知時,您不需要包括它。” 因此,例如,這將在沒有from情況下起作用:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM