2014-06-12 64 views
2

我有一個用於製作和處理JSON API請求的泛型類。我傳入TParamTResult模板參數,但是當我使用派生類型時,它的實現沒有被調用。不被稱爲泛型類模板的派生類方法

下面是一些代碼,你可以在操場扔來說明:

import Cocoa 

// Base class for parameters to POST to service 
class APIParams { 
    func getData() -> Dictionary<String, AnyObject> { 
     return Dictionary<String, AnyObject>() 
    } 
} 

// Base class for parsing a JSON Response 
class APIResult { 
    func parseData(data: AnyObject?) { 

    } 
} 

// Derived example for a login service 
class DerivedAPIParams: APIParams { 
    var user = "[email protected]" 
    var pass = "secret" 

    // THIS METHOD IS CALLED CORRECTLY 
    override func getData() -> Dictionary<String, AnyObject> { 
     return [ "user": user, "pass": pass ] 
    } 
} 

// Derived example for parsing a login response 
class DerivedAPIResult: APIResult { 
    var success = false 
    var token:String? = "" 

    // THIS METHOD IS NEVER CALLED 
    override func parseData(data: AnyObject?) { 
     /* 
     self.success = data!.valueForKey("success") as Bool 
     self.token = data!.valueForKey("token") as? String 
     */ 

     self.success = true 
     self.token = "1234" 
    } 
} 

class APIOperation<TParams: APIParams, TResult: APIResult> { 
    var url = "http://localhost:3000" 

    func request(params: TParams, done: (NSError?, TResult?) ->()) {    
     let paramData = params.getData() 

     // ... snip making a request to website ... 

     let result = self.parseResult(nil) 

     done(nil, result) 
    } 

    func parseResult(data: AnyObject?) -> TResult { 
     var result = TResult.self() 

     // This should call the derived implementation if passed, right? 
     result.parseData(data) 

     return result 
    } 
} 

let derivedOp = APIOperation<DerivedAPIParams, DerivedAPIResult>() 
let params = DerivedAPIParams() 

derivedOp.request(params) {(error, result) in 
    if result? { 
     result!.success 
    } 
} 

真正奇怪的是,只有DerivedAPIResult.parseData()不叫,而DerivedAPIParams.getData()方法被調用。任何想法爲什麼?

+0

你不應該爲你的基類使用'protocol'嗎? https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Protocols.html – Mohsen

+0

@Mohsen你也可以使用。如果您使用類型,則會將類限制爲該類型或派生類型。如果您使用協議,它會限制實現該協議的類型。 「[類型約束指定類型參數必須從特定類繼承,或者符合特定協議或協議組合。](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/ swift_programming_language/Generics.html#// apple_ref/doc/uid/TP40014097-CH26-XID_244)「 –

回答

2

UPDATE:該缺陷被固定的XCode 6.3β1(蘋果夫特版本1.2(swiftlang-602.0.37.3鐺-602.0.37))

使用的XCode 6.1(SWIFT 1.1)當一種解決方法增加了信息 看到這些開發論壇線程的詳細信息: https://devforums.apple.com/thread/251920?tstart=30 https://devforums.apple.com/message/1058033#1058033

在一個非常相似的代碼示例我有完全相同的問題。在Beta版之後等待beta版「修復」後,我做了更多的挖掘,並發現通過生成所需的基類init()可以獲得預期的結果。

舉例來說,這裏是馬特·吉布森的降低例如「固定」,加入適當的init()來ApiResult

// Base class for parsing a JSON Response 
class APIResult { 
    // adding required init() to base class yields the expected behavior 
    required init() {} 
} 

// Derived example for parsing a login response 
class DerivedAPIResult: APIResult { 

} 

class APIOperation<TResult: APIResult> { 
    init() {    
     // EDIT: workaround for xcode 6.1, tricking the compiler to do what we want here 
     let tResultClass : TResult.Type = TResult.self 
     var test = tResultClass() 
     // should be able to just do, but it is broken and acknowledged as such by Apple 
     // var test = TResult() 

     println(test.self) // now shows that we get DerivedAPIResult 
    } 
} 

// Templated creation creates APIResult 
let derivedOp = APIOperation<DerivedAPIResult>() 

我不知道爲什麼這個工程。如果我獲得時間,我會深入挖掘,但我最好的猜測是,由於某些原因需要init會導致生成不同的對象分配/構造代碼,從而強制正確設置我們所期望的vtable。

+0

太棒了。感謝您花時間在找到解決方案後將這個答案放在一起。 – Jacob

+0

不幸的是,這種解決方法不再適用於Swift 1.1(XCode 6.1 GM種子)。如果我有任何問題,我會發布更多信息。 – slund

+1

編輯答案以顯示XCode 6.1的解決方法(Swift 1.1) – slund

1

看起來可能令人驚訝,當然。我已經減少了你的情況的東西,而簡單,這可能有助於弄清楚發生了什麼事情:

// Base class for parsing a JSON Response 
class APIResult { 
} 

// Derived example for parsing a login response 
class DerivedAPIResult: APIResult { 
} 

class APIOperation<TResult: APIResult> { 
    init() { 
     var test = TResult() 
     println(test.self) // Shows that we get APIResult, not DerivedAPIResult 
    } 
} 

// Templated creation creates APIResult 
let derivedOp = APIOperation<DerivedAPIResult>() 

...所以它似乎與類型約束創建模板類的新實例,給你一個約束類的實例,而不是用於實例化特定模板實例的派生類。

現在,我想說Swift中的泛型可能會更喜歡你不要在模板代碼中創建你自己的衍生模板約束類實例,而只是定義要保存實例的地方被然後傳入我的意思是,這個作品:

// Base class for parsing a JSON Response 
class APIResult { 
} 

// Derived example for parsing a login response 
class DerivedAPIResult: APIResult { 
} 

class APIOperation<T: APIResult> { 
    var instance: T 
    init(instance: T) { 
     self.instance = instance 
     println(instance.self) // As you'd expect, this is a DerivedAPIResult 
    } 
} 

let derivedOpWithPassedInstance = APIOperation<DerivedAPIResult>(instance: DerivedAPIResult()) 

...但我不清楚你是否嘗試在技術上應該被允許或沒有什麼。

我的猜測是泛型的實現方式意味着在創建模板以從模板中的「nothing」創建派生類型的對象時沒有足夠的類型信息 - 所以您必須在代碼中創建它們,它知道它想要使用的派生類型,並將它們傳入,以便由模板約束類型保存。

+1

感謝您的關注。我已經提交了一份當前的錯誤報告,並會在看到結果時嘗試留下更新。 – Jacob

-1

parseData需要定義爲class func,它創建自己的一個實例,分配任何實例屬性,然後返回該實例。基本上,它需要是一種工廠方法。在類型上調用.self()只是將該類型作爲值訪問,而不是實例。我很驚訝你不會在類型上調用實例方法時出現某種錯誤。

+0

不,類方法將無法在派生實例上設置實例值,並且在本例中本質上是無用的。 – Jacob