為什麼唱吧iOS 6.0選擇了Mantle,ios6.0mantle
最近唱吧iOS的6.0版本已經成功上線了。18人月的投入,2500個commit,幾十萬行的代碼修改。唱吧iOS已經從內至外煥然一新,感謝一起並肩作戰的小夥伴們。
6.0一個很重大的修改就是基於Mantle重建(建立)了Model層。這裡不對Mantle作更多介紹,只分享一下使用Mantle的決策及執行過程。
我們遇到的問題
唱吧是一款上線2年多的App,產品形態的演化和迭代非常快。因此不可避免的遺留了各種問題:
- Model層不健全,沒有統一的結構,不同工程師做法差異很大;多數是啞類型,且沒有統一的序列化機制
- 商務邏輯冗餘、分散、不一致
- 模組劃分隨意,依賴關係混亂,維護困難
- NSDictionary作為承載業務的資料類型在各處出現(sqlite, Model object, API, Notification, web, OpenURL etc.),參數和值的正確性完全沒有編譯器檢查,字串很容易寫錯,風險延後至運行時,易產生低級bug
- 基本沒有文檔和注釋(結合上一點,不掛debugger很難讀懂代碼)
- 幾百個API,業務複雜,變動快,重構難;同一個API請求可能有重複和不一致
- API的一些參數和返回值,同一個參數/返回值可能存在類型差異;由於API需要向前相容,修改API有成本
除此之外,還有其他工程上的約束:
- 不能影響現有的API,所有的事情只限於iOS端的修改
- 代碼即文檔,因為沒有精力維護文檔
- 對不同Model的持久化方式作遷移
- 避免寫大段枯燥的Model的序列化/還原序列化代碼
- 沒有時間造出足夠成熟、健壯可重用的組件及撰寫文檔
上述的問題都是長期存在且需要解決的,否則嚴重影響開發效率及代碼品質。11年的時候我還在做社交遊戲的時候,設計並實現了一套簡單的基於Objective-C Runtime的數值表Model結構及轉換工具(Model<=>csv)供數值策劃使用。但想寫出一套成熟的方案還是有一些距離,而且也沒有資源和時間作維護、測試和文檔。
順著這個思路找到了JSONModel和Mantle,前者剛剛1.0,後者在Github for Mac中廣泛使用且社區更成熟(甚至Slack上有channel),所以成為了更好的選擇。
事實也證明這個選擇是對的,6.0上線後,crash率比之前的版本有顯示的降低,並且Mantle相關的crash佔總crash的比率不到3%,大可以直接用在大型的產品上。
除了成熟穩定,Mantle基本解決了我們遇到了的所有問題。下面具體介紹一些通用性Mantle使用經驗,基本的使用方法請直接移步Mantle的README。
Property名稱轉換
由於API使用的開發語言與iOS所使用的Objective-C是截然不同的,所以可能將一些保留關鍵字作為property的名稱(如id),或者不小心override掉基類的屬性(如description)。還有可能API中使用了一個很糟糕的名稱,或者使用了不符合Objective-C命名規範的名稱,這些我們都需要作轉換。
只需要實現MTLJSONSerializing protocol並在+JSONKeyPathsByPropertyKey方法中定義好新舊名稱的映射關係即可,Mantle會在序列化及還原序列化時對屬性名稱進行自動的轉換。
+ (NSDictionary *)JSONKeyPathsByPropertyKey {return @{@"identifier": @"id",@"displayDiscription": @"description",@"thisIsANewShit": @"newShit",@"creativeProduct": @"copyToChina",@"betterPropertyName": @"m_wired_propertyName"}}
好了很多吧?沒錯,只需要定義一次名稱的映射關係就可以了,Mantle負責model與JSON之間的雙向轉換。不需要將這種邏輯寫得到處都是,並且還得維護它的一致性。
Property的類型映射iOS中處理URL使用的是NSURL類型,但JSON只支援基本的字串,Mantle可以自動幫你轉換成NSURL。
+ (NSValueTransformer *)URLJSONTransformer {return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];}NSValueTransformer負責在不同類型間進行雙向轉換,請讀者研究一下Mantle的實現方式。在此前提下,留給讀者一個問題(其實這是一個真實的故事,類似的故事還有很多,詳見iOS應用開發之十大坑隊友):
假設我們有一個entity,名字且叫KTVConcreteEntity吧,它有一個屬性名稱字叫entityID,類型是NSInteger。問題來了,entityID可能在另外一個API的response中是字串類型,在不直接修改Mantle的源碼的前提下怎麼搞?歡迎在下方留言討論。
空標量異常有的時候API的response會有空值,比如
copyToChina可能不是每次都有的,JSON是這樣兒的:
{"copyToChina": null}
Mantle在這種情況會將newShit轉換為nil,但如果是標量如NSInteger怎麼辦?KVC會直接raise NSInvalidArgumentException。
Mantle是基於KVC給property賦值的,KVC提供了- (void)setNilValueForKey:(NSString *)key方法,讓我們為nil指定一個合理的替代值,我們來看一下此方法的解釋:
Invoked by setValue:forKey: when it’s given a nil value for a scalar value (such as an int or float).Subclasses can override this method to handle the request in some other way, such as by substituting 0 or a sentinel value for nil and invoking setValue:forKey: again or setting the variable directly. The default implementation raises an NSInvalidArgumentException.
對於標量來講,多數情況下合理的值即為0,我們來看下代碼:
@interface MTLModel (KTVNullableScalar)@end@implementation MTLModel (KTVNullableScalar)- (void)setNilValueForKey:(NSString *)key {[self setValue:@0 forKey:key]; // For NSInteger/CGFloat/BOOL}@end
問題完美解決,再也不需要到處寫無聊的if/else了。
其它重要特性
Mantle為我們帶來的方便不勝枚舉:
- 實現了
NSCopying protocol,子類可以直接copy是多麼爽的事情
- 實現了
NSCoding protocol,跟NSUserDefaults說拜拜
- 提供了
-isEqual:和-hash的預設實現,model作NSDictionary的key方便了許多
- 簡單且把一件事情做好,不摻雜網路相關的操作
如此強大優雅的設計,讓我不得不向Github的工程師們致敬!
寫在後面篇幅所限,只介紹了幾個典型的問題,歡迎大家討論。但如果你的App的代碼規模只有幾萬行,或者API只有十幾個,或者沒有遇到我們這些遺留問題,我建議還是不要引入了,殺雞用指甲刀就夠了,殺不動多磨磨找准要害。Anyway,Mantle的實現和思路是值得每位iOS工程師學習和借鑒的。
原文連結
選擇你的該裝置的iOS 6韌體(注意:不是iOS 601韌體),問越獄時這一步該怎做?
是A4裝置嗎?不完美越獄,C:\Users\管理員帳號名稱\AppData\Roaming\Apple Computer\iTunes\iPhone Software Updates 在這邊。。。。。提醒下,iPhone Software Updates這個檔案夾是隱藏的
唱吧錄的歌 分享到朋友圈, 但是總失敗 本人用的蘋果4S IOS601 幫忙
唱吧設定頭像了嗎?如果設定了頭像就可以了,否則是分享不成功的~