在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ 調(diào)試核對清單
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開發(fā)
Collection View 動(dòng)畫
截圖測試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫解釋
響應(yīng)式 Android 應(yīng)用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫
常見的后臺實(shí)踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計(jì)
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動(dòng)追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對清單
View Controller 容器
學(xué)無止境
XCTest 測試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

調(diào)試核對清單

尋找 bug 非常耗費(fèi)時(shí)間;幾乎每一個(gè)有經(jīng)驗(yàn)的開發(fā)者,都曾在某一個(gè) bug 上花費(fèi)過很多天。在一個(gè)平臺上開發(fā)的時(shí)間越久,就會(huì)越容易找到 bug。然而,總有一些 bug 是難以找到與復(fù)現(xiàn)的。在最開始的時(shí)候,找到一種途徑去復(fù)現(xiàn) bug 總是很有用的。一旦你找到了某種途徑,可以持續(xù)的復(fù)現(xiàn) bug ,你就可以開始下一步工作,找到 bug。

這篇文章試圖闡釋的是我們在調(diào)試中經(jīng)常遇到的一些相對常見的問題。當(dāng)你遇到了一個(gè) bug 時(shí),你可以把本文當(dāng)做一份核對清單。通過核對這份清單列出的一些問題,可能會(huì)使你更快的找到這個(gè) bug。更理想的情況下,這里提到的一些技巧可以幫助我們在第一時(shí)間避免這些 bug 出現(xiàn)。

我們會(huì)從一系列引起 bug 的原因開始講起,其中一大部分 bug 對大家都已經(jīng)不算陌生。

回調(diào)是否在正確的線程進(jìn)行?

一個(gè)引發(fā)意外行為的原因,是有些東西運(yùn)行在錯(cuò)誤的線程上。舉個(gè)例子,當(dāng)你在非主線程的其它線程上更新 UIKit 的對象時(shí),事情會(huì)變的很糟糕。有的時(shí)候,更新會(huì)正常運(yùn)轉(zhuǎn),但大多數(shù)情況下,發(fā)生的情況都很怪異,甚至?xí)鸨罎?。在你的代碼中,利用斷言來檢查你是否在主線程中的做法可以緩和這種情況。通常來說,可能(意外地)發(fā)生在后臺線程中的回調(diào),可以來自網(wǎng)絡(luò)請求,計(jì)時(shí)器,文件讀取,或者是外部庫。

另一個(gè)解決方法是劃分出一個(gè)線程獨(dú)立的區(qū)域。舉個(gè)例子,如果你正在構(gòu)建一個(gè)基于網(wǎng)絡(luò) API 的封裝,你可以把所有的線程都封裝在那里進(jìn)行處理。在后臺線程中執(zhí)行所有的網(wǎng)絡(luò)請求,但把它們的回調(diào)全部轉(zhuǎn)移到主線程中。如此一來,你就再也不必?fù)?dān)心調(diào)用代碼中會(huì)出現(xiàn)什么問題。一個(gè)簡單的設(shè)計(jì)在開發(fā)中真的很有用。

這個(gè)對象的類是否正確?

這個(gè)問題基本上只存在于 Objective-C;在 Swift 中,有一個(gè)強(qiáng)壯的類型系統(tǒng),可以精確的保證對象或值的類型安全。而在 Objective-C 中,偶然把對象的類型弄錯(cuò)是很常見的。

例如,在 Deckset中,我們加入了一個(gè)與字體相關(guān)的新特性。其中,有一個(gè)對象的某個(gè)數(shù)組屬性命名為 fonts,然后我假定這個(gè)數(shù)組中的對象類型都為 NSFont??墒聦?shí)證明,數(shù)組里其實(shí)包含的是 NSString 類型的對象(字體名)。我花費(fèi)了一些時(shí)間才找到了原因,這是因?yàn)?,在大多?shù)部分情況下,程序是正常工作的。在 Objective-C 中,一種檢查類型問題的方法是利用斷言。另一種可以幫到自己的方法,是在命名時(shí)添加類型信息(如:這個(gè)數(shù)組可以命名為 fontNames)。在 Swift 中,確定類型就可以避免這些錯(cuò)誤(如:使用 [NSFont] 而非 [AnyObject])。

當(dāng)不確定一個(gè)對象的類型是否正確時(shí),你可以在調(diào)試器中將類型打印出來。另外,使用 isKindOfClass:的斷言去檢查一個(gè)對象的類是否正確也很實(shí)用。在 Swift 中,因?yàn)榭蛇x值的存在,你還可以使用關(guān)鍵字 as? 在任何需要的地方去做類型適配, 這比直接用 as 做強(qiáng)制轉(zhuǎn)換好用的多。以上的方法會(huì)讓你大大減少錯(cuò)誤的概率。

具體的 Build 設(shè)置

另一個(gè)常見的原因,是 build 設(shè)置中不同的配置間有一些不易被發(fā)現(xiàn)的出入。比如,有時(shí)編譯器譯器會(huì)做一些優(yōu)化,這使在調(diào)試中根本不會(huì)出現(xiàn)的 bug 卻在產(chǎn)品發(fā)布版本中的存在。這個(gè)情況相對來說并不常見,不過在當(dāng)前的 Swift 發(fā)布版中,就有報(bào)告表明類似問題的存在。

還有一種原因,是某個(gè)確定的變量或宏定義被不同的方式定義。比如,一些代碼可能會(huì)在開發(fā)中被注釋起來。我們在一個(gè)實(shí)例中寫了一些錯(cuò)誤的(足以引發(fā)崩潰的)用戶行為統(tǒng)計(jì)代碼,但在開發(fā)中我們關(guān)掉了統(tǒng)計(jì),所以我們在開發(fā) app 時(shí)永遠(yuǎn)看不到這些崩潰。

這幾種 bug 在開發(fā)中是很難被發(fā)現(xiàn)的。所以,一定要詳細(xì)且徹底的測試你的發(fā)布版 app。當(dāng)然,如果有其他人(比如 QA 組)可以測試它再好不過。

不同的設(shè)備

不同的設(shè)備,可用性會(huì)有所不同。如果你只在有限數(shù)量的設(shè)備上進(jìn)行測試,未覆蓋到的設(shè)備就會(huì)成為可能的 bug 原因之一。經(jīng)典的劇情 是只在模擬中測試而從未使用真機(jī)。不過即便你在真機(jī)上做了測試,你也需要考慮到不同的設(shè)備與可用性。比如,在處理內(nèi)置攝像頭時(shí),總是使用類似 isSourceTypeAvailable: 這樣的方法來檢測你是否可以使用某個(gè)輸入源。在你的設(shè)備上或許有可以工作的攝像頭,但是在用戶的設(shè)備上卻并不總是存在。(譯者注:比如坑爹的老版本 iPod Touch 5 16G 版就沒有后置攝像頭)

可變性

可變性也是一個(gè)很常見的難以追蹤的原因。比如,如果你在兩個(gè)線程中共享了一個(gè)對象,且它們同時(shí)修改了該對象,就可能出現(xiàn)很意外的情況。這類 bug 的痛點(diǎn)在于它們很難復(fù)現(xiàn)。

有一種解決方法是創(chuàng)建不可變對象。這樣,當(dāng)你訪問對象時(shí),你就知道這個(gè)操作是無法改變它的狀態(tài)的。關(guān)于這點(diǎn)有太多可講,不過更多的信息,我們建議你閱讀以下文章:結(jié)構(gòu)體和值類型值對象,對象的可變性關(guān)于可變性。

是否為空 (nil)

作為 Objective-C 的編程者,我們有時(shí)會(huì)因?yàn)?NullPointerException 取笑 JAVA 程序員。在很多情況下,我們可以安全的發(fā)送消息給 nil 不出現(xiàn)什么問題。不過,也有一些棘手的 bug 可能因此出現(xiàn)。如果你寫 Swift 代替 Objective-C,你可以安全的跳過這節(jié)內(nèi)容的大部分,因?yàn)?Swift 的可選值足以解決這其中大部分的問題。

你是否以 nil 做為參數(shù)調(diào)用了函數(shù)?

這個(gè)原因挺常見。一些方法會(huì)因?yàn)槟銈魅肓?nil 參數(shù)而崩潰。舉例,考慮以下片段:

NSString *name = @"";
NSAttributedString *string = [[NSAttributedString alloc] initWithString:name];

如果 name 是 nil,這段代碼將崩潰。復(fù)雜的地方在于當(dāng)這可能是一個(gè)你沒有發(fā)現(xiàn)的邊界用例(如 myObject 在大多數(shù)情況下是不可能為 nil 的)。當(dāng)寫你自己的方法時(shí),你可以添加一個(gè)自定義標(biāo)記,用來通知編譯器你是否允許 nil 參數(shù):

- (void)doSomethingWithRequiredString:(NSString *)requiredString
                                  bar:(NSString *)optionalString
        __attribute((nonnull(1)));

(來自:StackOverflow)

在添加這個(gè)標(biāo)記之后,當(dāng)你嘗試傳入一個(gè) nil 參數(shù)時(shí),會(huì)出現(xiàn)一個(gè)編譯器警告 。這挺好,因?yàn)槟阍僖膊挥每紤]這個(gè)邊界用例:你可以利用編譯器提供的功能替你做這樣的檢查。

另一種可行的方法是倒置信息流。比如,你可以創(chuàng)建一個(gè)自定義分類,比如在 NSString 添加一個(gè) attributedString 的實(shí)例方法 :

@implementation NSString (Attributes)

- (NSAttributedString*)attributedString {
    return [[NSAttributedString alloc] initWithString:self];
}

@end

這段代碼的好處是你現(xiàn)在可以安全的構(gòu)造一個(gè) attributedString。你可以寫 [@"John" attributedString],但你也可以將這個(gè)消息發(fā)送給 nil([nil attributedString]),這樣做并不會(huì)崩潰,而是得到一個(gè) nil 的結(jié)果。想看到關(guān)于這點(diǎn)的更多信息,請查閱 Graham Lee 的文章反轉(zhuǎn)信息流。

如果你想捕捉到更多必須成立的條件(如一個(gè)參數(shù)必須為某個(gè)確定的類),你也可以使用 NSParameterAssert

你是否確定你可以向 nil 發(fā)送消息?

這其實(shí)不是一個(gè)太常見的原因,但是它卻在一個(gè)真實(shí)的 app 中出現(xiàn)過。有時(shí),當(dāng)我們處理標(biāo)量時(shí),發(fā)送一個(gè)消息給 nil 可能產(chǎn)生意外的結(jié)果。來看看下面這段看起來沒什么問題的代碼片段:

NSString *greeting = @"Hello objc.io";
NSRange range = [greeting rangeOfString:@"objc.io"];
if (range.location != NSNotFound) {
  NSLog(@"Found the keyword!");
}

如果 greeting 包含了字符串 "objc.io",消息會(huì)被打印。如果 greeting 不包含這個(gè)字符串,則不會(huì)有消息被打印。不過,當(dāng) greeting 為 nil 時(shí)會(huì)發(fā)生什么呢?range 會(huì)變成一個(gè)值全部為0的結(jié)構(gòu)體,而 location 會(huì)變成0。因?yàn)?NSNotFound 被定義為-1,所以之后的消息會(huì)被打印出來。所以,任何時(shí)候,當(dāng)你處理純值和 nil時(shí),要確??紤]了更多情況。同樣的,Swift 可以使用可選值避免這個(gè)問題。

是不是類中的有什么東西沒有初始化?

有時(shí),當(dāng)代碼運(yùn)行到某個(gè)對象相關(guān)的部分時(shí),可能因?yàn)檎{(diào)用了一個(gè)未完全初始化的對象而被中斷。因?yàn)樵?init 中加入一些額外的代碼并不常見,所以,有時(shí)在你使用某個(gè)對象之前,你需要提前調(diào)用這個(gè)對象的一些方法。如果你忘記了調(diào)用這些方法,這個(gè)類就可能因?yàn)闊o法完全的初始化而出現(xiàn)一些奇怪的情況。所以,一定要確保在指定的初始化方法運(yùn)行之后,類已經(jīng)處于可用狀態(tài)。如果你確實(shí)需要指定的帶參數(shù)的初始化方法被運(yùn)行,同時(shí)又無法構(gòu)建出一個(gè)只使用 init 方法的就能完成初始化的類的話,你也可以選擇重載 init 來讓它崩潰。不過,當(dāng)你之后偶爾不小心用到 init 來實(shí)例化對象的時(shí)候,你可能會(huì)浪費(fèi)一點(diǎn)時(shí)間來進(jìn)行修改。

KVO

一個(gè)常見的原因是錯(cuò)誤的使用 KVO。壞消息是,犯錯(cuò)誤并不難,但好消息是,有一系列方法去避免。

你是否清除了你的觀察者?

一個(gè)簡單的錯(cuò)誤是添加觀察者對象,但不清除它們。在這種情況下,KVO 將持續(xù)的發(fā)送消息,但接收者可能已經(jīng)被釋放了,于是引發(fā)了崩潰。繞開它的一種方法是使用成熟的框架如 ReactiveCocoa,還有一些輕量級的庫用起來也不錯(cuò)。

還有一種方法是,無論你何時(shí)創(chuàng)建了一個(gè)新觀察者,立刻在 dealloc 里寫一個(gè)移除。然而,這個(gè)過程可以自動(dòng)執(zhí)行:比直接添加觀察者更好的辦法是,你可以創(chuàng)建一個(gè)自定義對象來讓它幫你進(jìn)行添加。這個(gè)對象負(fù)責(zé)添加觀察者并在它自己的 dealloc 里移除它。這樣做的優(yōu)勢是你的觀察者的生命周期會(huì)和這個(gè)對象的生命周期一樣。這意味著創(chuàng)建這個(gè)對象等價(jià)于添加了一個(gè)觀察者。然后你可以將它存為一個(gè)屬性,當(dāng)容器對象被析構(gòu),屬性會(huì)自動(dòng)被設(shè)置為 nil,然后移除觀察者。

有一點(diǎn)相對詳細(xì)的關(guān)于這種技術(shù)的解釋,包括一段簡單的代碼,可以在這里被找到。一個(gè)小巧的庫可以實(shí)現(xiàn)這個(gè)功能,那就是 THObserversAndBinders,或者你可以看看 Facebook 的 KVOController。

另一個(gè)關(guān)于 KVO 的問題是回調(diào)可能會(huì)從你預(yù)料之外的線程上返回 (就像我們在開頭線程部分描述的那樣)。同樣的方案,使用一個(gè)對象來解決這個(gè)問題 (如以上所說),你可以確保所有的回調(diào)會(huì)在一個(gè)確定的線程上返回。

依賴鍵(Dependent Key)的路徑

如果你觀察的屬性基于于另一個(gè)屬性,你需要確保你注冊了依賴鍵。否則,當(dāng)你的屬性變化時(shí),你可能不會(huì)得到回調(diào)。不久之前,我在我的依賴鍵聲明里創(chuàng)建了一個(gè)遞歸依賴 (屬性依賴于自己),然后奇怪的事情就發(fā)生了。

視圖

Outlet 和 Action

在使用 Interface Builder 時(shí)有一個(gè)常見的錯(cuò)誤,那就是忘記了連接 outlet 和 action?,F(xiàn)在它們通常會(huì)被標(biāo)記在代碼旁 (你可以在 outlet 和 action 旁邊看到小的圓圈)。當(dāng)然,想測試是否所有連接都和預(yù)想的一樣的話,可以通過添加單元測試來達(dá)到目的 (但是這可能會(huì)變成很嚴(yán)重的維護(hù)負(fù)擔(dān))。

另外,為了確保無論這種情況在何時(shí)發(fā)生,你都能盡快發(fā)現(xiàn),你也可以使用斷言。比如用 NSAssert 去驗(yàn)證你的 outlet 不是 nil。

未釋放的對象

當(dāng)你使用了 Interface Builder,你需要確保從一個(gè) nib 文件中載入的對象圖不會(huì)被釋放。有一些蘋果關(guān)于處理這個(gè)問題的要點(diǎn)。最好讀讀這篇文章且遵從那些建議,要么你的對象可能在你眼皮底下消失,或者過度持有。在簡單的 XIB 文件和 Storyboard 中也有一些不同,請確保你已經(jīng)倒背如流。

視圖的生命周期

當(dāng)處理視圖的時(shí)候,有很多可能的 bug 會(huì)出現(xiàn)。一個(gè)常見的錯(cuò)誤是在視圖還沒有初始化的時(shí)候就使用它們?;蛘撸憧赡茉谝粋€(gè)視圖只是初始化,卻還沒有設(shè)置尺寸時(shí)使用就使用它們。這里的關(guān)鍵是在視圖生命周期中,找到合適的節(jié)點(diǎn)去安排代碼?;〞r(shí)間去深入理解它們是如何工作,相對于以后調(diào)試的時(shí)間來說,絕對是穩(wěn)賺不賠的。

當(dāng)你往 iPad 上 移植一個(gè)已有的 app 時(shí),這有時(shí)也是一個(gè)常見的 bug 原因。與此前不曾遇到的情況不同,你現(xiàn)在可能需要擔(dān)心一個(gè)視圖控制器是否是一個(gè)子視圖控制器,它們?nèi)绾雾憫?yīng)旋轉(zhuǎn)事件,還有一些細(xì)微區(qū)別。針對這種情況,自動(dòng)布局可能會(huì)有一些幫助,它可以自動(dòng)響應(yīng)很多類似的變化。

一個(gè)常見的錯(cuò)誤是我們總是創(chuàng)建一個(gè)視圖,添加一些約束,然后將它添加進(jìn)父視圖里。不過,為了讓大部分約束能夠工作,這個(gè)視圖是需要添加在父視圖的視圖層級中的。勉強(qiáng)算作好消息的是,大部分情況下這會(huì)直接讓你的代碼崩潰,然后你可以很快的找到 bug。

最后

但愿以上的技術(shù)會(huì)幫助你擺脫 bug 或者完全的避免它們。還有一些自動(dòng)的幫助是可用的:在 Clang 設(shè)置中打開所有的警告消息,這可以向你展示很多可能的 bug。另外,使用靜態(tài)分析肯定能找到一些 bug (當(dāng)然你得定期的運(yùn)行它)。

上一篇:Swift下一篇:客戶端