日常開(kāi)發(fā)中我們經(jīng)常會(huì)用到各種第三方庫(kù),而如何使用別人的代碼其實(shí)也有一點(diǎn)講究蛇摸。如果直接在業(yè)務(wù)代碼中使用第三方庫(kù),導(dǎo)致項(xiàng)目對(duì)某個(gè)第三方庫(kù)的依賴(lài)過(guò)重,那一旦因?yàn)楦鞣N原因需要更換方案的時(shí)候骨宠,所需要修改的代碼量之大可能還不如直接重寫(xiě)了浮定。
所以關(guān)鍵就在于如何將第三方庫(kù)與業(yè)務(wù)代碼進(jìn)行解耦,常見(jiàn)的思路就是對(duì)第三方庫(kù)進(jìn)行二度封裝层亿。而具體怎么封裝桦卒,可以有很多方案,比較簡(jiǎn)單粗暴的是使用繼承匿又。譬如我們?cè)陧?xiàng)目中集成了 AFNetworking方灾,但我們不直接使用AFHTTPRequestOperationManager
來(lái)進(jìn)行網(wǎng)絡(luò)請(qǐng)求,而是新建一個(gè)MyHTTPRequestOperationManager
繼承自AFHTTPRequestOperationManager
碌更,聲明一系列方法供業(yè)務(wù)方調(diào)用裕偿,這些方法可以執(zhí)行一些額外的操作,然后將請(qǐng)求消息轉(zhuǎn)發(fā)給父類(lèi)痛单。這樣如果以后不準(zhǔn)備使用 AFNetworking 了嘿棘,只需要改寫(xiě)AFHTTPRequestOperationManager
這個(gè)類(lèi),而不用去修改其它地方旭绒。這是可行的鸟妙,但是我們可以有更好的解決方案。
2015年的 WWDC 有一個(gè) topic 是 Protocol-Oriented Programming in Swift挥吵,蘋(píng)果宣稱(chēng) Swift 是一門(mén)面向協(xié)議的語(yǔ)言重父,說(shuō)是這將顛覆人們寫(xiě)代碼的方式。這多少有點(diǎn)夸大其辭忽匈,畢竟所謂的面向協(xié)議編程其實(shí)跟幾十年前就被提出的設(shè)計(jì)原則——面向接口編程而不是面向?qū)崿F(xiàn)編程房午,是有異曲同工之妙的。
那接下來(lái)丹允,我就運(yùn)用面向協(xié)議的思維來(lái)進(jìn)行解耦歪沃。舉個(gè)真實(shí)的案例吧,最近我準(zhǔn)備在項(xiàng)目中集成 Realm 來(lái)做緩存嫌松,于是我先聲明了一個(gè)CacheManagerType
:
typealias Handler = () -> Void
protocol CacheManagerType {
associatedtype Cacheable
static var defaultManager: Self { get }
func addItem(item: Cacheable)
func updateItem(item: Cacheable)
func deleteItem(item: Cacheable)
func addItems(items: [Cacheable])
func updateItems(items: [Cacheable])
func deleteItems(items: [Cacheable])
func deleteAllItems()
func itemWithId(id: Int) -> Cacheable?
func updateWithHandler(handler: Handler)
}
Swift 不直接支持范型協(xié)議沪曙,不過(guò)可以用associatedtype
達(dá)到相同的效果,這邊的Cacheable
就是一個(gè)范型萎羔,代表了可以被緩存的類(lèi)型液走。接下來(lái)我定義一個(gè)RealmCacheManager
,它遵守CacheManagerType
協(xié)議:
private let errorMessage = "Realm exception: write failed."
private let cacheQueueName = "cn.sheepy.CacheManager"
private let cacheQueue = dispatch_queue_create(cacheQueueName, DISPATCH_QUEUE_CONCURRENT)
private let instance = CacheManager()
final class RealmCacheManager: NSObject, CacheManagerType {
static var defaultManager: RealmCacheManager {
return instance
}
private var cache: RLMRealm {
// The default `RLMRealm` instance for the current thread
return RLMRealm.defaultRealm()
}
// MARK: - Handle item
func addItem(item: RLMObject) {
invokeInCacheQueue {
self.cache.addObject(item)
}
}
func updateItem(item: RLMObject) {
invokeInCacheQueue {
self.cache.addOrUpdateObject(item)
}
}
func deleteItem(item: RLMObject) {
invokeInCacheQueue {
self.cache.deleteObject(item)
}
}
// MARK: - Handle items
func addItems(items: [RLMObject]) {
invokeInCacheQueue {
self.cache.addObjects(items)
}
}
func updateItems(items: [RLMObject]) {
invokeInCacheQueue {
self.cache.addOrUpdateObjectsFromArray(items)
}
}
func deleteItems(items: [RLMObject]) {
invokeInCacheQueue {
self.cache.deleteObjects(items)
}
}
func deleteAllItems() {
invokeInCacheQueue {
self.cache.deleteAllObjects()
}
}
// MARK: - Handle with closure
func updateWithHandler(handler: Handler) {
invokeInCacheQueue {
handler()
}
}
// MARK: - Query
func itemWithId<T: RLMObject>(id: Int) -> T? {
if let result = T.objectsWhere("id == \(id)").firstObject() as? T {
return result
} else {
return nil
}
}
// MARK: - Private methods
private func invokeInCacheQueue(handler: Handler) {
dispatch_async(cacheQueue) {
self.invoke(handler)
}
}
private func invoke(handler: Handler) {
do {
try cache.transactionWithBlock {
handler()
}
} catch {
printLog(errorMessage)
}
}
}
這個(gè)類(lèi)是CacheManagerType
的 Realm 版本的實(shí)現(xiàn)贾陷,我們可以定義生成方法:
fun genericCacheManager<T: CacheManagerType>() -> T {
return T.defaultManager
}
func cacheManager() -> RealmCacheManager {
return genericCacheManager()
}
在業(yè)務(wù)方需要進(jìn)行緩存操作的時(shí)候缘眶,只需要調(diào)用生成方法拿到一個(gè)defaultManager
單例即可使用:
let cacheManager = cacheManager()
如果要以它作為方法參數(shù),那該方法應(yīng)該聲明為范型方法:
func doSometihingWithCacheManager<T: CacheManagerType>(cacheManager: T)
這樣髓废,一旦以后需要更換緩存方案巷懈,譬如還是準(zhǔn)備使用 CoreData,那只需要?jiǎng)?chuàng)建一個(gè)CoreDataCacheManager
慌洪,讓它遵守CacheManagerType
顶燕,然后把cacheManager
方法的返回類(lèi)型改為CoreDataCacheManager
即可:
func cacheManager() -> CoreDataCacheManager {
return genericCacheManager()
}
這樣原先業(yè)務(wù)代碼中得到的cacheManager
就自動(dòng)變成CoreDataCacheManager
的一個(gè)單例了凑保。用了一段時(shí)間如果覺(jué)得還是 Realm 好用,依舊只要把cacheManager
方法的返回類(lèi)型改為RealmCacheManager
就可以了涌攻,非常方便欧引。
以上方案還有一個(gè)問(wèn)題,就是RealmCacheManager
的一系列方法的參數(shù)都使用了RLMObject
恳谎,這是Realm
中 Model 的基類(lèi)芝此,業(yè)務(wù)代碼中需要生成具體RLMObject
對(duì)象才能使用這些緩存方法,這也是一種緊耦合因痛。一旦移除了 Realm婚苹,還是需要修改許多業(yè)務(wù)代碼。更合適的方法是聲明一個(gè)Cacheable
協(xié)議:
protocol Cacheable {
var entity: RLMObject { get }
}
然后讓一個(gè)原先就在項(xiàng)目中用來(lái)表示 Model 的類(lèi)型來(lái)遵守這個(gè)協(xié)議鸵膏,譬如 JOSN租副、Dictionary、MTLModel 等等都行较性。譬如這樣:
extension XXXModel: Cacheable {
var entity: RLMObject {
return RLMObject(object: self)
}
}
具體的轉(zhuǎn)換邏輯可以用 extension 加到 RLMObject 中去用僧,這樣就把轉(zhuǎn)換邏輯和 RLMObject 對(duì)象從業(yè)務(wù)代碼中剝離出來(lái)。而且CacheManagerType
中的associatedtype
和defaultManager
也可以去掉了赞咙,CacheManagerType
可以作為類(lèi)型使用了(Swift 中使用了associatedtype
和Self
的協(xié)議不能作為類(lèi)型责循,只能作為范型約束)。相應(yīng)的在RealmCacheManager
中相關(guān)的方法要進(jìn)行一點(diǎn)修改攀操,以addItem
為例:
func addItem(item: Cacheable) {
invokeInCacheQueue {
self.cache.addObject(item.entity)
}
}
生成方法只要這樣就行了:
func cacheManager() -> CacheManagerType {
return RealmCacheManager.defaultManager
}
以后要換緩存方案的時(shí)候院仿,只需要修改Cacheable
中entity
的返回類(lèi)型(譬如 CoreData 中的NSManagedObject
),然后相應(yīng)地添加轉(zhuǎn)換邏輯就行了速和。cacheManager
實(shí)例的替換跟之前差不多歹垫,把cacheManager
中的RealmCacheManager
換成CoreDataCacheManager
就行了。
說(shuō)了這么多颠放,其實(shí)主要目的就是在工具庫(kù)和業(yè)務(wù)代碼間設(shè)置一層抽象屏障排惨,不讓業(yè)務(wù)代碼對(duì)某個(gè)類(lèi)庫(kù)依賴(lài)過(guò)重(當(dāng)然,如果是確定會(huì)一直使用碰凶,中途不會(huì)有任何變更的第三方庫(kù)暮芭,那大可不必如此)。具體的做法不必拘泥于我文中的思路欲低,大家盡可各展神通辕宏。