GitHub 地址:YBModelFile
一句代碼自動(dòng)生成 Model 文件,拖入工程既能使用。
前言
當(dāng)一個(gè)網(wǎng)絡(luò)數(shù)據(jù)比較復(fù)雜時(shí),往往需要一些功夫來創(chuàng)建對(duì)應(yīng)的數(shù)據(jù)模型,筆者正是苦于手動(dòng)創(chuàng)建 Model 痛苦筑辨,決定做一個(gè)工具來自動(dòng)創(chuàng)建 Model 文件。
為了降低工具開發(fā)成本幸逆,直接基于 iOS 系統(tǒng)庫來做棍辕。如果是做 Mac 上的工具,會(huì)存在一些技術(shù)問題还绘,比如不便于使用 iOS 程序的動(dòng)態(tài)鏈接庫楚昭,處理 iOS 中的一些類型時(shí)會(huì)比較乏力,并且工具不知道目標(biāo)工程的信息拍顷,在判斷類名重復(fù)抚太、讀取工程信息等情況時(shí)會(huì)很不方便。
本文講解 YBModelFile 的設(shè)計(jì)思路和技術(shù)細(xì)節(jié)昔案。
一尿贫、示例
為了便于理解,先放上一個(gè) json:
{
"name":"jack",
"address":{"city":"北京", "location":"x,x"},
"orderList":[{"id":1, "goods":"手機(jī)"}, {"id":2, "goods":"電腦"}]
}
可以構(gòu)建為如下的一些類:
@interface PersonAddressModel : NSObject
@property (nonatomic, copy) NSString *city;
@property (nonatomic, copy) NSString *location;
@end
@interface PersonOrderListModel : NSObject
@property (nonatomic, copy) NSString *goods;
@property (nonatomic, assign) NSInteger *id;
@end
@interface PersonModel : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) PersonAddressModel *address;
@property (nonatomic, copy) NSArray<PersonOrderListModel *> *orderList;
@end
下面就來講述踏揣,工具如何來自動(dòng)構(gòu)建這些東西(當(dāng)然還包括.m文件的一些實(shí)現(xiàn))庆亡。
二、構(gòu)建多叉樹
工具需要通過 json 數(shù)據(jù)構(gòu)建一些自定義的類捞稿,那么如示例所示身冀,構(gòu)建一個(gè)類必須要知道它的類名和所有屬性钝尸,而一個(gè)類的屬性可能是另一個(gè)類,也可能是一個(gè)包裹類的數(shù)組...
很容易想到分治法搂根,將數(shù)據(jù)局部化處理,于是可以將它們構(gòu)建為一個(gè)樹形結(jié)構(gòu):在這個(gè)樹形結(jié)構(gòu)中铃辖,Custom Class
即是工具需要構(gòu)建的類剩愧,而諸如NSString, NSNumber, NSInteger
等本身就存在無需構(gòu)建,值得注意的是NSArray
類型的屬性里面會(huì)包含一個(gè)類娇斩,這個(gè)類可能是系統(tǒng)類也可能是自定義類仁卷。
可以明確的是,構(gòu)建類時(shí)子節(jié)點(diǎn)需要作為父節(jié)點(diǎn)的屬性犬第,那么Custom Class
是有子節(jié)點(diǎn)的锦积,而系統(tǒng)類型NSString
等是不需要子節(jié)點(diǎn)的。
由此歉嗓,通過遍歷 json 轉(zhuǎn)換的字典就能構(gòu)建出這樣一個(gè)樹形結(jié)構(gòu)丰介。工具中如下所示表示一個(gè)節(jié)點(diǎn):
@interface YBMFNode : NSObject
/** 節(jié)點(diǎn)類型 */
@property (nonatomic, assign) YBMFNodeType type;
/** 子節(jié)點(diǎn) */
@property (nonatomic, strong) NSMutableDictionary<NSString *, YBMFNode *> *children;
...
@end
子節(jié)點(diǎn)通過一個(gè)字典來存儲(chǔ),key
表示對(duì)應(yīng)節(jié)點(diǎn)在 json 中的字段名鉴分,構(gòu)建類時(shí)要作為屬性名哮幢。
三、類名和屬性名的處理
在構(gòu)建樹的過程中志珍,同時(shí)需要處理類名和屬性名橙垢。從上面的示例可以看出,屬性名直接就可以取 json 中的 key伦糯;類名可以通過父節(jié)點(diǎn)的類名加上 key (比如PersonModel + address = PersonAddressModel
) 柜某。
看起來問題是處理了,實(shí)際上還需要做一系列的判斷保證類名和屬性名的合法性敛纲。
類名處理:
- 過濾掉 key 中非法字符喂击。
- 將 key 蛇形命名轉(zhuǎn)換為駝峰命名。
- 父節(jié)點(diǎn)類名 + key + 后綴 = 當(dāng)前類名
- 類名判重:一是工程目錄和系統(tǒng)庫中已經(jīng)存在的類载慈;二是一次程序生命周期將要?jiǎng)?chuàng)建的類惭等。什么叫將要?jiǎng)?chuàng)建的類?其實(shí)就是在一次程序運(yùn)行中办铡,通過工具要?jiǎng)?chuàng)建的新類辞做,由于在本次程序運(yùn)行中這些新類還未加入工程,所以無法通過代碼獲取寡具。筆者通過申請(qǐng)一個(gè)靜態(tài)的 hash 容器把將要?jiǎng)?chuàng)建的新類放進(jìn)去秤茅,就能輕易的判斷重復(fù)。
- 類名重復(fù)處理:當(dāng)知道類名重復(fù)時(shí)童叠,處理方案就很多了框喳,筆者是在類名末尾加上數(shù)字课幕,循環(huán)累加這個(gè)數(shù)字直到不重名為止。
- 為什么不做類復(fù)用:首先個(gè)人對(duì)規(guī)范的理解五垮,數(shù)據(jù)模型類最好不好復(fù)用乍惊;其次從技術(shù)上說,由于自動(dòng)創(chuàng)建的類名每一次都不可預(yù)估放仗,判斷類是否可以復(fù)用只有通過遍歷所有的屬性來比較润绎,而已知的類不好規(guī)定可復(fù)用的范圍,對(duì)于時(shí)間和空間復(fù)雜度來說也是不小的挑戰(zhàn)诞挨,所以筆者認(rèn)為做類復(fù)用沒有什么太大意義莉撇。
- 為什么不過濾保留字:通常情況來說,工具需要使用者傳入一個(gè)主 Model 的名字惶傻,這個(gè)名字通常是大寫字母開頭棍郎,之后的類會(huì)拓展類名,并且還要拼接后綴银室,所以理論上直接規(guī)避了和保留字的沖突 (大寫的保留字比如 YES 和 NO)涂佃。
- TODO : json 嵌套過深,類名過長問題:考慮兩種處理方法粮揉,一是限制類名長度巡李,二是使用與 key 無關(guān)的類名拓展策略,不過顯而易見每種方式都有缺陷扶认。
屬性名處理:
- 過濾掉 key 中非法字符侨拦。
- 如果和保留字重名,全大寫辐宾。
- 如果前綴有特殊字符 (比如
init狱从、new
),把特殊字符部分大寫叠纹。 - 如果前綴包含數(shù)字季研,在前面加上"_"。
- 屬性名判重:情況一一個(gè)類中有兩個(gè)相同的屬性誉察,這種情況可能有人說 json 對(duì)象也不會(huì)有重名字段吧与涡,那是因?yàn)楣P者前面對(duì)屬性名做了處理可能會(huì)出現(xiàn)重名 (比如
order>list
和order?list
處理后都是order_list
);情況二是與父類的屬性重名持偏,所以筆者遍歷了父類的所有實(shí)例變量驼卖,同時(shí)加上了NSObject
協(xié)議的屬性 (若數(shù)據(jù)模型的基類不是NSObject
情況可能會(huì)出現(xiàn)協(xié)議名未包含的情況)。 - 屬性名重復(fù)處理:同類名重復(fù)處理一樣鸿秆。
四酌畜、算法邏輯分離
由于需要實(shí)現(xiàn)動(dòng)態(tài)的類文件分布,當(dāng)兩個(gè)類放在一起和兩個(gè)類分開卿叽,它們所涉及的代碼是不一樣的 (比如兩個(gè)類并在一起只需要一個(gè)文件頭注解桥胞、不需要進(jìn)行另一個(gè)頭文件的導(dǎo)入)恳守。
所以工具將.h
和.m
中的代碼分塊處理,比如文件頂部注解贩虾、導(dǎo)入文件依賴催烘、實(shí)際業(yè)務(wù)代碼等劃分為不同的處理單元《邪眨基于多叉樹的模型颗圣,可以靈活的通過深搜或廣搜等來進(jìn)行動(dòng)態(tài)的代碼插入,實(shí)現(xiàn)靈活控制屁使,為已有功能或者將來要做的功能提供一個(gè)有力的數(shù)據(jù)結(jié)構(gòu)支撐。
同時(shí)奔则,為了拓展性和定制性蛮寂,筆者創(chuàng)建數(shù)個(gè)協(xié)議并提供默認(rèn)實(shí)現(xiàn),使用者可以進(jìn)行靈活的局部算法替換:
/** 名字處理器 */
@property (nonatomic, strong) id<YBMFNameHandler> nameHander;
/** 文件頭部注解處理器 */
@property (nonatomic, strong) id<YBMFFileNoteHandler> fileNoteHander;
/** .h文件代碼處理器 */
@property (nonatomic, strong) id<YBMFFileHHandler> fileHHandler;
/** .m文件代碼處理器 */
@property (nonatomic, strong) id<YBMFFileMHandler> fileMHandler;
/** 節(jié)點(diǎn)作為父節(jié)點(diǎn)的屬性時(shí) Code 格式處理器 */
@property (nonatomic, strong) id<YBMFCodeForParentHandler> codeForParentHandler;
五易茬、類拆分策略
有了上面提到的東西酬蹋,構(gòu)建.h
或.m
文件的代碼字符串就是一個(gè)輕而易舉的事情。
類分離為多個(gè)文件
實(shí)現(xiàn)一個(gè)類對(duì)應(yīng)一組.h/.m
文件策略抽莱,直接通過一個(gè)深度優(yōu)先搜索范抓,在過程中組裝文件代碼并且創(chuàng)建文件,不過處理邏輯是后序的食铐,也就是說樹的層級(jí)越深越先創(chuàng)建匕垫,這樣是為了一個(gè)類依賴的類總是先于這個(gè)類創(chuàng)建,當(dāng)中途出現(xiàn)異常時(shí)虐呻,已經(jīng)創(chuàng)建的文件能有效運(yùn)行象泵。
類集中在一個(gè)文件
很多時(shí)候我們希望一個(gè) json 下的數(shù)據(jù)模型類放到一個(gè)文件中,得益于算法邏輯模塊分離斟叼,可以很輕松的使用深度優(yōu)先搜索來動(dòng)態(tài)構(gòu)建需要的代碼偶惠,組裝為合理的結(jié)構(gòu)。這種情況筆者仍然采用后序處理朗涩,目的是為了讓一個(gè)類依賴的類總是處于它的上方忽孽,這樣在一個(gè)文件中就不需要使用@class AnyClass;
來聲明了。
TODO : 類分離粒度控制
考慮在復(fù)雜場(chǎng)景下谢床,可能需要按需拆分文件兄一,比如 100 個(gè)類需要?jiǎng)澐譃?10 組.h/.m
文件。目前能想到的是三種方式:
- 對(duì)多叉樹按照層級(jí)劃分文件萤悴,在一層的類劃分到一個(gè)文件瘾腰,這種處理方式的缺點(diǎn)是一個(gè)文件的所有類是兄弟節(jié)點(diǎn)沒有什么邏輯關(guān)聯(lián),不便于管理覆履。
- 通過設(shè)置一個(gè)最大層級(jí)來控制蹋盆,比如設(shè)置的層級(jí)是 3费薄,那么第 3 層之后的子節(jié)點(diǎn)類都合并到第 3 層的類文件中。
- 三是在深搜過程中記錄文件中的類數(shù)量栖雾,一個(gè)文件達(dá)到數(shù)量限制就創(chuàng)建新的文件來寫入類楞抡。
后語
該工具是筆者為開發(fā)效率所做的努力,通常情況下能為大家節(jié)約不少時(shí)間析藕,希望能對(duì)大家有所幫助召廷。
在發(fā)現(xiàn)需求、設(shè)計(jì)方案账胧、遇到問題竞慢、解決問題的過程中,筆者似乎感受到了技術(shù)之外的東西治泥。對(duì)于一個(gè)優(yōu)秀的工程師來說筹煮,能隨時(shí)高效全面解決問題的能力比技術(shù)本身重要,希望大家共勉居夹。
??????