2016-07-26 58 views
3

我想在類型爲[String:SomeClass]的字典中存儲更專門化的類型。這是說明我的問題的一些示例代碼(也可在https://swiftlang.ng.bluemix.net/#/repl/579756cf9966ba6275fc794a一起玩):如何在Swift中的類型爲[String:Class <Protocol>]的Dictionary中存儲Class <ClassImplementingProtocol>類型的值?

class Thing<T> {} 

protocol Flavor {} 

class Vanilla: Flavor {} 

var dict = [String:Thing<Flavor>]() 

dict["foo"] = Thing<Vanilla>() 

它產生錯誤ERROR at line 9, col 28: cannot assign value of type 'Thing<Vanilla>' to type 'Thing<Any>?'

我試過鑄造Thing<Vanilla>() as Thing<Flavor>但產生錯誤cannot convert value of type 'Thing<Vanilla>' to type 'Thing<Flavor>' in coercion

我也嘗試將字典定義爲類型[String:Thing<Any>],但是這也不會改變任何東西。

如何創建一個不同的Thing s的集合而不求助於簡單的[String:AnyObject]

我還要提到的是,類Thing不是由我定義的(實際上它是關於BoltsSwift Task S),因此該解決方案無需類型參數不起作用創建一個基類的Thing

+0

泛型是不變的斯威夫特 - 有很好的理由了。假設你有一個'級巧克力:香料',然後是一個'事情'。現在讓我們假設你可以把'Thing '和'Thing '放在'[Thing ]'中。如果你的'Thing'類有一個'T'類型的屬性 - 你的數組現在說你可以在'Thing '或'Thing '這個屬性上分配'Chocolate()'或'Vanilla() '例子,這是瘋狂的。你試圖解決的實際問題是什麼? – Hamish

+0

我想創建一個BoltsSwift'Task's(或'TaskCompletionSource'es)集合以返回請求相同工作負載的調用者,同時處理它的請求已經在運行。 「任務」具有不同的類型,具體取決於特定工作負載返回的模型類型。所以我需要_some_方式來表達一種只說「任何」事物「的類型」。 – Mike

+0

我決定使用'Protocol BaseThing {}','extension Thing:BaseThing',然後使用'[String:BaseThing]'作爲集合。不完全漂亮,但它的作品。 – Mike

回答

6

A Thing<Vanilla>不是Thing<Flavor>Thing不是協變的。 Swift沒有辦法表達Thing是協變的。這有很好的理由。如果是你所要求的被允許的周圍沒有仔細的規則,我將被允許寫下面的代碼:

func addElement(array: inout [Any], object: Any) { 
    array.append(object) 
} 

var intArray: [Int] = [1] 
addElement(array: &intArray, object: "Stuff") 

IntAny一個亞型,因此,如果[Int]人的[Any]一個亞型,我可以使用這個函數將字符串追加到一個int數組中。這打破了類型系統。不要這樣做。

根據您的確切情況,有兩種解決方案。如果它是一個值類型,那麼重新包裝:

let thing = Thing<Vanilla>(value: Vanilla()) 
dict["foo"] = Thing(value: thing.value) 

如果它是一個引用類型,具有type eraser框它。例如:

// struct unless you have to make this a class to fit into the system, 
// but then it may be a bit more complicated 
struct AnyThing { 
    let _value:() -> Flavor 
    var value: Flavor { return _value() } 
    init<T: Flavor>(thing: Thing<T>) { 
     _value = { return thing.value } 
    } 
} 

var dict = [String:AnyThing]() 
dict["foo"] = AnyThing(thing: Thing<Vanilla>(value: Vanilla())) 

類型橡皮擦的細節可能因您的基礎類型而異。


順便說一句:這方面的診斷已經相當不錯了。如果你試圖打電話給我addElement以上在Xcode 9,你會得到這樣的:

Cannot pass immutable value as inout argument: implicit conversion from '[Int]' to '[Any]' requires a temporary 

這是什麼告訴你的是,斯威夫特是願意傳球[Int],你問[Any]作爲特殊情況的陣列(雖然這種特殊待遇並未擴展到其他通用類型)。 但是它只會通過創建數組的臨時(不可變)副本來允許它。 (這是另一個很難說明Swift性能的例子,在其他語言中「鑄造」的情況下,Swift可能會創建一個副本,或者它可能不會,很難確定。)

+0

謝謝你,很好的回答!我在Swift標準庫中見過很多類型的擦除器,並且想知道它們用於什麼。 –

+0

請注意,帶有inout的反例只能說明對於作爲inout參數傳遞的變量必須有不變性(因爲inout可以讀寫該變量);這並不是因爲任意泛型不變(事實上,當你在編輯中突出顯示時,「數組」是一個特例 - 「Int」確實是「[Any]」的一個子類型,但你的例子仍然是非法的) 。你也可以看到沒有泛型,例如'func foo(a:inout Any,b:Any){a = b}; var i = 1; foo(a:&i,b:「Stuff」)是非法的。 – Hamish

1

解決此問題的一種方法是向Thing添加一個初始化程序,並創建一個Thing<Flavor>,該對象將包含一個Vanilla對象。

它看起來是這樣的:

class Thing<T> { 

    init(thing : T) { 
    } 

} 

protocol Flavor {} 

class Vanilla: Flavor {} 

var dict = [String:Thing<Flavor>]() 

dict["foo"] = Thing<Flavor>(thing: Vanilla())