Swift泛型語法高階處理一例
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泛型的折騰,謝謝觀賞!