翻譯:https://engineering.pinterest.com/blog/immutable-models-and-data-consistency-our-ios-app
今年早些時候,為了讓iOS客戶端反應(yīng)更快,體驗更簡明逼友,特別是針對非美國用戶夺鲜,我們對iOS客戶端進(jìn)行了重構(gòu)变勇。一個重構(gòu)的目標(biāo)就是憨攒,將客戶端的模型層變?yōu)橥耆豢勺儭_@篇文章中待笑,我將討論這種做法背后的動機,探討我們的新系統(tǒng)在更新模型抓谴,加載網(wǎng)絡(luò)接口數(shù)據(jù)和保持?jǐn)?shù)據(jù)一致性方面的做法暮蹂。
不可變模型?
“不可變模型”是近期經(jīng)常聽到討論的術(shù)語癌压,而且很多客戶端已經(jīng)轉(zhuǎn)為不可變模型仰泻。不可變意味著模型一旦初始化完成之后就不能再修改。為什么要使用呢滩届,可變模型的主要問題是數(shù)據(jù)處于共享狀態(tài)集侯。
考慮下面這種情況,在一個可變模型系統(tǒng)中帜消,A和B都引用了C棠枉。
如果A修改了C,A和B都將得到變化后的值泡挺。這很好辈讶,但是如果B不期望如此,就會發(fā)生問題娄猫。
舉個例子贱除,在消息界面中生闲,有兩個其他的用戶。每個消息對象都有一個“用戶”屬性勘伺。
當(dāng)我停留在這個頁面上時跪腹,客戶端中的其它部分把Devin從對話中移除(可能是接收了服務(wù)器的回應(yīng),更改了數(shù)據(jù)模型)飞醉。這時我點擊了第二行想屏蔽Devin冲茸,會關(guān)聯(lián)用戶列表中的第二個對象。將要返回的是Stephanie而不是Devin缅帘,最終我把錯誤的人加入了黑名單轴术。
不可變對象是線程安全的。以前钦无,我們得擔(dān)心一個線程寫數(shù)據(jù)的時候逗栽,另一個線程正在讀數(shù)據(jù)。在新系統(tǒng)中失暂,對象自從創(chuàng)建后就不可以更改彼宠,所以我們可以安全的并發(fā)的使用多線程讀取數(shù)據(jù),而不用擔(dān)心數(shù)據(jù)錯亂弟塞。這讓開發(fā)變得更輕松凭峡,因為客戶端可以越來越支持并發(fā)和多線程。
更新數(shù)據(jù)模型
自從數(shù)據(jù)模型創(chuàng)建后就變得完全不可變决记,唯一更新或者說改變的方法是構(gòu)造一個新的模型摧冀。有兩種方式來做這件事:
-
使用字典來初始化模型(通常來自json報文)
User *user = [[User alloc] initWithDictionary:dictionary];
-
使用構(gòu)造器對象,通常是擁有數(shù)據(jù)模型所有屬性的可變描述系宫∷靼海可以基于存在的數(shù)據(jù)模型中創(chuàng)建一個構(gòu)造器,修改你想修改的屬性扩借,然后調(diào)用initWithBuilder方法來返回一個全新模型椒惨。(關(guān)于這一點后續(xù)文章會有更多介紹)
// Change the current user's username to “taylorswift” UserBuilder *userBuilder = [[UserBuilder alloc] initWithModel:self.currentUser]; userBuilder.username = @"taylorswift"; self.currentUser = [[User alloc] initWithBuilder:userBuilder];
加載和緩存接口數(shù)據(jù)
我們的接口允許從服務(wù)器拉取部分模型數(shù)據(jù),模型屬性的部分子集往枷。比如在圖釘列表頁框产,只需要圖片鏈接和描述即可,并不需要全部數(shù)據(jù)错洁,直到用戶進(jìn)入圖釘聚合頁才需要食譜原料(recipe ingredients)屬性秉宿。
客戶端中有一個PINCache數(shù)據(jù)緩存中心,PINCache是一個數(shù)據(jù)模型緩存庫屯碴,已經(jīng)開源描睦。緩存的key是服務(wù)器為每個模型提供的唯一的ID。每當(dāng)我們從服務(wù)器得到數(shù)據(jù)导而,先從緩存中心里檢查模型是否存在忱叭。如果存在隔崎,將服務(wù)器的新的數(shù)據(jù)和模型存在的屬性進(jìn)行融合,生成新的模型韵丑。新的模型將替代緩存中舊的模型爵卒。這樣,緩存的模型總是擁有我們接收到的所有最新屬性撵彻。
數(shù)據(jù)一致性
模型改變(比如新建一個模型)钓株,應(yīng)該通知到展示模型的視圖。我們之前使用KVO來完成陌僵,但是KVO對不可變不起作用轴合,他只觀察一個實例的改變。現(xiàn)在我們使用基于NSNotificationCenter的通知系統(tǒng)來廣播數(shù)據(jù)模型的更改碗短。
觀察變化
視圖或者視圖控制器可以注冊成為模型更新的觀察者受葛。這個例子中,消息頁面控制器監(jiān)聽了他的消息對象的更新通知偎谁∽芴玻控制器需要知道模型變化,因為新的模型可能會更新屬性巡雨。
下面的代碼咳秉,創(chuàng)建了一個觀察者用來監(jiān)聽模型的變化,觀察的名字是模型的唯一標(biāo)識符鸯隅。在這個方法下,使用基于block的NSNotificationCenter接口向挖,這樣可以比較方便的管理觀察著的生命周期蝌以。
[self.notificationManager addObserverForUpdatedModel:self.message block:^(NSNotification *notification) {
// Update message view here!
}];
notificationManager只是一個強引用觀察者的NSObject,由于它是視圖控制器的屬性何之,得確备控制器銷毀的時候,它也會銷毀溶推,并且能夠解除對所有觀察者的引用關(guān)系徊件。
廣播變化
當(dāng)消息對象更新,會廣播一個更新通知
Message *newMessage = [[Message alloc] initWithBuilder:newBuilder];
[NotificationManager postModelUpdatedNotificationWithObject:newMessage];
postModelUpdatedNotificationWithObject方法會查找緩存中心中擁有相同標(biāo)識符的對象蒜危,并將這個對象廣播出去虱痕。
更新界面
收到通知后,新的對象可以通過NSNotification的object屬性獲得辐赞。視圖控制器可以用新的模型對象做任何事情部翘。
__weak __typeof__(self) weakSelf = self;
[self.notificationManager addObserverForUpdatedModel:self.message block:^(NSNotification *notification) {
__typeof__(self) strongSelf = weakSelf;
Message *newMessage = (Message *)notification.object;
strongSelf.usersInMessageThread = newMessage.users;
[strongSelf.tableView reloadData];
}];
待更新
對于我們這種體量的客戶端來說,完全替換為不可變模型不是件易事响委,在這個過程中新思,我們也創(chuàng)造了一些很棒的工具來輔助開發(fā)窖梁。下一片文章,將介紹我們是如何進(jìn)行自動合成模型類以及其它知識夹囚。