構(gòu)建測試平臺
獲取隱藏的文件信息改衩,在終端輸入如下命令:
defaults write com.apple.Finder AppleShowAllFiles TRUE
defaults write com.apple.Finder Showpathbar -bool true
defaults write com.apple.Finder _FXShowPosixPathInTitle -bool true
defaults write NSGlobalDomain AppleShowAllExtensions -bool true
chflags nohidden ~/Library/
通過上述設(shè)置,F(xiàn)inder中的所有文件都變?yōu)榭梢娖缢拢ㄒ浴?”開頭的隱藏文件钞脂。此外還會顯示詳細的文件路徑和擴展信息揣云,最為重要的是能看到與用戶相關(guān)的Library目錄,iOS模擬器和Xcode的數(shù)據(jù)都存儲在此目錄下冰啃。
chflags命令將那些蘋果認為會迷惑用戶的隱藏文件全部顯示了出來邓夕,例如/tmp或/usr。這樣更方便查看模擬器目錄阎毅。
把$SIMPATH添加到Finder的側(cè)邊欄焚刚,便于訪問。默認設(shè)置下扇调,F(xiàn)inder沒有$SIMPATH矿咕。想要實現(xiàn)上述設(shè)置,在終端輸入以下命令:
cd ~/Library/Developer
open .
Developer文件夾下有三個目錄狼钮。其中CoreSimulator碳柱、Xcode是我們常用的。將Developer拖拽到Finder側(cè)邊欄中燃领。
Xcode構(gòu)建設(shè)置
首先將警告視為錯誤士聪。大部分警告都是由clang產(chǎn)生的锦援,它屬于Xcode編譯器的前端(frontend)猛蔽,值得認真對待。這樣做可以減少代碼復雜度灵寺、確保語法正確曼库,還可以捕獲那些難以發(fā)現(xiàn)的錯誤,比如無符號問題或是格式字符串漏洞略板,看如下代碼:
- (void)validate:(NSArray *)someTribbles withValue:(NSInteger)desired{
if(desired > [someTribbles count]) {
}
}
NSArray的count方法返回一個無符號整形(NSUInteger)毁枯,if判斷語句會提示 “'NSInteger' (aka 'long') and 'NSUInteger' (aka 'unsigned long'”警告。啟用將警告視為錯誤的選項叮称,從而使clang標記該類型為bug种玛,運行工程將會失敗。
這里的意思是說啟用項目構(gòu)建配置中的更多警告模式瓤檐,并將警告提升到bug的高度赂韵,強迫自己盡可能在開發(fā)的早期階段解決掉一些隱患,培養(yǎng)良好的編碼習慣挠蛉。
在target->build setting->Warning Policies下祭示,把Treat Warnings as Errors 設(shè)置為YES。
在Custom Compiler Flags下谴古,設(shè)置Other Warning Flags质涛,這里有-Wall稠歉、-Wextra、-Weverything三個值可選汇陆,含義如下:
-Wall 并不是所有警告怒炸。這一個警告組開啟的是編譯器開發(fā)者對于“你所寫的代碼中有問題”這一命題有著很高的自信的那些警告。要是在這一組設(shè)定下你的代碼出現(xiàn)了警告瞬测,那基本上就是你的代碼真的存在嚴重問題了横媚。但是同時,并不是說打開Wall就萬事大吉了月趟,因為Wall所針對的僅僅只是經(jīng)典代碼庫中的為數(shù)不多的問題灯蝴,因此有一些致命的警告并不能被其捕捉到。但是不論如何孝宗,因為Wall的警告提供的都是可信度和優(yōu)先級很高的警告穷躁,所以為所有項目(至少是所有新項目)打開這組警告,應(yīng)該成為一種良好的習慣因妇。
-Wextra 如其所名问潭,-Wextra組提供“額外的”警告。這個組和-Wall組幾乎一樣有用婚被,但是有些情況下對于代碼相對過于嚴苛狡忙。一個很常見的例子是,-Wextra中包含了-Wsign-compare址芯, 這個警告標識會開啟比較時候?qū)igned和unsigned的類型檢查灾茁,當比較符兩邊一邊是signed一邊是unsigned時,產(chǎn)生警告谷炸。其實很多 代碼并沒有特別在意這樣的比較北专,而且絕大多數(shù)時候,比較signed和unsigned也是沒有太大問題的(當然不排除會有致命錯誤出現(xiàn)的情況)旬陡。需要注意拓颓,-Wextra和-Wall是相互獨立的兩個警告組,雖然里面打開的警告標識有個別是重復的描孟,但是兩組并沒有包含的關(guān)系驶睦。想要同時使用的話必須都加上
-Weverything 這個是真正的所有警告。但是一般開發(fā)者不會選擇使用這個標識匿醒,因為它包含了那些還正在開發(fā)中的可能尚存bug的警告提示场航。這個標識一般是編譯器開發(fā)者用來調(diào)試時使用的,如果你想在自己的項目里開啟的話青抛,警告一定會爆棚導致你想開始撞墻..
這里推薦-Wall和-Wextra旗闽,或者-Wextra,設(shè)置后運行程序警告數(shù)成倍的增長,看看其中哪些是真正的bug适室。
Clang和靜態(tài)分析(Static Analyzer)
靜態(tài)分析一般指的是使用工具來分析代碼并找出安全漏洞嫡意。這可能涉及識別一系列危險的API,或分析通過應(yīng)用程序的數(shù)據(jù)流捣辆,來標識潛在不安全的程序輸入蔬螟。
Xcode中提供了一個UI界面工具。用戶可以使用該工具很直觀的完成邏輯追蹤汽畴、代碼漏洞以及通用API誤用等分析操作旧巾。但有一些重要特性,Xcode默認是禁止忍些。其中值得關(guān)注的特性有:對C語言庫函數(shù)等經(jīng)典威脅的檢查鲁猩,比如針對strcpy和strcat的檢查就是默認關(guān)閉的。你可以在項目或Target設(shè)置中開啟這些特性:
在Xcode8中罢坝,靜態(tài)分析能夠檢測出三種新的錯誤, 它們分別是Localizability廓握、Instance Cleanup、Nullability嘁酿。
- Localizability 其實說的是靜態(tài)分析能夠檢測出本地化信息缺失的問題隙券,目前能夠檢測出來兩種類型的錯誤块差,一種是沒有使用 NSLocalizeString這樣的API渐夸,而直接給控件設(shè)置Sting的情況,一種是使用了相應(yīng)的API虫溜,但在comment信息里面賦值為nil游桩。默認是不開啟的牲迫,在BuildSting中作如下設(shè)置:
Instance Cleanup 在MRC的代碼中,尤其在dealloc中众弓,我們不應(yīng)該對assign類型的屬性進行release操作恩溅,應(yīng)該對retain或者 copy類型的屬性進行release操作隔箍,如果不這樣操作的話谓娃,會引發(fā)一些不必要的麻煩
Nullability 在2015年的WWDC大會上,Objective-C引入的一個新特性就叫做Nullability蜒滩,用于表明一個東西到底可以為nil還是不可以為nil滨达,這和Swift里的option類型很相似。
Sanitizer和動態(tài)分析
- Sanitizer是一個用于優(yōu)化的工具俯艰。
在edit scheme->Diagnostics->Runtime Sanitizer中勾選相應(yīng)的Sanitizer選項捡遍。
勾選了相應(yīng)的選項并不代表你就能使用 Sanitizer 來Check代碼了, 你還必須重新run一下代碼,為什么呢?
這就必須說說整個代碼 build flow 了. 如下圖所示, 通過勾選了對應(yīng)的選項, Xcode 會向 clang 傳遞一個特定的參數(shù), 然后生成一個獨特的 binary, 然后這個 binary 會和 Thread Sanitizer 或者 Address Sanitizer 的 dylib 鏈接在一起. 這樣 Sanitizer 就實現(xiàn)了它想要達到的功能.
Address Sanitizer(ASan)是一個類似Valgrind的動態(tài)分析工具竹握,ASan可以檢測出堆画株、棧溢出和釋放后又被使用的bug,還能找到關(guān)鍵的安全漏洞。ASan會對性能產(chǎn)生一些影響(程序執(zhí)行速度會慢一些)谓传,因此不要在發(fā)行版本中啟用這個選項蜈项,但是在測試、質(zhì)量保證檢測或是缺陷測試階段续挟,使用這一特性帶來便利吧紧卒。ASan能檢查以下類型的錯誤:
- Use after free
- Heap bu?er over?ow
- Stack bu?er over?ow
- Global variable over?ow
- Over?ows in C++ containers
- Use after return
- (void)viewDidLoad {
[super viewDidLoad];
char *buffer = malloc(10);
buffer[10] = 'A';
free(buffer);
}
運行上面的代碼,在開啟ASan時诗祸,程序會崩潰在“buffer[10] = 'A';”這一行跑芳,并顯示相應(yīng)的崩潰類型。在不開啟ASan時直颅,不添加任何斷點時博个,程序會崩潰在main函數(shù)里;添加任何斷點后功偿,程序不會崩潰坡倔。
問題很明顯,這是一個數(shù)組越界脖含,開啟ASan后會直接定位到問題發(fā)生的地方罪塔,不開啟的話,阿彌陀佛了养葵。
- Thread Sanitizer (TSan)是Xcode8的新特性征堪,檢測線程方面的問題。
讓我們想想自己在調(diào)試線程方面的 bug 時, 有哪些令人記憶深刻的東西:
- 線程方面的 bug 對時間很敏感, 這就導致很多線程的 bug 極難復現(xiàn), 復現(xiàn)都成問題, 還怎么改 bug
- 由于線程的抽象概念導致在 debug 時候也比一般的 debug 更費勁兒, 這時候總覺自己腦子不夠使
- 有時候, 由于線程引起的 crash 或者 error ,讓我們根本意識不到這其實是線程出了問題
在Address Sanitizer下面勾選Thread Sanitizer关拒。至于 Thread Sanitizer下面的那個Pause on Issues的選項就是說佃蚜,如果你想一個一個看runtime issue就勾選它,如果你不想這樣着绊,就不要勾選它谐算, 具體是個神馬感覺,你自己試試嘍归露。
如果你喜歡使用 Comman-Line ,那么請記住下面的代碼
//Compile and Link with TSan
$ clang -fsanitize=thread source.c -o executable
$ swift -sanitize=thread source.swift -o executable
$ xcodebuild -enableThreadSanitizer YES
//Stop after the first error
$ TSAN_OPTIONS=halt_on_error=1 ./executable
TSan現(xiàn)在只支持64位macOS洲脂,以及64位的iOS和tvOS的模擬器,并不支持真機調(diào)試和watchOS剧包。
那么TSan作為一個能夠檢查線程錯誤的工具, 它能檢查以下類型的錯誤:
- Use of uninitialized mutexes(使用未初始化的互斥器)
- Thread leaks (missing phread_johin)(線程泄漏)
- Unsafe calls in signal handlers (ex:malloc)()
- Unlock from wrong thread(從錯誤的線程解鎖)
- Data race(數(shù)據(jù)沖突)
那么我們拿下面的這段代碼來舉例:
- (void)viewDidLoad {
[super viewDidLoad];
[self resetStatue];
pthread_mutex_init(&(_mutex), NULL);
}
- (void)resetStatue{
[self acquireLock];
self.dataArray = nil;
[self releaseLock];
}
- (void)acquireLock{
pthread_mutex_lock(&_mutex);
}
- (void)releaseLock{
pthread_mutex_unlock(&_mutex);
}
這里我們?yōu)榉乐苟鄠€線程去訪問同一個dataArray屬性恐锦,在resetStatue方法中使用了互斥鎖,但是resetStatue的調(diào)用是在互斥鎖初始化(pthread_mutex_init(&(_mutex), NULL))之前疆液,這樣的調(diào)用順序是錯誤的一铅,在不開啟TSan時,程序不會發(fā)生崩潰堕油。開啟Tasn后潘飘,效果如下:
左邊runtime issue中明確的告訴了我們錯誤的類型(Use of uninitialized mutexes)肮之,而且把線程中的歷史信息都記錄了下來以便我們分析并解決這個問題。
在開發(fā)中我們還會用到Reveal卜录、Charles局骤、Instruments的Leaks、Time Profiler等工具暴凑,掌握這些后會讓你在開發(fā)中發(fā)現(xiàn)問題峦甩、解決問題更加容易,提高生產(chǎn)力现喳。