可能碰到的iOS筆試面試題(11)--報錯警告除錯

b10l07發表於2016-05-04

報錯警告除錯

你在實際開發中,有哪些手機架構與效能除錯經驗

  • 剛接手公司的舊專案時,模組特別多,而且幾乎所有的程式碼都寫在控制器裡面,比如UI控制元件程式碼、網路請求程式碼、資料儲存程式碼
  • 接下來採取MVC模式進行封裝、重構
    • 自定義UI控制元件封裝內部的業務邏輯
    • 封裝網路請求工具類(降低耦合)
    • 封裝資料儲存工具類

BAD_ACCESS在什麼情況下出現?

這種問題是經常遇到的,在開發時經常會出現BAD_ACCESS。原因是訪問了野指標,比如訪問已經釋放物件的成員變數或者發訊息、死迴圈等。

如何除錯BAD_ACCESS錯誤?

出現BAD_ACCESS錯誤,通常是訪問了野指標,比如訪問了已經釋放了的物件。快速定位問題的步驟有:
1.  重寫物件的respondsToSelector方法,先找到出現EXECBADACCESS前訪問的最後一個object
2.  設定Enable Zombie Objects
3.  設定全域性斷點快速定位問題程式碼所在行,接收所有的異常
4.  Xcode7已經整合了BAD_ACCESS捕獲功能:Address Sanitizer,與步驟2一樣設定
5. analyze也行(不一定管用)

什麼時候會報 unrecognized selector 異常?

  • 當呼叫物件(子類,各級父類)中不含有對應方法的時候,並且依舊沒有給出“訊息轉發”的具體方案的時候,程式在執行時會crash並丟擲 unrecognized selector 異常

  • objective-c 中的每個方法在執行時會被轉為訊息傳送objc_msgSend(reciver, selector)

  • 例如 [person say]就會被轉化為 objc_msgSend(person, @selector(say))

  • 執行時會根據物件(reciever) 的isa 指標找到該物件所對應的類,然後會依次在對應的類,父類,爺爺類,根類中找對應的方法

下面只講述物件方法的解析過程:

  • 第一步:+ (BOOL)resolveInstanceMethod:(SEL)sel實現方法,指定是否動態新增方法。若返回NO,則進入下一步,若返回YES,則通過class_addMethod函式動態地新增方法,訊息得到處理,此流程完畢。
  • 第二步:在第一步返回的是NO時,就會進入- (id)forwardingTargetForSelector:(SEL)aSelector方法,這是執行時給我們的第二次機會,用於指定哪個物件響應這個selector。不能指定為self。若返回nil,表示沒有響應者,則會進入第三步。若返回某個物件,則會呼叫該物件的方法。
  • 第三步:若第二步返回的是nil,則我們首先要通過- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector指定方法簽名,若返回nil,則表示不處理。若返回方法簽名,則會進入下一步。
  • 第四步:當第三步返回方法方法簽名後,就會呼叫- (void)forwardInvocation:(NSInvocation *)anInvocation方法,我們可以通過anInvocation物件做很多處理,比如修改實現方法,修改響應物件等
  • 第五步:若沒有實現- (void)forwardInvocation:(NSInvocation *)anInvocation方法,那麼會進入- (void)doesNotRecognizeSelector:(SEL)aSelector方法。若我們沒有實現這個方法,那麼就會crash,然後提示打不到響應的方法。到此,動態解析的流程就結束了。

有哪些常見的 Crash 場景?

  • 訪問了殭屍物件
  • 訪問了不存在的方法
  • 陣列越界
  • 在定時器下一次回撥前將定時器釋放,會Crash

lldb(gdb)常用的除錯命令?

•   p 輸出基本型別//p (int)[[[self view] subviews] count]
•   po 用於輸出 Objective-C 物件//po [self view]
•   expr 可以在除錯時動態執行指定表示式,並將結果列印出來。常用於在除錯過程中修改變數的值。//原始碼中 a = 1 ;expr a=2 輸出結果:(int) $0 = 2

如果一個函式10次中有7次正確,3次錯誤,問題可能出現在哪裡?

這樣的問題通過應聘者的分析,可以知道應聘者的功底如何。很多人的回答會是很簡單的,沒有從多方面去分析。這樣的問題也是很有意義的,在專案開發中所產生的bug,有的時候會出現這樣的情況,而程式碼量比較大且業務比較複雜時,通過其他工具並不能分析出來是什麼bug,但是我們卻可以根據出現的頻率推測。筆者把這個問題當作測試部反饋過來的bug描述問題來分析一下。
參考答案:
從問題描述可知,bug不會必現的,因此無法直接定位出錯之處。從以下角度出現來分析可能出錯之處:
1.  因出錯並不是崩潰,因此沒有錯誤日誌可看。第一步就是分析函式中的所有分支,是否在語法上存在可能缺少條件的問題。所以,檢查所有的分支,確保每個分支執行的結果的正確的
2.  檢測函式的引數,保證必傳引數不能為空,若為空應該丟擲異常。因此,用斷言檢測引數的正確性是很重要的。
3.  檢測函式中每個分支所呼叫的函式返回結果是正確的,其實就是一個遞迴的過程(步驟1、2)

你一般是如何除錯Bug的?

這個問題看起來很籠統,但又一針見血。通過應聘者的回答,可很直觀地看出這個應聘者的處理bug的能力,以及其解決問題的思維。
參考答案:
Bug分為測試中的Bug和線上的Bug:
•   線上Bug:專案使用了友盟統計,因此會有崩潰日誌,通過解析dYSM可以直接定位到大部分bug崩潰之處。解決線上bug需要從主幹拉一個新的分支,解決bug並測試通過後,再合併到主幹,然後上線。若是多團隊開發,可以將fix bug分支與其他團隊最近要上線的分支整合,然後整合測試再上線。
•   測試Bug:根據測試所反饋的bug描述,若語義不清晰,則直接找到提bug人,操作給開發人員看,最好是可以bug復現。解決bug時,若能根據描述直接定位bug出錯之處,則好處理;若無法直觀定位,則根據bug型別分幾種處理方式,比如崩潰的bug可以通過instruments來檢測、資料顯示錯誤的bug,則需要閱讀程式碼一步步檢視邏輯哪裡寫錯。
對於開發中出現的崩潰或者資料顯示不正常,那就需要根據經驗或者相關工具來檢測可能出錯之處。當然,團隊內溝通解決是最好的。

獲取一臺裝置唯一標識的方法有哪些?

  • 現在常用的是用UUID + keychain結合來實現這個需求。

  • UUID是Universally Unique Identifier的縮寫,中文意思是通用唯一識別碼。它是讓分散式系統中的所有元素,都能有唯一的辨識資訊,而不需要透過中央控制端來做辨識資訊的指定。這樣,每個人都可以建立不與其它人衝突的 UUID。在此情況下,就不需考慮資料庫建立時的名稱重複問題。蘋果公司建議使用UUID為應用生成唯一標識字串。

//獲取一個UUID
 - (NSString*)uuid {
    CFUUIDRef uuid = CFUUIDCreate( nil );
    CFStringRef uuidString = CFUUIDCreateString( nil, uuid );
    NSString * result = (NSString *)CFBridgingRelease(CFStringCreateCopy( NULL, uuidString));
    CFRelease(uuid);
    CFRelease(uuidString);
    return result;
}
  • 現在我們獲取到了一個UUID,雖然這個標識是唯一的,但是這樣還是無法保證每一次的唯一性,因為當你每次呼叫這個方法或者把應用解除安裝了,UUID會重新生成一個不同的。這個時候keychain就起到了作用。

  • 所以整個邏輯是這樣的:先從keychain取UUID,如果能取到,就用這個比對,如果取不到就重新生成一個儲存起來。keychain獨立在App之外,是和系統統一等級的,所以你不用擔心它掛掉。

  • keychain是蘋果公司Mac OS中的密碼管理系統。它在Mac OS 8.6中被匯入,並且包括在了所有後續的Mac OS版本中,包括Mac OS X。一個鑰匙串可以包含多種型別的資料:密碼(包括網站,FTP伺服器,SSH帳戶,網路共享,無線網路,群組軟體,加密磁碟映象等),私鑰,電子證書和加密筆記等。iOS端同樣有個keychain幫助我們管理這些敏感資訊。

  • 使用過keychain儲存過賬號密碼的童鞋應該對這個工具非常瞭解,在這裡不做過多解釋。使用keychain需要匯入Security.framework和KeychainItemWrapper.h/.m,KeychainItemWrapper.h/.m搜一下可以下載下來,拖入工程中。儲存UUID程式碼如下:

- (void)saveUuidWithKeyChain {
    KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc]
                                         initWithIdentifier:@"UUID" accessGroup:@"com.xxx.www"];
    NSString *strUUID = [keychainItem objectForKey:(id)kSecValueData];
    if (strUUID == nil || [strUUID isEqualToString:@""])
    {
        [keychainItem setObject:[self uuid] forKey:(id)kSecValueData];
    }
}
注:這個方法中accessGroup:這個引數如果一些App設定相同的話,是可以共享的。

  • 從keychain獲取UUID的方法如下:
- (NSString *)getKeychain {
    KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc]
                                         initWithIdentifier:@"UUID" accessGroup:@"com.xxx.www"];
    NSString *strUUID = [keychainItem objectForKey:(id)kSecValueData];
    return strUUID;
}
  • 至此,基本上唯一標識的幾個方法算是寫完了,大家可以測試一下,解除安裝應用再重新裝,從keychain讀取的UUID還是和之前一樣。

  • 但這裡有個不確定因素,就是手機系統恢復出廠設定或者抹掉所有資料的話,這個方法也可能不起作用了,因為它是依靠鑰匙串在生存,鑰匙串掛掉的話它也就失效了。

你一般是怎麼用 Instruments 的?

  • 這個問題也就是考察下你經驗如何了, Instruments裡面工具很多,也沒必要逐一說明,挑幾個常用的說下就好

  • 參考答案:

  • Time Profiler:效能分析

  • Zombies:檢查是否訪問了殭屍物件,但是這個工具只能從上往下檢查,不智慧

  • Allocations:用來檢查記憶體,寫演算法的那批人也用這個來檢查

  • Leaks:檢查記憶體,看是否有記憶體洩露

你一般是如何除錯 Bug 的?

  • 檢視異常報告
  • 配置相關環境,重現bug
  • 程式碼檢查
  • 用測試案例來捕獲bug
  • 可以請同事一同來審查問題,有些時候當局者迷,旁觀者清。

如何對iOS裝置進行效能測試?

Profile-> Instruments ->Time Profiler 進行效能測試!

測試iOS版的 App 注意事項分享以下幾點:

1.app使用過程中,接聽電話。可以測試不同的通話時間的長短,對於通話結束後,原先開啟的app的響應,比如是否停留在原先介面,繼續操作時的相應速度等。

2.app使用過程中,有推送訊息時,對app的使用影響

3.裝置在充電時,app的響應以及操作流暢度

4.裝置在不同電量時(低於10%,50%,95%),app的響應以及操作流暢度

5.意外斷電時,app資料丟失情況

6.網路環境變化時,app的應對情況如何:是否有適當提示?從有網路環境到無網路環境時,app的反饋如何?從無網路環境回到有網路環境時,是否能自動載入資料,多久才能開始載入資料

7.多點觸控的情況

8.跟其他app之間互相切換時的響應

9.程式關閉再重新開啟的反饋

10.IOS系統語言環境變化時

文章如有問題,請留言,我將及時更正。

滿地打滾賣萌求贊,如果本文幫助到你,輕點下方的紅心,給作者君增加更新的動力。

相關文章