最近開(kāi)始做公司的iOS項(xiàng)目重構(gòu)惹悄,現(xiàn)準(zhǔn)備每周做一次匯總春叫,把重構(gòu)過(guò)程中遇到的問(wèn)題和解決方案記錄下來(lái),做一個(gè)記錄和分享泣港。
1.文件目錄結(jié)構(gòu)
我們?cè)赬code中使用“new group”創(chuàng)建一個(gè)新的目錄時(shí)暂殖,對(duì)應(yīng)的文件系統(tǒng)中并不會(huì)相應(yīng)的創(chuàng)建一個(gè)實(shí)體文件夾,只是在Xcode中創(chuàng)建一個(gè)便于管理的虛擬文件夾当纱。這樣就導(dǎo)致添加的所有文件最終都放在文件系統(tǒng)的同一目錄下呛每,這里面可能會(huì)包含.h文件、.m文件坡氯、nib文件晨横,圖片等等一系列的資源文件。當(dāng)項(xiàng)目最終變的比較龐大時(shí)箫柳,眾多的文件將變的極難管理手形。而且,當(dāng)我們使用SVN或Git來(lái)做版本控制時(shí)悯恍,一旦產(chǎn)生工程文件沖突库糠,重新添加文件也很難定位,并且容易遺漏文件涮毫。
所以我們應(yīng)該使用實(shí)體文件夾來(lái)構(gòu)建項(xiàng)目中的Group瞬欧,另外當(dāng)項(xiàng)目比較龐大時(shí)贷屎,Group的劃分也是需要斟酌的一項(xiàng)。好的結(jié)構(gòu)劃分能夠幫助我們快速定位代碼艘虎,有助于理解整個(gè)項(xiàng)目的架構(gòu)和邏輯豫尽。所以我建議基于以下幾條來(lái)區(qū)分目錄結(jié)構(gòu)。
公共部分和各功能模塊的區(qū)分
公共部分和各功能模塊應(yīng)該區(qū)分開(kāi)來(lái)顷帖,公共模塊一般包含公共模型、方法渤滞、視圖贬墩、第三方庫(kù)。我們寫(xiě)的任何可被其他功能模塊調(diào)用的組件都應(yīng)該包含到公共目錄下妄呕。資源類(lèi)型的區(qū)分
所有的圖片陶舞、數(shù)據(jù)庫(kù)文件、bundle绪励、plist等等資源文件都應(yīng)該統(tǒng)一包含到資源目錄下肿孵。MVC的區(qū)分
各功能模塊都可按MVC來(lái)區(qū)分,視圖模型控制器的區(qū)分可以幫助自己和他人更快的定位代碼疏魏。
另外說(shuō)一下添加文件夾到工程中時(shí)停做,“Create groups”和“Create folder references”的區(qū)別。
Create group類(lèi)似于我們“new group”時(shí)創(chuàng)建的組大莫,其中包含的文件會(huì)自動(dòng)添加到Compile Sources中蛉腌,Create folder references只會(huì)引用文件夾,文件夾里面的東西都會(huì)直接拷貝到bundle包只厘,不參與編譯烙丛。
2.注釋
注釋也是重構(gòu)中的一部分,好的注釋能夠極大程度上幫助自己和他人理解代碼羔味。我相信對(duì)注釋負(fù)責(zé)的人河咽,也從側(cè)面證明是一個(gè)靠譜的人。只是這里要注意注釋的格式赋元。
這里推薦一個(gè)喵神寫(xiě)的自動(dòng)注釋工具:VVDocumentor
這是一個(gè)Xcode插件忘蟹,只需要在要寫(xiě)文檔的代碼上面連打三個(gè)斜杠,就能自動(dòng)提取參數(shù)等生成規(guī)范的Javadoc格式文檔注釋?zhuān)螺d編譯一下们陆,然后重啟Xcode就可以使用了寒瓦。
使用這種方式的注釋?zhuān)灰醋ption鍵+鼠標(biāo)左鍵,是可以在調(diào)用時(shí)直接查看注釋內(nèi)容的:
3.手寫(xiě)代碼 or Xib坪仇?
關(guān)于這個(gè)問(wèn)題相信很多同學(xué)都有困惑杂腰,國(guó)內(nèi)iOS界的大神唐巧和喵神對(duì)這個(gè)問(wèn)題也都有自己的見(jiàn)解,大家可以移步到他們的博客看看:
唐巧:http://blog.devtang.com/blog/2015/03/22/ios-dev-controversy-2/
喵神:http://onevcat.com/2013/12/code-vs-xib-vs-storyboard/
借用唐巧的幾句話:
- 對(duì)于復(fù)雜的椅文、動(dòng)態(tài)生成的界面喂很,建議使用手工編寫(xiě)界面惜颇。
- 對(duì)于需要統(tǒng)一風(fēng)格的按鈕或UI控件,建議使用手工用代碼來(lái)構(gòu)造少辣。方便之后的修改和復(fù)用凌摄。
- 對(duì)于需要有繼承或組合關(guān)系的 UIView 類(lèi)或 UIViewController 類(lèi),建議用代碼手工編寫(xiě)界面漓帅。
- 對(duì)于那些簡(jiǎn)單的锨亏、靜態(tài)的、非核心功能界面忙干,可以考慮使用 xib 或 storyboard 來(lái)完成器予。
4.多用類(lèi)型常量,少用#define
一個(gè)龐大的項(xiàng)目中捐迫,常常使用了大量的宏定義乾翔。宏定義的初衷之一是提高了程序的可讀性,同時(shí)也方便進(jìn)行修改施戴》磁ǎ可是過(guò)度的宏定義往往違背了它的初衷。
例如
#define ANIMATION_DURATION 0.3
我們并不能很直觀的理解它其中的時(shí)間含義赞哗,而
static const NSTimeInterval kAnimationDuration = 0.3;
就很好的描述了常量的含義雷则。
此外,為什么要加一個(gè)static和const來(lái)同時(shí)命名肪笋?因?yàn)閟tatic意味著該變量?jī)H在定義此變量的編譯單元中可見(jiàn)巧婶。編譯器每收到一個(gè)編譯單元,就會(huì)相應(yīng)的輸出一份目標(biāo)文件(object file即.o文件)涂乌。假如我們不聲明static艺栈,編譯器就會(huì)為它創(chuàng)建一個(gè)“外部符號(hào)”。此時(shí)如果另一個(gè)編譯單元也聲明了一個(gè)同名變量湾盒,那么編譯器就會(huì)拋出一條錯(cuò)誤消息湿右。事實(shí)上,如果同時(shí)用static和const命名罚勾,編譯器根本不會(huì)創(chuàng)建符號(hào)毅人,而是會(huì)像#define預(yù)處理指令一樣,把所有遇到的變量都替換為常值尖殃。不過(guò)還是有一點(diǎn)區(qū)別的丈莺,用這種方式定義的常量是帶有類(lèi)型信息的。
不管是#define還是static const都不應(yīng)該在頭文件里聲明送丰,因?yàn)槌A棵Q很有可能互相沖突缔俄,如果一定要這么做的話,要加上前綴,表明它輸入哪個(gè)類(lèi)俐载。
再延伸的說(shuō)一下extern:
extern常用于NSNotification中等蟹略,供外部使用,extern常量放在“全局符號(hào)表”中遏佣,以便可以在定義該常量的編譯單元之外使用挖炬。這也是不用使用#import引入其所在頭文件的原因,需注意状婶,此類(lèi)常量必須要定義意敛,而且只能定義一次。
5.Reveal
對(duì)于龐大的項(xiàng)目膛虫,純代碼構(gòu)建的UI空闲,使用Reveal來(lái)調(diào)試界面的用處還是很大的∽叩校可以幫助我們更直觀的了解代碼,快速定位UI細(xì)節(jié)逗噩。顏色掉丽,位置,大小异雁,間距捶障,和View之間的相對(duì)關(guān)系都可以一目了然。Reveal甚至比Xcode自帶的Interface Builder做的還要好纲刀。對(duì)于越獄的設(shè)備项炼,Reveal還可以用來(lái)分析其他應(yīng)用程序的UI,實(shí)在是不可多得的利器示绊。
Reveal加載的三個(gè)方法
加載方法1
下載Reveal之后打開(kāi)锭部,在菜單中的Help中可以找到集成到Xcode項(xiàng)目的方法,這里不再贅述面褐。
加載方法2
此方法可以在不改變工程設(shè)置的前提下加載Reveal
打開(kāi)終端拌禾,輸入
vim ~/.lldbinit
LLDB每次啟動(dòng)的時(shí)候都會(huì)加載這個(gè)文件。輸入:
command alias reveal_load_sim expr (void*)dlopen("/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib", 0x2);
command alias reveal_load_dev expr (void*)dlopen([(NSString*)[(NSBundle*)[NSBundle mainBundle] pathForResource:@"libReveal" ofType:@"dylib"] cStringUsingEncoding:0x4], 0x2);
command alias reveal_start expr (void)[(NSNotificationCenter*)[NSNotificationCenter defaultCenter] postNotificationName:@"IBARevealRequestStart" object:nil];
command alias reveal_stop expr (void)[(NSNotificationCenter*)[NSNotificationCenter defaultCenter] postNotificationName:@"IBARevealRequestStop" object:nil];
然后輸入control+c展哭,w q enter退出終端湃窍。
上述文件創(chuàng)建了4個(gè)命令:
reveal_load_sim,reveal_load_dev, reveal_start 和 reveal_stop
reveal_load_sim 這個(gè)只在iOS模擬器上有效。它從Reveal的應(yīng)用程序bundle中找到并加載libReveal.dylib(請(qǐng)確保你把Reveal安裝到了系統(tǒng)的Application文件夾匪傍,如果你換地方了您市,你修改上述的文件)。
reveal_load_dev 這個(gè)命令在iOS設(shè)備和模擬器上都有效役衡。不過(guò)茵休,它需要你在Build Phase中的的Copy Bundle Resources中加上libReveal.dylib,請(qǐng)確保沒(méi)有放到Link Binary With Libraries這個(gè)地方。
reveal_start 這個(gè)命令發(fā)出一個(gè)通知啟動(dòng)Reveal Server泽篮。
reveal_stop 這個(gè)命令發(fā)出一個(gè)通知停止Reveal Server盗尸。
如果在模擬器下調(diào)試,只需要在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
方法中加入一個(gè)斷點(diǎn)并且如圖所示編輯:
編譯運(yùn)行即可帽撑。
在真機(jī)下泼各,需在工程中導(dǎo)入Reveal的動(dòng)態(tài)庫(kù),打開(kāi)Reveal亏拉,點(diǎn)擊Help-Show Library in Finder扣蜻,將libReveal.dylib
文件拖動(dòng)到目標(biāo)Xcode工程中,Xcode默認(rèn)情況下錯(cuò)誤地將libReveal.dylib設(shè)置到了”Link Binary With Libraries”下及塘,我們需要進(jìn)行一下調(diào)整莽使,將其中”Link Binary With Libraries”中刪除,然后將其添加到“Copy Bundle Resources”下面笙僚。
之后用Reveal連接真機(jī)的方式和連接模擬器的方式類(lèi)似芳肌,我們只需要把模擬器調(diào)試下的斷點(diǎn)Action的內(nèi)容從reveal_load_sim改成reveal_load_dev即可。
加載方法3
對(duì)于越獄的機(jī)器肋层,可以用Reveal來(lái)”調(diào)試“其它應(yīng)用界面亿笤,什么時(shí)候會(huì)有這種奇怪的需求呢?——當(dāng)我們想學(xué)習(xí)別人是如何實(shí)現(xiàn)界面效果的時(shí)候栋猖。iOS設(shè)備的目錄/Library/MobileSubstrate/DynamicLibraries
下存放著所有在系統(tǒng)啟動(dòng)時(shí)就需要加載的動(dòng)態(tài)鏈接庫(kù)净薛,所以我們只需要將Reveal的動(dòng)態(tài)鏈接庫(kù)上傳到該目錄即可。
對(duì)于越獄的設(shè)備蒲拉,我們可以在安裝OpenSSH之后肃拜,用scp來(lái)上傳該文件。具體步驟如下:
1.將libReveal.dylib 上傳到/Library/MobileSubstrate/DynamicLibraries
2.如果libReveal.dylib沒(méi)有執(zhí)行權(quán)限雌团,用chmod +x libReveal.dylib
命令燃领,給其增加執(zhí)行權(quán)限
3.執(zhí)行killall SpringBoard
重啟桌面
另外需要注意的是,使用Reveal真機(jī)測(cè)試時(shí)手機(jī)和電腦應(yīng)處于同一網(wǎng)絡(luò)下锦援。