iOS 效率工具:自動生成 Model 文件

GitHub 地址:YBModelFile

一句代碼自動生成 Model 文件,拖入工程既能使用。

前言

當(dāng)一個網(wǎng)絡(luò)數(shù)據(jù)比較復(fù)雜時,往往需要一些功夫來創(chuàng)建對應(yīng)的數(shù)據(jù)模型,筆者正是苦于手動創(chuàng)建 Model 痛苦,決定做一個工具來自動創(chuàng)建 Model 文件。

為了降低工具開發(fā)成本,直接基于 iOS 系統(tǒng)庫來做。如果是做 Mac 上的工具,會存在一些技術(shù)問題,比如不便于使用 iOS 程序的動態(tài)鏈接庫,處理 iOS 中的一些類型時會比較乏力,并且工具不知道目標(biāo)工程的信息,在判斷類名重復(fù)、讀取工程信息等情況時會很不方便。

本文講解 YBModelFile 的設(shè)計思路和技術(shù)細(xì)節(jié)。

一、示例

為了便于理解,先放上一個 json:

{
  "name":"jack", 
  "address":{"city":"北京", "location":"x,x"},
  "orderList":[{"id":1, "goods":"手機"}, {"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

下面就來講述,工具如何來自動構(gòu)建這些東西(當(dāng)然還包括.m文件的一些實現(xiàn))。

二、構(gòu)建多叉樹

工具需要通過 json 數(shù)據(jù)構(gòu)建一些自定義的類,那么如示例所示,構(gòu)建一個類必須要知道它的類名和所有屬性,而一個類的屬性可能是另一個類,也可能是一個包裹類的數(shù)組...

很容易想到分治法,將數(shù)據(jù)局部化處理,于是可以將它們構(gòu)建為一個樹形結(jié)構(gòu):

在這個樹形結(jié)構(gòu)中,Custom Class即是工具需要構(gòu)建的類,而諸如NSString, NSNumber, NSInteger等本身就存在無需構(gòu)建,值得注意的是NSArray類型的屬性里面會包含一個類,這個類可能是系統(tǒng)類也可能是自定義類。

可以明確的是,構(gòu)建類時子節(jié)點需要作為父節(jié)點的屬性,那么Custom Class是有子節(jié)點的,而系統(tǒng)類型NSString等是不需要子節(jié)點的。

由此,通過遍歷 json 轉(zhuǎn)換的字典就能構(gòu)建出這樣一個樹形結(jié)構(gòu)。工具中如下所示表示一個節(jié)點:

@interface YBMFNode : NSObject
/** 節(jié)點類型 */
@property (nonatomic, assign) YBMFNodeType type;
/** 子節(jié)點 */
@property (nonatomic, strong) NSMutableDictionary<NSString *, YBMFNode *> *children;
...
@end

子節(jié)點通過一個字典來存儲,key表示對應(yīng)節(jié)點在 json 中的字段名,構(gòu)建類時要作為屬性名。

三、類名和屬性名的處理

在構(gòu)建樹的過程中,同時需要處理類名和屬性名。從上面的示例可以看出,屬性名直接就可以取 json 中的 key;類名可以通過父節(jié)點的類名加上 key (比如PersonModel + address = PersonAddressModel) 。

看起來問題是處理了,實際上還需要做一系列的判斷保證類名和屬性名的合法性。

類名處理:
  • 過濾掉 key 中非法字符。
  • 將 key 蛇形命名轉(zhuǎn)換為駝峰命名。
  • 父節(jié)點類名 + key + 后綴 = 當(dāng)前類名
  • 類名判重:一是工程目錄和系統(tǒng)庫中已經(jīng)存在的類;二是一次程序生命周期將要創(chuàng)建的類。什么叫將要創(chuàng)建的類?其實就是在一次程序運行中,通過工具要創(chuàng)建的新類,由于在本次程序運行中這些新類還未加入工程,所以無法通過代碼獲取。筆者通過申請一個靜態(tài)的 hash 容器把將要創(chuàng)建的新類放進去,就能輕易的判斷重復(fù)。
  • 類名重復(fù)處理:當(dāng)知道類名重復(fù)時,處理方案就很多了,筆者是在類名末尾加上數(shù)字,循環(huán)累加這個數(shù)字直到不重名為止。
  • 為什么不做類復(fù)用:首先個人對規(guī)范的理解,數(shù)據(jù)模型類最好不好復(fù)用;其次從技術(shù)上說,由于自動創(chuàng)建的類名每一次都不可預(yù)估,判斷類是否可以復(fù)用只有通過遍歷所有的屬性來比較,而已知的類不好規(guī)定可復(fù)用的范圍,對于時間和空間復(fù)雜度來說也是不小的挑戰(zhàn),所以筆者認(rèn)為做類復(fù)用沒有什么太大意義。
  • 為什么不過濾保留字:通常情況來說,工具需要使用者傳入一個主 Model 的名字,這個名字通常是大寫字母開頭,之后的類會拓展類名,并且還要拼接后綴,所以理論上直接規(guī)避了和保留字的沖突 (大寫的保留字比如 YES 和 NO)。
  • TODO : json 嵌套過深,類名過長問題:考慮兩種處理方法,一是限制類名長度,二是使用與 key 無關(guān)的類名拓展策略,不過顯而易見每種方式都有缺陷。
屬性名處理:
  • 過濾掉 key 中非法字符。
  • 如果和保留字重名,全大寫。
  • 如果前綴有特殊字符 (比如init、new),把特殊字符部分大寫。
  • 如果前綴包含數(shù)字,在前面加上"_"。
  • 屬性名判重:情況一一個類中有兩個相同的屬性,這種情況可能有人說 json 對象也不會有重名字段吧,那是因為筆者前面對屬性名做了處理可能會出現(xiàn)重名 (比如order>listorder?list處理后都是order_list);情況二是與父類的屬性重名,所以筆者遍歷了父類的所有實例變量,同時加上了NSObject協(xié)議的屬性 (若數(shù)據(jù)模型的基類不是NSObject情況可能會出現(xiàn)協(xié)議名未包含的情況)。
  • 屬性名重復(fù)處理:同類名重復(fù)處理一樣。

四、算法邏輯分離

由于需要實現(xiàn)動態(tài)的類文件分布,當(dāng)兩個類放在一起和兩個類分開,它們所涉及的代碼是不一樣的 (比如兩個類并在一起只需要一個文件頭注解、不需要進行另一個頭文件的導(dǎo)入)。

所以工具將.h.m中的代碼分塊處理,比如文件頂部注解、導(dǎo)入文件依賴、實際業(yè)務(wù)代碼等劃分為不同的處理單元。基于多叉樹的模型,可以靈活的通過深搜或廣搜等來進行動態(tài)的代碼插入,實現(xiàn)靈活控制,為已有功能或者將來要做的功能提供一個有力的數(shù)據(jù)結(jié)構(gòu)支撐。

同時,為了拓展性和定制性,筆者創(chuàng)建數(shù)個協(xié)議并提供默認(rèn)實現(xià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é)點作為父節(jié)點的屬性時 Code 格式處理器 */
@property (nonatomic, strong) id<YBMFCodeForParentHandler> codeForParentHandler;

五、類拆分策略

有了上面提到的東西,構(gòu)建.h.m文件的代碼字符串就是一個輕而易舉的事情。

類分離為多個文件

實現(xiàn)一個類對應(yīng)一組.h/.m文件策略,直接通過一個深度優(yōu)先搜索,在過程中組裝文件代碼并且創(chuàng)建文件,不過處理邏輯是后序的,也就是說樹的層級越深越先創(chuàng)建,這樣是為了一個類依賴的類總是先于這個類創(chuàng)建,當(dāng)中途出現(xiàn)異常時,已經(jīng)創(chuàng)建的文件能有效運行。

類集中在一個文件

很多時候我們希望一個 json 下的數(shù)據(jù)模型類放到一個文件中,得益于算法邏輯模塊分離,可以很輕松的使用深度優(yōu)先搜索來動態(tài)構(gòu)建需要的代碼,組裝為合理的結(jié)構(gòu)。這種情況筆者仍然采用后序處理,目的是為了讓一個類依賴的類總是處于它的上方,這樣在一個文件中就不需要使用@class AnyClass;來聲明了。

TODO : 類分離粒度控制

考慮在復(fù)雜場景下,可能需要按需拆分文件,比如 100 個類需要劃分為 10 組.h/.m文件。目前能想到的是三種方式:

  • 對多叉樹按照層級劃分文件,在一層的類劃分到一個文件,這種處理方式的缺點是一個文件的所有類是兄弟節(jié)點沒有什么邏輯關(guān)聯(lián),不便于管理。
  • 通過設(shè)置一個最大層級來控制,比如設(shè)置的層級是 3,那么第 3 層之后的子節(jié)點類都合并到第 3 層的類文件中。
  • 三是在深搜過程中記錄文件中的類數(shù)量,一個文件達(dá)到數(shù)量限制就創(chuàng)建新的文件來寫入類。

后語

該工具是筆者為開發(fā)效率所做的努力,通常情況下能為大家節(jié)約不少時間,希望能對大家有所幫助。

在發(fā)現(xiàn)需求、設(shè)計方案、遇到問題、解決問題的過程中,筆者似乎感受到了技術(shù)之外的東西。對于一個優(yōu)秀的工程師來說,能隨時高效全面解決問題的能力比技術(shù)本身重要,希望大家共勉。

??????

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。