Swift參考某幾種語言,增加了泛型這一機制,讓人又愛又恨。
泛型增加了語言的表現力,減少了冗餘,這是好訊息;然而壞訊息是:對於複雜的實現來說,七繞八不繞,文法容易把人搞暈…以下就是一例。
這是從我實際的項目中摘出來的例子,做了簡化。有童鞋看到後面的代碼可能會問,這麼簡單的問題,幹嘛要繞圈寫這麼複雜的實現。。。這不前面說了麼,因為實際的項目複雜。。。這裡只是簡化到講解本主題,所以若有詞不達意,也請包涵。
這裡有一個通用協議和另一個Main協議,Main協議遵守通用協議:
protocol CommonDelegate { associatedtype Item func invoke(with item:Item)}protocol MainDelegate:CommonDelegate { func save(with item:Item)}
這裡比較奇怪的是Item類型,它是什麼呢。它是實際要操作的Model。
因為上述協議和遵守協議的類在Framework中,而實際的Model在App裡,所以有必要再寫一個Model協議:
protocol FooDelegate{ var name:String {get set} var id:Int {get set} var desc:String {get}}
記住,實際的Foo資料模型類在App裡,它遵守FooDelegate協議:
class Foo:FooDelegate{ var name:String var id:Int var desc: String{ return "\(name):\(id)" } init(name:String,id:Int) { self.name = name self.id = id }}
下面輪到”過渡”類Main隆重出場了:
class Main{ var delegate:MainDelegate! func breed(){ let newFoo = Foo(name: "hopy", id: 1) self.delegate.save(with: newFoo) }}
沒錯,它只有一個breed方法,而其中又調用了委託的save方法。這裡很有意思,實際上它本身不幹啥活,具體幹啥還得委託delegate說了算。
上面這樣寫對麼。。。錯。。。
回到MainDelegate協議看一下,它繼承於CommonDelegate,其中有一個關聯類別型Item,在聲明委託變數delegate的時候必須確定Item的類型,但是這裡啥也沒有說明白…
所以我們需要將Main類修改成如下形式:
class Main<T> where T:MainDelegate,T.Item:FooDelegate{ var delegate:T! func breed(){ let newFoo = Foo(name: "hopy", id: 1) self.delegate.save(with: newFoo) //TODO:注意這行 }}
注意我們在Main裡綁定了一個泛型T,確定了Item的類型為FooDelegate。這裡Item的類型為什麼不是Foo。。。因為前面說過了實際的Model在App裡由使用者定義,它可以是一個CoreData的託管對象或是其他什麼別的東東,Framework控制不了,也沒心思理這些,不管實際資料模型是啥,只要遵守FooDelegate就行了。
看到上面TODO那行了麼。別急,我們最後再來說它。
OK,現在只剩最後一個實際“幹活”的類了,就叫它Maker吧:
class Maker:MainDelegate { typealias Item = Foo var main:Main<Maker>! func didLoad(){ main = Main<Maker>() main.delegate = self main.breed() } func invoke(with item: Item) { print("invoke item:\(item.desc) done!!!") } func save(with item: Item) { print("save item [\(item.desc)] done!!!") }}
因為顯然Maker在App裡,所以我們把Item和Foo綁定;實際上這裡也不可以和FooDelegate綁定,因為不可以將非實體類型和關聯類別型綁定。
Maker裡的main屬性很有意思,它將Main的泛型型別設為自己。
上面的代碼都可在Xcode的playground裡愉快地玩耍,大家可以實際運行下試試。
如果到這裡你還沒有暈,那麼最後我們再來聊一聊,前面TODO那一行。
假設你按我說的嘗試運行一下,你會失望的:就在TODO那行報錯了:
為毛呢。協議裡save方法的參數類型是Item,實際Item類型卻是FooDelegate,你可能會想做一下強轉:
let newFoo = Foo(name: "hopy", id: 1) as FooDelegate
很遺憾,錯誤依舊。我們不可以在Maker裡將Item設定為FooDelegate,原因前面說過了。那麼這裡該怎麼寫呢。
很簡單,你不是要抽象麼。我就給你抽象:
let newFoo = Foo(name: "hopy", id: 1) as! T.Item
現在運行OK啦:
這就是Swift泛型的折騰,謝謝觀賞。