在 Swift4 上使用泛型 T.Type 类时如何调用正确的构造函数?

在 Swift4 上使用泛型 T.Type 类时如何调用正确的构造函数?

问题描述:

我有一个包含一些表的数据库,每个表代表我项目的一个对象.我想编写一个通用函数来通过 SQL 读取一个表并创建一个带有读取记录的对象.所以,我的函数的参数是:表名和对象类型.下面的代码是我的功能来做到这一点.在 func 的最后,我尝试调用我想做的事情,但是对于特定的对象,这不是我想要的.

I have a database with some tables, each table represents a object of my project. I want write a generic function to read, by SQL, a table and create a object with the records readed. So, the parameters of my function are: Table Name and Object Type. The code below is my func to do this. In the end of func, I tries call what I would like to do, but with a especific object, that's don't the I want.

func readAll<T>(objeto: String, typeClass: T.Type) -> [T] {
    var ret : [T] = []

    // STATEMENT DATA
    let queryString = "SELECT * FROM \(objeto);"
    var queryStatement: OpaquePointer? = nil

    // STATEMENT DATA TYPE
    let queryString2 = "PRAGMA table_info(\(objeto));"
    var queryStatement2: OpaquePointer? = nil

    // 1
    if sqlite3_prepare_v2(db,queryString,-1,&queryStatement,nil) != SQLITE_OK {
        print("Error to compile readAll \(objeto) 1")
        return ret
    }

    if sqlite3_prepare_v2(db,queryString2,-1,&queryStatement2,nil) != SQLITE_OK {
        print("Error to compile readAll \(objeto) 2")
        return ret
    }

    var listNameColumns : [String] = []

    while( sqlite3_step(queryStatement2) == SQLITE_ROW ) {
        listNameColumns.append( String(cString: sqlite3_column_text(queryStatement2, 2)!) )
    }

    // 2
    while ( sqlite3_step(queryStatement) == SQLITE_ROW ) {
        var dict: [String:Any] = [:]

        for i in 0...listNameColumns.count-1 {
            let nameColumn = String(cString: sqlite3_column_name(queryStatement,Int32(i))!)
            switch (sqlite3_column_type(queryStatement, Int32(i))) {

            case SQLITE_TEXT:
                dict[nameColumn] = String(cString: sqlite3_column_text(queryStatement, Int32(i))!)
                break

            case SQLITE_INTEGER:
                dict[nameColumn] = sqlite3_column_int(queryStatement, Int32(i))
                break

            case SQLITE_FLOAT:
                dict[nameColumn] = sqlite3_column_double(queryStatement, Int32(i))
                break

            default:
                print("Tipo desconhecido.")
                break
            }
        }

        ret.append(ResPartner(dict: dict)) <------ HERE IS MY QUESTION!
    }

    // 3
    sqlite3_finalize(queryStatement2)
    sqlite3_finalize(queryStatement)

    return ret
}

这里有两个对象,它们有点不同,但构建器的工作方式和字段相同.

Here are two objects, They are a bit different, but the builder works the same and the fields as well.

class ResPartner {

    static let fieldsResPartner : [String] = ["id","company_type_enum_for_customer","name","contact_address","customer_account_number","customer_group_id","segment_id","subsegment_id","economic_group_id","street","category_id","type_stablishment_id","final_user","final_taxpayer","cnpj_cpf","inscr_est","ccm","cnae","phone","phone_extension","mobile","fax","email","email_extra","website","lang"]

    var attributes : [String:Any] = [:]

    init(dict : [String:Any]) {
        for k in dict.keys {
            if(ResPartner.fieldsResPartner.contains(k)) {
                attributes[k] = dict[k]
            }
        }
    }

    func toString() {
        for k in attributes.keys{
            print("\(k) - \(attributes[k]!)")
        }
    }
}

class Product {

       static let fieldsProducts : [String] =  ["id","name","default_code","display_name","categ_id","company_ax_id","destination_type","fiscal_class_code","multiple","taxes_id","uom_id","uom_po_id","__last_update","active","create_date","create_uid","currency_id","invoice_police","item_ids","list_price","price","pricelist_id","type"]

    public var default_code: String!
    public var display_name: String!
    public var id: Int!
    public var name: String!
    public var destination_type: String!
    public var company_ax_id: Int!
    public var categ_id: Int!
    public var fiscal_class_code: String!
    public var taxes_id: Int!
    public var uom_id: Int!
    public var uom_po_id: Int!
    public var multiple: Int!
    public var last_update: String!
    public var active: Bool!
    public var create_date: String!
    public var create_uid: Int!
    public var currency_id: Int!
    public var invoice_police: String!
    public var item_ids: [Int]!
    public var list_price: String!
    public var price: Float!
    public var pricelist_id: Int!
    public var type: String!

    init() {

    }

    init( dict : [String:Any] ) {
        self.default_code = dict["default_code"] as! String
        self.display_name = dict["display_name"] as! String
        self.id = dict["id"] as! Int
        self.name = dict["name"] as! String
        self.destination_type = dict["destination_type"] as! String
        self.company_ax_id = dict["company_ax_id"] as! Int
        self.categ_id = dict["categ_id"] as! Int
        self.fiscal_class_code = dict["fiscal_class_code"] as! String
        self.taxes_id = dict["taxes_id"] as! Int
        self.uom_id = dict["uom_id"] as! Int
        self.uom_po_id = dict["uom_po_id"] as! Int
        self.multiple = dict["multiple"] as! Int
        self.last_update = dict["last_update"] as! String
        self.active = dict["active"] as! Bool
        self.create_date = dict["create_date"] as! String
        self.create_uid = dict["create_uid"] as! Int
        self.currency_id = dict["currency_id"] as! Int
        self.invoice_police = dict["invoice_police"] as! String
        self.item_ids = dict["item_ids"] as! [Int]
        self.list_price = dict["list_price"] as! String
        self.price = dict["price"] as! Float
        self.pricelist_id = dict["pricelist_id"] as! Int
        self.type = dict["type"] as! String
    }
}

那么,我的问题是,我如何调用通过参数传递的 T.Type 类的构造函数?我确实阅读了协议、扩展和其他帖子,但没有解决我的问题.

So, my question is, How I call the constructor of T.Type class passed by parameter? I did read about protocols, extensions, other posts, but not solves my problem.

你可以用协议约束你的泛型:

You can constrain your generic with protocol:

  1. 定义一个使用字典初始化的协议:

  1. Define a protocol for initializing with a dictionary:

protocol DictionaryInitializable {
    init(dict: [String: Any])
}

  • 使您的两种类型符合该类型(您必须按照 Xcode 的提示将 required 添加到您的 init 方法中),例如:

  • Make your two types conform to that type (you'll have to add required to your init methods, as prompted by Xcode), e.g.:

    class Product: DictionaryInitializable {
    
        ...
    
        required init(dict: [String: Any]) {
            ...
        }
    }
    

    class ResPartner: DictionaryInitializable {
    
        static let fieldsResPartner = ...
    
        var attributes: [String: Any] = [:]
    
        required init(dict: [String: Any]) {
            for k in dict.keys where ResPartner.fieldsResPartner.contains(k) {
                attributes[k] = dict[k]
            }
        }
    
        func toString() { ... }
    }
    

  • 更改您的方法声明以明确T 必须符合您的新协议:

  • Change your method declaration to make it clear that T must conform to your new protocol:

    func readAll<T: DictionaryInitializable>(objeto: String, typeClass: T.Type) -> [T] {
        var ret: [T] = []
    
        ... 
    
        // 2
        while ( sqlite3_step(queryStatement) == SQLITE_ROW ) {
            var dict: [String: Any] = [:]
    
            ...                
    
            ret.append(T(dict: dict)) // You can now use `T` here
        }
    
        return ret
    }
    

  • 你会这样称呼它:

  • And you’d call it like:

    let list = db_respartner.readAll(objeto: "res_partner", typeClass: ResPartner.self)