iOS 效率工具:自動(dòng)生成 Model 文件

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>listorder?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ù)本身重要,希望大家共勉居夹。

??????

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末败潦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子准脂,更是在濱河造成了極大的恐慌劫扒,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狸膏,死亡現(xiàn)場(chǎng)離奇詭異沟饥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)环戈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門闷板,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人院塞,你說我怎么就攤上這事遮晚。” “怎么了拦止?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵县遣,是天一觀的道長。 經(jīng)常有香客問我汹族,道長萧求,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任顶瞒,我火速辦了婚禮夸政,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘榴徐。我一直安慰自己守问,他們只是感情好匀归,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著耗帕,像睡著了一般穆端。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仿便,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天体啰,我揣著相機(jī)與錄音,去河邊找鬼嗽仪。 笑死荒勇,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的闻坚。 我是一名探鬼主播枕屉,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼鲤氢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起西潘,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤卷玉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后喷市,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體相种,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年品姓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寝并。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腹备,死狀恐怖衬潦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情植酥,我是刑警寧澤镀岛,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站友驮,受9級(jí)特大地震影響漂羊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卸留,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一走越、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧耻瑟,春花似錦旨指、人聲如沸赏酥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽今缚。三九已至,卻和暖如春低淡,著一層夾襖步出監(jiān)牢的瞬間姓言,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工蔗蹋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留何荚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓猪杭,卻偏偏與公主長得像餐塘,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子皂吮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345