愛悠閑 > Mantle源代碼閱讀筆記 一

Mantle源代碼閱讀筆記 一

分類: iOS  |  作者: colorapp 相關  |  發布日期 : 2015-12-18  |  熱度 : 483°
最近的項目需求需要持久化一些對象,由于只是一些比較簡單的數據,使用NSUserDefaults進行存儲即可。之前實現過比較簡單自動archive和unarchive的操作。原理很簡單,遍歷NSObject的property list,然后通過valueForKey:和setValue:forKey:方法進行操作。這種實現不能滿足我的新需求,我的新需求需要做到將property為其他類型的對象也做到自動archive和unarchive,再加上JSON解析方面的工作量,直接粗暴通過硬編碼實現會產生一大堆verbose的代碼,自己實現需要自動化archive和unarchive的代碼需要的工作量較大。于是順便看了一下Mantle的源代碼,發現其中這方面的處理很不錯,各方面很合理,就通過這個實現了。

Mantle解析JSON或者NSCoding操作我認為實際上都可以分成兩個大步驟來閱讀: Transform賦值 。Mantle的源代碼不是很多,但是代碼很干凈,注釋也很完善。

我把全部文件根據我認為的步驟進行了一下分類:
1. Transform相關:
MTLJSONAdapter
MTLManagedObjectAdapter
MTLValueTransformer
NSValueTransformer+MTLInversionAdditions
NSValueTransformer+MTLPredefinedTransformerAdditions
MTLModel+NSCoding

2.賦值相關:
MTLModel

3.工具類:
MTLReflection:
NSArray+MTLManipulationAdditions
NSDictionary+MTLManipulationAdditions
NSError+MTLModelException
NSObject+MTLComparisonAdditions

4. extobjc:
          MTLEXTKeyPathCoding
          MTLEXTRuntimeExtension
          MTLEXTScope
          metamacros


從NSDictionary到Model


把JSON數據解析為Model只需要下面兩行代碼即可:


Transform過程


從JSON轉換到model,方法入口是在 MTLJSONAdapter 的 modelOfClass:fromJSONDictionary:error: ,詳細邏輯的實現方法是 - (id)initWithJSONDictionary:modelClass:error: 。這個方法在入口處進行了assert,modelClass的類型必須是MTLModel的子類,同時modelClass必須實現MTLJSONSerializing protocol。



接下來就是上面這段代碼,這段代碼比較有意思,它涉及到一個我們經常使用卻不太在意的東西,類簇(class cluster),這個設計模式在Cocoa中使用很廣泛,最明顯的例子是NSNumber,關于class cluster可以參考: 鏈接地址。這里我們只需要知道,如果在使用Mantle的過程中,我們要使用class cluster,只需要實現這個方法,然后返回具體的class類型即可。

========================================================================

接下來我們需要處理 JSONKeyPathsByPropertyKey 返回的值,這個值是model的property與JSON的key之間的映射關系,例如官方示例JSONKeyPathsByPropertyKey中返回的數據如下:


在把原始JSON數據轉換為model需要的Dictionary之前,對JSONKeyPathsByPropertyKey的返回的數據進行驗證,在上面這段代碼中就是做這個事情,主要驗證兩方面,一方面,JSONKeyPathsByPropertyKey中要求的model的property在modelClass中是否存在,另外一方面,JSONKeyPathsByPropertyKey要求JSON的key是否是合法的,必須是NSString或者NSNull.null。

其中,NSNull.null表示忽略model中的這個property( 鏈接地址),不進行賦值,這個需求也是經常遇到的。

這里的 [self.modelClass propertyKeys]; 后面在runtime部分會在講解一下,這里只需要知道,它返回了modelClass的property列表即可。

========================================================================

完成對 JSONKeyPathsByPropertyKey 返回的驗證之后,下面要做的是把JSON數據transform為model的property要求的類型,比如說JSON數據中返回的url對應的數據是字符串,但是model中的URL要求的是NSURL *類型,這個轉換過程就是在這個步驟中完成的。

首先需要說一下 NSValueTransformer,這個東西在我沒有使用Mantle之前也不太了解,看來一下Mantle中的使用,發現這個東西的確是非常方便靈活,與NSURLProtocol類似,正常來說Transformer的使用需要通過register class,Mantle中的用法則是直接獲取transformer對象,關于NSValueTransformer可以參考: 鏈接地址


如下面這段代碼,遍歷propertyKeys,這個是modelClass的屬性列表,這個列表是通過MTLModel的+propertyKeys方法獲取的。通過propertyKey獲取它對應JSON的key(-JSONKeyPathForPropertyKey:),得到key值JSONKeyPath之后,校驗JSONDictionary中這個JSONKeyPath中的value是否合法,如果校驗過程中拋出異常,則catch之后,返回nil,設置error。

上面這段代碼中,第一個比較有意思的地方是,valueForKeyPath: VS objectForKey:, 鏈接地址,對NSDictionary來說,這兩個差異不是特別明顯。第二個比較有意思的是NSLog中使用的“%[email protected]”這種輸出格式, 鏈接地址


========================================================================

下一步要做的事情就是整個transform的核心步驟了,我們首先需要把JSON數據轉換為model所需要的類型,這個需要通過獲取NSValueTransformer來進行轉換,所以第一步是獲取一個NSValueTransformer對象。

使用官方的demo作為示例,看看這個NSValueTransformer在model中是如何生成的,下面這個是寫在model中的一個靜態方法,返回一個NSValueTransformer對象,這個對象不僅支持JSON transform到Model,也支持Model 進行reverse transform到JSON,分別對應下面兩個block中的代碼塊。比如下面這個例子中,JSON轉換為model的時候,JSON數據中的string會被轉換為NSDate *,在model被轉換為JSON的時候,NSDate*會被轉為JSON中數據string。



adapter通過 -JSONTransformerForKey: 方法來獲取一個 NSValueTransformer,這個方法的代碼實現如下。首先,通過MTLSelectorWithKeyPattern來生成selector,OC的方法簽名比較簡單,基本上使用字符串就行進行調用,這里的生成的規則是把屬性的名稱xxx與JSONTransformer進行字符串拼接。OC的靈活讓我們可以通過字符串就能進行消息發送,這里后面會詳細解析用法。可以看到除了按一定規則拼接selector的方式從model中獲取transformer之外,最后還會調用model的+JSONTransformerForKey: 方法,當然這個很少用,寫一大堆if/else判斷代碼閱讀起來肯定不夠干凈。



講完如何獲取JSONTransformer之后,我們下面開始看一下transform階段比較關鍵的代碼,下面的代碼實際上非常好閱讀和理解,獲取transformer之后,調用transformer的-transformedValue: 來直接將JSON中的value轉換為我們需要的類型。這里可以看出Mantle對NSNull.null的處理,不用我們擔心JSON中的null導致程序crash的問題。轉換到我們需要的數據類型之后,接下來會把這個數據存放到一個dictaionaryValue臨時字典中,JSON中所有的數據都transform之后,我們就能得到我們需要數據類型的dictaionary了,后面我們會利用這個dictionary來對model進行賦值操作。同時,可以看看這里對try/catch以及NSError的利用,這個是個比較簡單,但是我們日常開發中卻經常疏漏的東西,這種技巧在商業代碼中使用是很有必要的。



通過上面的步驟,我們就能得到JSON轉換我們需要的數據類型之后的結果,看下面的代碼,就進入了賦值的階段。


========================================================================

賦值過程



上面這段代碼是賦值階段的處理邏輯,這段代碼讀起來依然非常簡潔清晰,我們從轉換完的dictionary中取出value,然后把這個value賦值給對象的property。除了這里以及對NSNull.null做了額外處理之外,核心邏輯基本上都在MTLValidateAndSetValue()函數中。一開始我以為這個過程會直接去操作OC的property去實現,所以過程會比較復雜,但是看了代碼之后發現,感謝KVC,整個過程非常簡單卻又很實用。

通過 validateValue:forKey:error: 方法來對賦值的合法性進行校驗,校驗合法之后,直接通過 setValue:forKey: 方法進行賦值即可,通過KVC讓整個流程變得非常簡潔。這里有個需要注意的地方,如果transformer轉換之后的任意一個value與model的property不匹配,則整個model轉換的過程就會失敗,而不僅僅是這個property發生失敗!




從Model到NSDictionary


把Model序列化為JSON數據,同樣只需要下面兩行代碼即可:


入口方式MTLJSONAdapter的JSONDictionaryFromModel:error: 方法,詳細邏輯實現在MTLJSONAdapter的 -initWithModel: 和 -JSONDictionary,initWithMode: 方法要求傳入的model是繼承于MTLModel并且conforms to MTLJSONSerializing 的。詳細的轉換和賦值過程都是在JSONDictionary方法中完成的,這個方法的代碼不算長,直接把代碼貼出來,然后解析其中的邏輯。


首先是第一行代碼,self.model.dictionaryValue,這個是包含了model的property的key以及這個property的value的一個dictionary,我剛開始認為的做法是通過runtime實現,但是看來mantle的實現,發現需要再次感謝KVC,用非常簡單的代碼就實現了很強大的功能,下面的代碼中,self.class.propertyKeys 對它我們已經有了說明,后面會再細說一下,這里比較有意思的方式 dictionaryWithValuesForKeys: 這個方法也是屬于NSKeyValueCoding protocol的

看來一下頭文件中 dictionaryWithValuesForKeys: 的說明,覺得之前對KVC的使用真的不夠。



然后下面的邏輯與從NSDictionary到Model中的做法類似,同樣是創建一個臨時的NSMutableDictionary,然后通過獲取NSValueTransformer進行 reverseTransformedValue: 把property屬性轉換為可以JSON數據支持的類型,以便后面序列化為字符串。這個循環中的代碼,JSONKeyPathForPropertyKey: 與 JSONTransformerForKey: 方法前面已經說過了,校驗property keypath以及獲取這個keypath對應的NSValueTransformer。注意其中,同樣對NSNull.null做了特殊處理,可以看出Mantle中對使用JSON過程中常見的NSNull問題處理的比較干凈的。

將轉換完的數據設置到臨時Dictionary的時候,如果JSONKeyPath為有多個step的路徑時,這個時候的處理比較有意思



明白了從JSON轉換到Model的代碼之后,這部分從Model轉換為JSON的代碼就非常簡單和容易理解了。可以看到由于KVC的存在,我們不用去操作runtime就能很靈活實現很多功能,把復雜的部分交給API交給框架去做。



NSCoding


Mantle中對NSCoding的支持的代碼主要在MTLModel+NSCoding文件中,但是除了與NSCoder API交互的部分之外,比較核心的邏輯與前面看過的是類似的,尤其是Transform部分,這樣就避免了業務層的不必要的工作量。與NSCoding相關的主要涉及到model需要override的兩個方法:-initWithCoder: 和 encodeWithCoder: ,而MTLModel+NSCoding已經默認幫我們實現了這兩個方法。這部分實際上主要的冗余代碼在于secure coding和老版本的兼容代碼,去除這些之后,我們日常使用的功能的話,實際上核心代碼非常少,但是很完整。而且我自己感覺思路上與上面JSON轉換的過程實際上是極其類似的,唯一不同的地方是transform的過程,JSON與Model之間轉換的過程使用了NSValueTransformer,而NSCoding則是依賴于property實現了NSCoding。

encode



encoderWithCoder:的邏輯主要集中在encoderWithCoder:中實現,如上圖中的代碼。首先我們看看第一行代碼,coderRequireSecureCoding是檢驗NSCoder是否是支持NSSecureCoding安全措施。關于NSSecureCoding,我找了半天沒有找到合適的資料,最后在nshipster上面發現了一篇短文進行介紹,這個主要是為了解決substitution attack安全問題的, 鏈接地址。接下來是verifyAllowedClassesByPropertyKey函數,這個secure相關的校驗函數,說實話,我沒有讀懂,看來幾遍,發現exception永遠不可能拋出。

下面一行可以看到,Mantle還提供了一個比較有意思的特性,版本號,這個實際用途還是相當大的。model變化較大時,尤其需要注意這方面的問題,使用這個版本號就可以很方便管理了。

后面的代碼就相對簡單了,用到了我們之前講過的 self.dictionaryValue,然后結合encodingBehaviorsByPropertyKey一起來使用coder的API進行encode的過程。可以看到self.dictionaryValue與上面從model到JSON的轉換過程的第一步是相同的,不同的地方在于transform的過程,這里并沒有使用NSValueTransformer來進行transform,而是依賴于各個property需要實現NSCoding,這里說實話,感覺比較突然,我以為會復用NSValueTransformer的邏輯。

假如我們想要是Mantle不去encode某個property的話,做法也很簡單,mantle這方面也有充分考慮。override encodingBehaviorsByPropertyKey ,然后將要額外處理的property與super的結合即可,如下:


decode


decode的過程與從JSON到model比較類似,不同的是,同樣沒有使用NSValueTransformer來進行value的變換,而是使用NSCoder的decode方法,這個跟上面的encode過程是對應的,不過感覺也是比較合理的,充分利用API提供的便利。賦值的過程與JSON到model的賦值過程是比較類似的,下面這段代碼就是這個過程的主要的邏輯代碼了。 


self.class.propertyKeys上面已經進行了講解,后面的代碼邏輯還是相對簡單的,很容易就能看明白這個過程中做了什么事情。


快乐彩中奖说明