翻譯:https://engineering.pinterest.com/blog/immutable-models-and-data-consistency-our-ios-app
今年早些時候,為了讓iOS客戶端反應(yīng)更快,體驗更簡明,特別是針對非美國用戶,我們對iOS客戶端進行了重構(gòu)。一個重構(gòu)的目標就是,將客戶端的模型層變?yōu)橥耆豢勺?。這篇文章中,我將討論這種做法背后的動機,探討我們的新系統(tǒng)在更新模型,加載網(wǎng)絡(luò)接口數(shù)據(jù)和保持數(shù)據(jù)一致性方面的做法。
不可變模型?
“不可變模型”是近期經(jīng)常聽到討論的術(shù)語,而且很多客戶端已經(jīng)轉(zhuǎn)為不可變模型。不可變意味著模型一旦初始化完成之后就不能再修改。為什么要使用呢,可變模型的主要問題是數(shù)據(jù)處于共享狀態(tài)。
考慮下面這種情況,在一個可變模型系統(tǒng)中,A和B都引用了C。

如果A修改了C,A和B都將得到變化后的值。這很好,但是如果B不期望如此,就會發(fā)生問題。
舉個例子,在消息界面中,有兩個其他的用戶。每個消息對象都有一個“用戶”屬性。

當我停留在這個頁面上時,客戶端中的其它部分把Devin從對話中移除(可能是接收了服務(wù)器的回應(yīng),更改了數(shù)據(jù)模型)。這時我點擊了第二行想屏蔽Devin,會關(guān)聯(lián)用戶列表中的第二個對象。將要返回的是Stephanie而不是Devin,最終我把錯誤的人加入了黑名單。
不可變對象是線程安全的。以前,我們得擔心一個線程寫數(shù)據(jù)的時候,另一個線程正在讀數(shù)據(jù)。在新系統(tǒng)中,對象自從創(chuàng)建后就不可以更改,所以我們可以安全的并發(fā)的使用多線程讀取數(shù)據(jù),而不用擔心數(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ù),直到用戶進入圖釘聚合頁才需要食譜原料(recipe ingredients)屬性。

客戶端中有一個PINCache數(shù)據(jù)緩存中心,PINCache是一個數(shù)據(jù)模型緩存庫,已經(jīng)開源。緩存的key是服務(wù)器為每個模型提供的唯一的ID。每當我們從服務(wù)器得到數(shù)據(jù),先從緩存中心里檢查模型是否存在。如果存在,將服務(wù)器的新的數(shù)據(jù)和模型存在的屬性進行融合,生成新的模型。新的模型將替代緩存中舊的模型。這樣,緩存的模型總是擁有我們接收到的所有最新屬性。

數(shù)據(jù)一致性
模型改變(比如新建一個模型),應(yīng)該通知到展示模型的視圖。我們之前使用KVO來完成,但是KVO對不可變不起作用,他只觀察一個實例的改變?,F(xiàn)在我們使用基于NSNotificationCenter的通知系統(tǒng)來廣播數(shù)據(jù)模型的更改。
觀察變化
視圖或者視圖控制器可以注冊成為模型更新的觀察者。這個例子中,消息頁面控制器監(jiān)聽了他的消息對象的更新通知??刂破餍枰滥P妥兓驗樾碌哪P涂赡軙聦傩?。

下面的代碼,創(chuàng)建了一個觀察者用來監(jiān)聽模型的變化,觀察的名字是模型的唯一標識符。在這個方法下,使用基于block的NSNotificationCenter接口,這樣可以比較方便的管理觀察著的生命周期。
[self.notificationManager addObserverForUpdatedModel:self.message block:^(NSNotification *notification) {
// Update message view here!
}];
notificationManager只是一個強引用觀察者的NSObject,由于它是視圖控制器的屬性,得確??刂破麂N毀的時候,它也會銷毀,并且能夠解除對所有觀察者的引用關(guān)系。
廣播變化
當消息對象更新,會廣播一個更新通知
Message *newMessage = [[Message alloc] initWithBuilder:newBuilder];
[NotificationManager postModelUpdatedNotificationWithObject:newMessage];
postModelUpdatedNotificationWithObject方法會查找緩存中心中擁有相同標識符的對象,并將這個對象廣播出去。
更新界面
收到通知后,新的對象可以通過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ā)。下一片文章,將介紹我們是如何進行自動合成模型類以及其它知識。