Xcode開發(fā)選項(xiàng)設(shè)置與調(diào)試工具

構(gòu)建測(cè)試平臺(tái)

獲取隱藏的文件信息,在終端輸入如下命令:

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)榭梢姡ㄒ浴?”開頭的隱藏文件。此外還會(huì)顯示詳細(xì)的文件路徑和擴(kuò)展信息,最為重要的是能看到與用戶相關(guān)的Library目錄,iOS模擬器和Xcode的數(shù)據(jù)都存儲(chǔ)在此目錄下。

chflags命令將那些蘋果認(rèn)為會(huì)迷惑用戶的隱藏文件全部顯示了出來,例如/tmp或/usr。這樣更方便查看模擬器目錄。

把$SIMPATH添加到Finder的側(cè)邊欄,便于訪問。默認(rèn)設(shè)置下,F(xiàn)inder沒有$SIMPATH。想要實(shí)現(xiàn)上述設(shè)置,在終端輸入以下命令:

cd ~/Library/Developer
open .

Developer文件夾下有三個(gè)目錄。其中CoreSimulator、Xcode是我們常用的。將Developer拖拽到Finder側(cè)邊欄中。

屏幕快照 2017-03-26 下午12.33.07.png

Xcode構(gòu)建設(shè)置

首先將警告視為錯(cuò)誤。大部分警告都是由clang產(chǎn)生的,它屬于Xcode編譯器的前端(frontend),值得認(rèn)真對(duì)待。這樣做可以減少代碼復(fù)雜度、確保語法正確,還可以捕獲那些難以發(fā)現(xiàn)的錯(cuò)誤,比如無符號(hào)問題或是格式字符串漏洞,看如下代碼:

- (void)validate:(NSArray *)someTribbles withValue:(NSInteger)desired{
    if(desired > [someTribbles count]) {
        
    }
}

NSArray的count方法返回一個(gè)無符號(hào)整形(NSUInteger),if判斷語句會(huì)提示 “'NSInteger' (aka 'long') and 'NSUInteger' (aka 'unsigned long'”警告。啟用將警告視為錯(cuò)誤的選項(xiàng),從而使clang標(biāo)記該類型為bug,運(yùn)行工程將會(huì)失敗。

這里的意思是說啟用項(xiàng)目構(gòu)建配置中的更多警告模式,并將警告提升到bug的高度,強(qiáng)迫自己盡可能在開發(fā)的早期階段解決掉一些隱患,培養(yǎng)良好的編碼習(xí)慣。

在target->build setting->Warning Policies下,把Treat Warnings as Errors 設(shè)置為YES。

屏幕快照 2017-03-26 下午1.06.59.png

在Custom Compiler Flags下,設(shè)置Other Warning Flags,這里有-Wall、-Wextra、-Weverything三個(gè)值可選,含義如下:

  • -Wall 并不是所有警告。這一個(gè)警告組開啟的是編譯器開發(fā)者對(duì)于“你所寫的代碼中有問題”這一命題有著很高的自信的那些警告。要是在這一組設(shè)定下你的代碼出現(xiàn)了警告,那基本上就是你的代碼真的存在嚴(yán)重問題了。但是同時(shí),并不是說打開Wall就萬事大吉了,因?yàn)閃all所針對(duì)的僅僅只是經(jīng)典代碼庫中的為數(shù)不多的問題,因此有一些致命的警告并不能被其捕捉到。但是不論如何,因?yàn)閃all的警告提供的都是可信度和優(yōu)先級(jí)很高的警告,所以為所有項(xiàng)目(至少是所有新項(xiàng)目)打開這組警告,應(yīng)該成為一種良好的習(xí)慣。

  • -Wextra 如其所名,-Wextra組提供“額外的”警告。這個(gè)組和-Wall組幾乎一樣有用,但是有些情況下對(duì)于代碼相對(duì)過于嚴(yán)苛。一個(gè)很常見的例子是,-Wextra中包含了-Wsign-compare, 這個(gè)警告標(biāo)識(shí)會(huì)開啟比較時(shí)候?qū)igned和unsigned的類型檢查,當(dāng)比較符兩邊一邊是signed一邊是unsigned時(shí),產(chǎn)生警告。其實(shí)很多 代碼并沒有特別在意這樣的比較,而且絕大多數(shù)時(shí)候,比較signed和unsigned也是沒有太大問題的(當(dāng)然不排除會(huì)有致命錯(cuò)誤出現(xiàn)的情況)。需要注意,-Wextra和-Wall是相互獨(dú)立的兩個(gè)警告組,雖然里面打開的警告標(biāo)識(shí)有個(gè)別是重復(fù)的,但是兩組并沒有包含的關(guān)系。想要同時(shí)使用的話必須都加上

  • -Weverything 這個(gè)是真正的所有警告。但是一般開發(fā)者不會(huì)選擇使用這個(gè)標(biāo)識(shí),因?yàn)樗四切┻€正在開發(fā)中的可能尚存bug的警告提示。這個(gè)標(biāo)識(shí)一般是編譯器開發(fā)者用來調(diào)試時(shí)使用的,如果你想在自己的項(xiàng)目里開啟的話,警告一定會(huì)爆棚導(dǎo)致你想開始撞墻..

這里推薦-Wall和-Wextra,或者-Wextra,設(shè)置后運(yùn)行程序警告數(shù)成倍的增長(zhǎng),看看其中哪些是真正的bug。

屏幕快照 2017-03-26 下午1.21.17.png

Clang和靜態(tài)分析(Static Analyzer)

靜態(tài)分析一般指的是使用工具來分析代碼并找出安全漏洞。這可能涉及識(shí)別一系列危險(xiǎn)的API,或分析通過應(yīng)用程序的數(shù)據(jù)流,來標(biāo)識(shí)潛在不安全的程序輸入。

Xcode中提供了一個(gè)UI界面工具。用戶可以使用該工具很直觀的完成邏輯追蹤、代碼漏洞以及通用API誤用等分析操作。但有一些重要特性,Xcode默認(rèn)是禁止。其中值得關(guān)注的特性有:對(duì)C語言庫函數(shù)等經(jīng)典威脅的檢查,比如針對(duì)strcpy和strcat的檢查就是默認(rèn)關(guān)閉的。你可以在項(xiàng)目或Target設(shè)置中開啟這些特性:

屏幕快照 2017-03-26 下午1.33.09.png

在Xcode8中,靜態(tài)分析能夠檢測(cè)出三種新的錯(cuò)誤, 它們分別是Localizability、Instance Cleanup、Nullability。

  • Localizability 其實(shí)說的是靜態(tài)分析能夠檢測(cè)出本地化信息缺失的問題,目前能夠檢測(cè)出來兩種類型的錯(cuò)誤,一種是沒有使用 NSLocalizeString這樣的API,而直接給控件設(shè)置Sting的情況,一種是使用了相應(yīng)的API,但在comment信息里面賦值為nil。默認(rèn)是不開啟的,在BuildSting中作如下設(shè)置:
屏幕快照 2017-03-26 下午1.46.12.png
  • Instance Cleanup 在MRC的代碼中,尤其在dealloc中,我們不應(yīng)該對(duì)assign類型的屬性進(jìn)行release操作,應(yīng)該對(duì)retain或者 copy類型的屬性進(jìn)行release操作,如果不這樣操作的話,會(huì)引發(fā)一些不必要的麻煩

  • Nullability 在2015年的WWDC大會(huì)上,Objective-C引入的一個(gè)新特性就叫做Nullability,用于表明一個(gè)東西到底可以為nil還是不可以為nil,這和Swift里的option類型很相似。

Sanitizer和動(dòng)態(tài)分析

  • Sanitizer是一個(gè)用于優(yōu)化的工具。

在edit scheme->Diagnostics->Runtime Sanitizer中勾選相應(yīng)的Sanitizer選項(xiàng)。

屏幕快照 2017-03-26 下午6.44.40.png

勾選了相應(yīng)的選項(xiàng)并不代表你就能使用 Sanitizer 來Check代碼了, 你還必須重新run一下代碼,為什么呢?

這就必須說說整個(gè)代碼 build flow 了. 如下圖所示, 通過勾選了對(duì)應(yīng)的選項(xiàng), Xcode 會(huì)向 clang 傳遞一個(gè)特定的參數(shù), 然后生成一個(gè)獨(dú)特的 binary, 然后這個(gè) binary 會(huì)和 Thread Sanitizer 或者 Address Sanitizer 的 dylib 鏈接在一起. 這樣 Sanitizer 就實(shí)現(xiàn)了它想要達(dá)到的功能.

406302-b3a58fd1cf107857.jpg

Address Sanitizer(ASan)是一個(gè)類似Valgrind的動(dòng)態(tài)分析工具,ASan可以檢測(cè)出堆、棧溢出和釋放后又被使用的bug,還能找到關(guān)鍵的安全漏洞。ASan會(huì)對(duì)性能產(chǎn)生一些影響(程序執(zhí)行速度會(huì)慢一些),因此不要在發(fā)行版本中啟用這個(gè)選項(xiàng),但是在測(cè)試、質(zhì)量保證檢測(cè)或是缺陷測(cè)試階段,使用這一特性帶來便利吧。ASan能檢查以下類型的錯(cuò)誤:

  • 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);
}

運(yùn)行上面的代碼,在開啟ASan時(shí),程序會(huì)崩潰在“buffer[10] = 'A';”這一行,并顯示相應(yīng)的崩潰類型。在不開啟ASan時(shí),不添加任何斷點(diǎn)時(shí),程序會(huì)崩潰在main函數(shù)里;添加任何斷點(diǎn)后,程序不會(huì)崩潰。

問題很明顯,這是一個(gè)數(shù)組越界,開啟ASan后會(huì)直接定位到問題發(fā)生的地方,不開啟的話,阿彌陀佛了。

  • Thread Sanitizer (TSan)是Xcode8的新特性,檢測(cè)線程方面的問題。

讓我們想想自己在調(diào)試線程方面的 bug 時(shí), 有哪些令人記憶深刻的東西:

  • 線程方面的 bug 對(duì)時(shí)間很敏感, 這就導(dǎo)致很多線程的 bug 極難復(fù)現(xiàn), 復(fù)現(xiàn)都成問題, 還怎么改 bug
  • 由于線程的抽象概念導(dǎo)致在 debug 時(shí)候也比一般的 debug 更費(fèi)勁兒, 這時(shí)候總覺自己腦子不夠使
  • 有時(shí)候, 由于線程引起的 crash 或者 error ,讓我們根本意識(shí)不到這其實(shí)是線程出了問題

在Address Sanitizer下面勾選Thread Sanitizer。至于 Thread Sanitizer下面的那個(gè)Pause on Issues的選項(xiàng)就是說,如果你想一個(gè)一個(gè)看runtime issue就勾選它,如果你不想這樣,就不要勾選它, 具體是個(gè)神馬感覺,你自己試試嘍。

如果你喜歡使用 Comman-Line ,那么請(qǐng)記住下面的代碼

//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的模擬器,并不支持真機(jī)調(diào)試和watchOS。

那么TSan作為一個(gè)能夠檢查線程錯(cuò)誤的工具, 它能檢查以下類型的錯(cuò)誤:

  • Use of uninitialized mutexes(使用未初始化的互斥器)
  • Thread leaks (missing phread_johin)(線程泄漏)
  • Unsafe calls in signal handlers (ex:malloc)()
  • Unlock from wrong thread(從錯(cuò)誤的線程解鎖)
  • 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)榉乐苟鄠€(gè)線程去訪問同一個(gè)dataArray屬性,在resetStatue方法中使用了互斥鎖,但是resetStatue的調(diào)用是在互斥鎖初始化(pthread_mutex_init(&(_mutex), NULL))之前,這樣的調(diào)用順序是錯(cuò)誤的,在不開啟TSan時(shí),程序不會(huì)發(fā)生崩潰。開啟Tasn后,效果如下:

屏幕快照 2017-03-26 下午7.33.48.png

左邊runtime issue中明確的告訴了我們錯(cuò)誤的類型(Use of uninitialized mutexes),而且把線程中的歷史信息都記錄了下來以便我們分析并解決這個(gè)問題。

在開發(fā)中我們還會(huì)用到Reveal、Charles、Instruments的Leaks、Time Profiler等工具,掌握這些后會(huì)讓你在開發(fā)中發(fā)現(xiàn)問題、解決問題更加容易,提高生產(chǎn)力。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容