如何在 Swift 中的泛型 class 的函数参数中使用泛型类型
How to use generic type in function parameter from generic class in Swift
我有一个 class 生成查询 SQLite 使用的函数可以将一个与另一个连接起来看起来像一个 'native' 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 协议实现功能的当前工作查询 class:
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 { [=14=].stringValue }, from: T.tablename)
}
func select(distinct: Bool? = nil, _ columns: T.Columns...) -> Query {
return generateSelect(distinct ?? false, columns.map { [=14=].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...
}
错误显示:"Generic parameter 'T' is not used in function signature".
我知道这样做可以解决问题,但是我需要避开参数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
是一个 static 方法):
let sentence = Query.select(.field1, .field2)
为此,Columns 需要知道他们的 Table。正如您所写的那样,拥有两个具有相同 Columns 类型的不同 Table 是合法的,然后这是不明确的。 (注意上面的语法肯定是不可能的,因为没办法知道enum .field1
属于什么,但是我们可以更接近)。
所以首先,我们需要一个知道其 Table:
的 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
。将一种类型嵌套在另一种类型中不会以任何方式连接它们。你不能说 "my containing type" 或类似的东西。
此方法将防止 table 交叉链接其他 table 的列标识符。例如:
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
的小技巧。这意味着 "when the return type is known, you don't need to include it." 因此,例如,这将在没有 from
:
的情况下工作
func f() -> Query<SomeTable> {
return Query.select(columns: .field1, .field2)
}
我有一个 class 生成查询 SQLite 使用的函数可以将一个与另一个连接起来看起来像一个 'native' 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 协议实现功能的当前工作查询 class:
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 { [=14=].stringValue }, from: T.tablename)
}
func select(distinct: Bool? = nil, _ columns: T.Columns...) -> Query {
return generateSelect(distinct ?? false, columns.map { [=14=].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...
}
错误显示:"Generic parameter 'T' is not used in function signature".
我知道这样做可以解决问题,但是我需要避开参数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
是一个 static 方法):
let sentence = Query.select(.field1, .field2)
为此,Columns 需要知道他们的 Table。正如您所写的那样,拥有两个具有相同 Columns 类型的不同 Table 是合法的,然后这是不明确的。 (注意上面的语法肯定是不可能的,因为没办法知道enum .field1
属于什么,但是我们可以更接近)。
所以首先,我们需要一个知道其 Table:
的 ColumnIdentifierpublic 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
。将一种类型嵌套在另一种类型中不会以任何方式连接它们。你不能说 "my containing type" 或类似的东西。
此方法将防止 table 交叉链接其他 table 的列标识符。例如:
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
的小技巧。这意味着 "when the return type is known, you don't need to include it." 因此,例如,这将在没有 from
:
func f() -> Query<SomeTable> {
return Query.select(columns: .field1, .field2)
}