Làm thế nào để vượt qua một loại lớp như là một tham số chức năng


146

Tôi có một hàm chung gọi một dịch vụ web và tuần tự hóa phản hồi JSON trở lại một đối tượng.

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: AnyClass, completionHandler handler: ((T) -> ())) {

            /* Construct the URL, call the service and parse the response */
}

Những gì tôi đang cố gắng thực hiện là tương đương với mã Java này

public <T> T invokeService(final String serviceURLSuffix, final Map<String, String> params,
                               final Class<T> classTypeToReturn) {
}
  • Là chữ ký phương thức của tôi cho những gì tôi đang cố gắng thực hiện đúng?
  • Cụ thể hơn, việc chỉ định AnyClasslà một loại tham số là điều nên làm?
  • Khi gọi phương thức, tôi chuyển qua MyObject.selflàm giá trị returnClass, nhưng tôi gặp lỗi biên dịch "Không thể chuyển đổi biểu thức '()' thành kiểu 'Chuỗi'"
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: CityInfo.self) { cityInfo in /*...*/

}

Biên tập:

Tôi đã thử sử dụng object_getClass, như được đề cập bởi holex, nhưng bây giờ tôi nhận được:

lỗi: "Nhập 'CityInfo.Type' không tuân thủ giao thức 'AnyObject'"

Những gì cần phải được thực hiện để phù hợp với giao thức?

class CityInfo : NSObject {

    var cityName: String?
    var regionCode: String?
    var regionName: String?
}

Tôi không nghĩ rằng thuốc generic Swifts hoạt động như javas. do đó, suy luận không thể thông minh. id bỏ qua Class <T> Thing và chỉ định rõ ràng Loại chungCastDAO.invokeService("test", withParams: ["test" : "test"]) { (ci:CityInfo) in }
Christian Dietrich

Câu trả lời:


144

Bạn đang tiếp cận nó theo cách sai: trong Swift, không giống như Objective-C, các lớp có các kiểu cụ thể và thậm chí có một hệ thống phân cấp thừa kế (nghĩa là, nếu lớp Bkế thừa từ A, thì B.Typecũng thừa hưởng từ A.Type):

class A {}
class B: A {}
class C {}

// B inherits from A
let object: A = B()

// B.Type also inherits from A.Type
let type: A.Type = B.self

// Error: 'C' is not a subtype of 'A'
let type2: A.Type = C.self

Đó là lý do tại sao bạn không nên sử dụng AnyClass, trừ khi bạn thực sự muốn cho phép bất kỳ lớp học nào . Trong trường hợp này, loại đúng sẽ là T.Typebởi vì nó thể hiện liên kết giữa returningClasstham số và tham số của bao đóng.

Trong thực tế, sử dụng nó thay vì AnyClasscho phép trình biên dịch suy ra chính xác các kiểu trong lệnh gọi phương thức:

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: T.Type, completionHandler handler: ((T) -> ())) {
    // The compiler correctly infers that T is the class of the instances of returningClass
    handler(returningClass())
}

Bây giờ có vấn đề về việc xây dựng một thể hiện Tđể chuyển sang handler: nếu bạn thử và chạy mã ngay bây giờ, trình biên dịch sẽ phàn nàn rằng Tkhông thể xây dựng được (). Và đúng như vậy: Tphải được ràng buộc rõ ràng để yêu cầu nó thực hiện một trình khởi tạo cụ thể.

Điều này có thể được thực hiện với một giao thức như sau:

protocol Initable {
    init()
}

class CityInfo : NSObject, Initable {
    var cityName: String?
    var regionCode: String?
    var regionName: String?

    // Nothing to change here, CityInfo already implements init()
}

Sau đó, bạn chỉ phải thay đổi các ràng buộc chung invokeServicetừ từ <T>sang <T: Initable>.

tiền boa

Nếu bạn gặp các lỗi lạ như "Không thể chuyển đổi biểu thức '()' sang loại 'Chuỗi'", việc di chuyển mọi đối số của lệnh gọi phương thức sang biến của chính nó là rất hữu ích. Nó giúp thu hẹp mã gây ra lỗi và phát hiện ra các vấn đề suy luận kiểu:

let service = "test"
let params = ["test" : "test"]
let returningClass = CityInfo.self

CastDAO.invokeService(service, withParams: params, returningClass: returningClass) { cityInfo in /*...*/

}

Bây giờ có hai khả năng: lỗi di chuyển đến một trong các biến (có nghĩa là phần sai ở đó) hoặc bạn nhận được một thông báo khó hiểu như "Không thể chuyển đổi loại biểu thức ()thành loại ($T6) -> ($T6) -> $T5".

Nguyên nhân của lỗi thứ hai là trình biên dịch không thể suy ra các loại của những gì bạn đã viết. Trong trường hợp này, vấn đề là Tchỉ được sử dụng trong tham số của bao đóng và bao đóng mà bạn đã chuyển không chỉ ra bất kỳ loại cụ thể nào để trình biên dịch không biết loại nào sẽ suy ra. Bằng cách thay đổi kiểu returningClassbao gồm, Tbạn cung cấp cho trình biên dịch một cách để xác định tham số chung.


Cảm ơn về gợi ý T.Type - chỉ những gì tôi cần để vượt qua một loại lớp làm đối số.
Echelon

"Mẹo" ở cuối đã cứu tôi
Alex

Thay vì sử dụng chức năng tạo khuôn mẫu <T: Ban đầu>, bạn cũng có thể vượt qua returnClass dưới dạng Ban đầu. Loại
Devous

Trong mã ban đầu sẽ tạo ra các kết quả khác nhau. Tham số chung Tđược sử dụng để thể hiện mối quan hệ giữa returningClassvà đối tượng được truyền vào completionHandler. Nếu Initiable.Typeđược sử dụng mối quan hệ này bị mất.
EliaCereda

VÀ? Generics không cho phép viếtfunc somefunc<U>()
Gargo

34

bạn có thể nhận được lớp AnyObjectthông qua cách này:

Swift 3.x

let myClass: AnyClass = type(of: self)

Swift 2.x

let myClass: AnyClass = object_getClass(self)

và bạn có thể vượt qua nó như là paramater sau, nếu bạn muốn.


1
bạn cũng có thể làmself.dynamicType
newacct

1
Bất cứ khi nào tôi cố gắng sử dụng myClass, nó sẽ gây ra lỗi "sử dụng loại không khai báo" myClass ". Swift 3, bản dựng mới nhất của Xcode và iOS
Bị nhầm lẫn

@Confuse, tôi thấy không có vấn đề như vậy với điều đó, bạn có thể cần cung cấp cho tôi thêm thông tin về bối cảnh.
holex

Tôi đang làm một nút. Trong mã của nút đó (đó là SpriteKit, vì vậy bên trong touchesBegan) Tôi muốn nút gọi một hàm Class, vì vậy cần tham chiếu đến lớp đó. Vì vậy, trong Lớp Nút tôi đã tạo một biến để giữ tham chiếu đến Lớp. Nhưng tôi không thể có được nó để giữ một Class, hoặc Loại đó là Class đó, bất kể từ ngữ / thuật ngữ đúng là gì. Tôi chỉ có thể lấy nó để lưu trữ các tài liệu tham khảo cho các trường hợp. Tốt hơn là, tôi muốn chuyển một tham chiếu đến Loại lớp vào nút. Nhưng tôi chỉ bắt đầu với việc tạo một tài liệu tham khảo nội bộ và thậm chí không thể làm việc đó được.
Bối rối

Đó là sự tiếp nối về phương pháp luận và suy nghĩ tôi đang sử dụng ở đây: stackoverflow.com/questions/41092440/ . Tôi đã nhận được một số logic làm việc với các Giao thức, nhưng nhanh chóng đi ngược lại thực tế là tôi cũng sẽ cần phải tìm ra các loại và tổng quát liên quan để có được những gì tôi nghĩ tôi có thể làm với các cơ chế đơn giản hơn. Hoặc chỉ cần sao chép và dán rất nhiều mã xung quanh. Đó là những gì tôi thường làm;)
Bối rối

7

Tôi có một trường hợp sử dụng tương tự trong swift5:

class PlistUtils {

    static let shared = PlistUtils()

    // write data
    func saveItem<T: Encodable>(url: URL, value: T) -> Bool{
        let encoder = PropertyListEncoder()
        do {
            let data = try encoder.encode(value)
            try data.write(to: url)
            return true
        }catch {
            print("encode error: \(error)")
            return false
        }
    }

    // read data

    func loadItem<T: Decodable>(url: URL, type: T.Type) -> Any?{
        if let data = try? Data(contentsOf: url) {
            let decoder = PropertyListDecoder()
            do {
                let result = try decoder.decode(type, from: data)
                return result
            }catch{
                print("items decode failed ")
                return nil
            }
        }
        return nil
    }

}

Tôi đang cố gắng làm một cái gì đó tương tự, nhưng tôi gặp lỗi tại callite : Static method … requires that 'MyDecodable.Type' conform to 'Decodable'. Bạn có phiền cập nhật câu trả lời của bạn để đưa ra một cuộc gọi ví dụ loadItemkhông?
Barry Jones

Hóa ra đây là điểm mà tôi phải tìm hiểu sự khác biệt giữa .Type.self(loại và siêu dữ liệu).
Barry Jones

0

Sử dụng obj-getclass:

CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: obj-getclass(self)) { cityInfo in /*...*/

}

Giả sử bản thân là một đối tượng thông tin thành phố.


0

Swift 5

Không hoàn toàn giống như vậy, nhưng tôi đã gặp vấn đề tương tự. Điều cuối cùng đã giúp tôi là:

func myFunction(_ myType: AnyClass)
{
    switch type
    {
        case is MyCustomClass.Type:
            //...
            break

        case is MyCustomClassTwo.Type:
            //...
            break

        default: break
    }
}

Sau đó, bạn có thể gọi nó trong một thể hiện của lớp đã nói như thế này:

myFunction(type(of: self))

Hy vọng điều này sẽ giúp ai đó trong tình huống tương tự của tôi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.