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

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

調試核對清單

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

這篇文章試圖闡釋的是我們在調試中經常遇到的一些相對常見的問題。當你遇到了一個 bug 時,你可以把本文當做一份核對清單。通過核對這份清單列出的一些問題,可能會使你更快的找到這個 bug。更理想的情況下,這里提到的一些技巧可以幫助我們在第一時間避免這些 bug 出現。

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

回調是否在正確的線程進行?

一個引發(fā)意外行為的原因,是有些東西運行在錯誤的線程上。舉個例子,當你在非主線程的其它線程上更新 UIKit 的對象時,事情會變的很糟糕。有的時候,更新會正常運轉,但大多數情況下,發(fā)生的情況都很怪異,甚至會引起崩潰。在你的代碼中,利用斷言來檢查你是否在主線程中的做法可以緩和這種情況。通常來說,可能(意外地)發(fā)生在后臺線程中的回調,可以來自網絡請求,計時器,文件讀取,或者是外部庫。

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

這個對象的類是否正確?

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

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

當不確定一個對象的類型是否正確時,你可以在調試器中將類型打印出來。另外,使用 isKindOfClass:的斷言去檢查一個對象的類是否正確也很實用。在 Swift 中,因為可選值的存在,你還可以使用關鍵字 as? 在任何需要的地方去做類型適配, 這比直接用 as 做強制轉換好用的多。以上的方法會讓你大大減少錯誤的概率。

具體的 Build 設置

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

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

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

不同的設備

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

可變性

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

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

是否為空 (nil)

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

你是否以 nil 做為參數調用了函數?

這個原因挺常見。一些方法會因為你傳入了 nil 參數而崩潰。舉例,考慮以下片段:

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

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

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

(來自:StackOverflow)

在添加這個標記之后,當你嘗試傳入一個 nil 參數時,會出現一個編譯器警告 。這挺好,因為你再也不用考慮這個邊界用例:你可以利用編譯器提供的功能替你做這樣的檢查。

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

@implementation NSString (Attributes)

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

@end

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

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

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

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

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

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

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

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

KVO

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

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

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

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

有一點相對詳細的關于這種技術的解釋,包括一段簡單的代碼,可以在這里被找到。一個小巧的庫可以實現這個功能,那就是 THObserversAndBinders,或者你可以看看 Facebook 的 KVOController。

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

依賴鍵(Dependent Key)的路徑

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

視圖

Outlet 和 Action

在使用 Interface Builder 時有一個常見的錯誤,那就是忘記了連接 outlet 和 action?,F在它們通常會被標記在代碼旁 (你可以在 outlet 和 action 旁邊看到小的圓圈)。當然,想測試是否所有連接都和預想的一樣的話,可以通過添加單元測試來達到目的 (但是這可能會變成很嚴重的維護負擔)。

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

未釋放的對象

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

視圖的生命周期

當處理視圖的時候,有很多可能的 bug 會出現。一個常見的錯誤是在視圖還沒有初始化的時候就使用它們?;蛘?,你可能在一個視圖只是初始化,卻還沒有設置尺寸時使用就使用它們。這里的關鍵是在視圖生命周期中,找到合適的節(jié)點去安排代碼?;〞r間去深入理解它們是如何工作,相對于以后調試的時間來說,絕對是穩(wěn)賺不賠的。

當你往 iPad 上 移植一個已有的 app 時,這有時也是一個常見的 bug 原因。與此前不曾遇到的情況不同,你現在可能需要擔心一個視圖控制器是否是一個子視圖控制器,它們如何響應旋轉事件,還有一些細微區(qū)別。針對這種情況,自動布局可能會有一些幫助,它可以自動響應很多類似的變化。

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

最后

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

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