Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Debug Debug

Debug Debug

How to debug an iOS application

Weizhong Yang

October 22, 2012
Tweet

More Decks by Weizhong Yang

Other Decks in Technology

Transcript

  1. 講者 • KKBOX 軟體⼯工程師 • KKBOX Mac/iOS Client • ⼩小⿆麥注⾳音、Yahoo!

    輸⼊入法、嘸蝦⽶米 X1、 OpenVanilla • 其他有的沒的(台灣天氣 iPhone…etc) Friday, August 3,
  2. 今天的講題 • 難度介於⾮非常初級到⾮非常深⼊入…因為新 ⼿手⽼老⼿手寫 code 都會有 bug • 如何閱讀 Crash

    Report,判斷是哪種問 題… • 也需要了解⼀一下常⾒見症狀 • https://github.com/zonble/Crashy Friday, August 3,
  3. 摘要 • 從哪裡找到 Crash Report • 從 Crash Report 找到程式錯在哪⼀一⾏行

    • EXC_BAD_ACCESS 錯誤 • EXC_CRASH 錯誤 • failed to resume in time • 還有⼀一個之前讓我抓了兩個星期的 Bug Friday, August 3,
  4. 什麼是 bug? • ⼀一般⽽而⾔言,程式運作不如預期,就可以 視作是 bug • 但是其實很多⼈人會把 spec 當做

    bug… • 今天主要討論的是各種 Crash、系統 Framework 視為異常⾏行為,以及 OS 會 把 app 給 kill 掉的狀況 Friday, August 3,
  5. 抓取 Crash Report 的 ⽅方法 • Xcode -> Organizer •

    Console.app(系統監視程式) • iTunes Connect • QuincyKit Friday, August 3,
  6. • 平時我們透過 50 pin 連接線連接 iOS Device 實機測試的時候,Xcode 可以幫 我們將

    crash log 中的記憶體位置,轉換 成實際的程式碼⾏行數 • 所以可以得到跟 gdb/lldb 的 backtrace ⼀一 樣的內容 Friday, August 3,
  7. 開發時 Crash 看到的 Back Trace Thread 0 name: Dispatch queue:

    com.apple.main-thread Thread 0 Crashed: 0 libsystem_kernel.dylib 0x3629632c 0x36285000 + 70444 1 libsystem_c.dylib 0x32de2208 0x32d95000 + 315912 2 libsystem_c.dylib 0x32ddb298 0x32d95000 + 287384 3 libsystem_c.dylib 0x32dedefa 0x32d95000 + 364282 4 libsystem_c.dylib 0x32deeb2a 0x32d95000 + 367402 5 libsystem_c.dylib 0x32d99934 0x32d95000 + 18740 6 CoreFoundation 0x35929730 0x35927000 + 10032 7 CoreFoundation 0x359296a4 0x35927000 + 9892 8 Crashy 0x000037dc -[CCRootTableViewController tableView:didSelectRowAtIndexPath:] (CCRootTableViewController.m:157) 9 UIKit 0x334c6936 0x33411000 + 743734 10 UIKit 0x33540620 0x33411000 + 1242656 11 Foundation 0x354fa92c 0x3545f000 + 637228 12 CoreFoundation 0x359b4a2c 0x35927000 + 580140 13 CoreFoundation 0x359b4692 0x35927000 + 579218 14 CoreFoundation 0x359b3268 0x35927000 + 574056 15 CoreFoundation 0x3593649e 0x35927000 + 62622 16 CoreFoundation 0x35936366 0x35927000 + 62310 17 GraphicsServices 0x375d2432 0x375ce000 + 17458 18 UIKit 0x33442cce 0x33411000 + 203982 19 Crashy 0x00002ca6 main (main.m:7) 20 Crashy 0x00002c4c start + 32 Friday, August 3,
  8. 平常會拿到的是這種 log Thread 0 name: Dispatch queue: com.apple.main-thread Thread 0

    Crashed: 0 libsystem_kernel.dylib 0x3629632c 0x36285000 + 70444 1 libsystem_c.dylib 0x32de2208 0x32d95000 + 315912 2 libsystem_c.dylib 0x32ddb298 0x32d95000 + 287384 3 libsystem_c.dylib 0x32dedefa 0x32d95000 + 364282 4 libsystem_c.dylib 0x32deeb2a 0x32d95000 + 367402 5 libsystem_c.dylib 0x32d99934 0x32d95000 + 18740 6 CoreFoundation 0x35929730 0x35927000 + 10032 7 CoreFoundation 0x359296a4 0x35927000 + 9892 8 Crashy 0x000157dc 0x13000 + 10204 9 UIKit 0x334c6936 0x33411000 + 743734 10 UIKit 0x33540620 0x33411000 + 1242656 11 Foundation 0x354fa92c 0x3545f000 + 637228 12 CoreFoundation 0x359b4a2c 0x35927000 + 580140 13 CoreFoundation 0x359b4692 0x35927000 + 579218 14 CoreFoundation 0x359b3268 0x35927000 + 574056 15 CoreFoundation 0x3593649e 0x35927000 + 62622 16 CoreFoundation 0x35936366 0x35927000 + 62310 17 GraphicsServices 0x375d2432 0x375ce000 + 17458 18 UIKit 0x33442cce 0x33411000 + 203982 19 Crashy 0x00014ca6 0x13000 + 7334 20 Crashy 0x00014c4c 0x13000 + 7244 Friday, August 3,
  9. 要有當初的 Debug Symbol 才能解開 • 上架時、給 tester 時,都應該保存當時 的 Xcode

    Archive • 只記得當初是⽤用哪個 git revision 發⾏行然 後重編也不⾒見得會編出同樣的東⻄西,因 為我們中間可能會升級 Xcode • 使⽤用 Jenkins 等 CI 系統也是好主意 Friday, August 3,
  10. Debug Symbol • 放在你的 .app.dSYM ⽂文件 bundle 中 • 打開⽂文件

    bundle,進⼊入 <YourApp>.app.dSYM/Contents/Resources/ DWARF Friday, August 3,
  11. 記憶體管理 • 有點懶得細講… • 有了 ARC 之後問題應該少很多 • 跑⼀一下靜態分析多半可以找出問題 •

    原則其實很簡單,主要是要培養好習慣 • 只要是成員變數與 getter 就 retain • dealloc 時 release 所有成員變數 • ⼀一般 method 都回傳 auto release 物件 Friday, August 3,
  12. 記憶體管理 • Notifications • 不是 Push Notification,是指 Notification Center •

    addObserver… 之後,dealloc 的時候要 removeObserver:self • KVO 也⼀一樣要注意 Friday, August 3,
  13. 常⾒見 Exception • 超出 Array 範圍 • addObject:、setObject:forKey: 傳⼊入了 nil

    • 呼叫了物件不⽀支援的 selector • 還有很多… Friday, August 3,
  14. Raising Exceptions • assert • NSAssert • 可以⽤用 -DNS_BLOCK_ASSERTIONS=1 在

    release build 取消 NSAssert • [NSException raise: format:]… Friday, August 3,
  15. Try Catch • 我們可以使⽤用 @try @catch 語法捕捉 NSException,⽤用法與許多其他語⾔言 (Java、Python)⼀一樣 •

    但是慣例上,蘋果不建議使⽤用 @try @catch 做流程控制;原因是, NSException 往往被定義成「⼀一定不可以 這樣寫」的程式錯誤 Friday, August 3,
  16. 往每個 thread 裡頭是 讀不到東⻄西的 Thread 0 Crashed: 0 libsystem_kernel.dylib 0x3629632c

    __pthread_kill + 8 1 libsystem_c.dylib 0x32de2208 pthread_kill + 48 2 libsystem_c.dylib 0x32ddb298 abort + 88 3 libc++abi.dylib 0x35d98f64 abort_message + 40 4 libc++abi.dylib 0x35d96346 _ZL17default_terminatev + 18 5 libobjc.A.dylib 0x37d87350 _objc_terminate + 140 6 libc++abi.dylib 0x35d963be _ZL19safe_handler_callerPFvvE + 70 7 libc++abi.dylib 0x35d9644a std::terminate() + 14 8 libc++abi.dylib 0x35d9781e __cxa_rethrow + 82 9 libobjc.A.dylib 0x37d872a2 objc_exception_rethrow + 6 10 CoreFoundation 0x35936506 CFRunLoopRunSpecific + 398 11 CoreFoundation 0x35936366 CFRunLoopRunInMode + 98 12 GraphicsServices 0x375d2432 GSEventRunModal + 130 13 UIKit 0x33442cce UIApplicationMain + 1074 14 Crashy 0x00009ca6 main (main.m:7) 15 Crashy 0x00009c4c start + 32 這只是看系統怎麼 throw Exception 還有 kill ⽽而已 Friday, August 3,
  17. 要去看 Last Exception Backtrace Last Exception Backtrace: 0 CoreFoundation 0x359e088f

    __exceptionPreprocess + 163 1 libobjc.A.dylib 0x37d87259 objc_exception_throw + 33 2 CoreFoundation 0x359351d7 -[__NSArrayM insertObject:atIndex:] + 187 3 Crashy 0x0000a9a7 -[CCRootTableViewController tableView:didSelectRowAtIndexPath:] (CCRootTableViewController.m:191) 4 UIKit 0x334c693d -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 945 5 UIKit 0x33540627 -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 159 6 Foundation 0x354fa933 __NSFireDelayedPerform + 415 7 CoreFoundation 0x359b4a33 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 15 8 CoreFoundation 0x359b4699 __CFRunLoopDoTimer + 365 9 CoreFoundation 0x359b326f __CFRunLoopRun + 1207 10 CoreFoundation 0x359364a5 CFRunLoopRunSpecific + 301 11 CoreFoundation 0x3593636d CFRunLoopRunInMode + 105 12 GraphicsServices 0x375d2439 GSEventRunModal + 137 13 UIKit 0x33442cd5 UIApplicationMain + 1081 Friday, August 3,
  18. 解開之前的樣⼦子 Last Exception Backtrace: (0x359e088f 0x37d87259 0x359351d7 0xa9a7 0x334c693d 0x33540627

    0x354fa933 0x359b4a33 0x359b4699 0x359b326f 0x359364a5 0x3593636d 0x375d2439 0x33442cd5 0x9cad 0x9c54) ⼀一樣去⽤用 atos 查 Friday, August 3,
  19. 無窮迴圈 - (void)loadView { self.view; } # self.view 是 nil

    的時候會呼叫 -loadView Friday, August 3,
  20. 不可以 push 另外⼀一個 Navigation Controller UINavigationController *navController = [[UINavigationController alloc]

    initWithRootViewController:nil]; [self.navigationController pushViewController:navController animated:YES]; [navController release]; Friday, August 3,
  21. ⼀一邊推⼀一邊拉會亂掉 UIViewController *viewController = [[UIViewController alloc] init]; [self.navigationController pushViewController:viewController animated:YES];

    [self.navigationController performSelector:@selector(popToRootViewController Animated:) withObject:nil afterDelay:0.1]; [viewController release]; # Push 的動畫時間為 0.25 秒 Friday, August 3,
  22. Console Log Jun 3 02:08:06 unknown SpringBoard[15] <Warning>: net.zonble.Crashy failed

    to resume in time Jun 3 02:08:06 unknown SpringBoard[15] <Warning>: Forcing crash report of Crashy[1473]... Jun 3 02:08:07 unknown SpringBoard[15] <Warning>: Finished crash reporting. Friday, August 3,
  23. Crash Report Exception Type: 00000020 Exception Codes: 0x8badf00d Highlighted Thread:

    0 Application Specific Information: net.zonble.Crashy failed to launch in time Elapsed total CPU time (seconds): 21.560 (user 21.560, system 0.000), 54% CPU Elapsed application CPU time (seconds): 19.573, 49% CPU Friday, August 3,
  24. failed to resume in time • 在 application: didFinishLaunchingWithOptions: 太久,被

    系統當做無回應被 kill 掉 • 解法:想想有什麼東⻄西⾮非要在啟動時執 ⾏行? • performSelector: withObject: afterDelay: • dispatch_after Friday, August 3,
  25. ⼀一個簡單的 delegate 設計 @protocol CCBuggyObjectDelegate <NSObject> - (void)buggyObjectWillStart:(CCBuggyObject *)inObject; -

    (void)buggyObjectDidStart:(CCBuggyObject *)inObject; - (void)buggyObjectWillStop:(CCBuggyObject *)inObject; - (void)buggyObjectDidStop:(CCBuggyObject *)inObject; @end @interface CCBuggyObject : NSObject { id <CCBuggyObjectDelegate> delegate; } - (void)start; - (void)stop; @property (assign, nonatomic) id <CCBuggyObjectDelegate> delegate; @end Friday, August 3,
  26. 實作部分 - (void)start { NSLog(@"start step 1"); [delegate buggyObjectWillStart:self]; NSLog(@"start

    step 2"); [delegate buggyObjectDidStart:self]; } - (void)stop { NSLog(@"Stop step 1"); [delegate buggyObjectWillStop:self]; NSLog(@"Stop step 2"); [delegate buggyObjectDidStop:self]; } Friday, August 3,
  27. delegate method 亂實作就 爆炸了… - (void)buggyObjectWillStart:(CCBuggyObject *)inObject { [inObject start];

    # 無窮迴圈 } - (void)buggyObjectWillStart:(CCBuggyObject *)inObject { [inObject release]; # ⾃自殺⾏行為 } Friday, August 3,
  28. 這樣實作 Table View Delegate 穩死無疑 :) - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

    [tableView reloadData]; return 1; } # Table View 的 Data Source 與 Delegate method 的最⼤大 差別,就在於 Data Source method 絕對不可以呼叫 - reloadData。 Friday, August 3,
  29. 因為 start 接下來的部分 還是會繼續⾛走下去 - (void)start { NSLog(@"start step 1");

    [delegate buggyObjectWillStart:self]; #這邊呼叫 stop,但是會繼續⾛走到 start step 2,如 果 stop 之後⾺馬上 start,這邊的 code 就會不預期的 執⾏行了兩次 NSLog(@"start step 2"); [delegate buggyObjectDidStart:self]; } Friday, August 3,
  30. 阻⽌止在呼叫了 stop 之後繼 續往下⾛走…很醜 - (void)start { stopEverCalled = YES;

    // start step 1; [delegate buggyObjectWillStart:self]; if (stopEverCalled) return; # 沒事⼀一堆這種檢查真⾟辛苦… // start step 2; [delegate buggyObjectDidStart:self]; } - (void)stop { stopEverCalled = YES; // Do something… } Friday, August 3,
  31. 正規的設計 @protocol CCBuggyObjectDelegate <NSObject> - (BOOL)buggyObjectShouldStart:(CCBuggyObject *)inObject; - (void)buggyObjectDidStart:(CCBuggyObject *)inObject;

    - (BOOL)buggyObjectShouldStop:(CCBuggyObject *)inObject; - (void)buggyObjectDidStop:(CCBuggyObject *)inObject; @end Friday, August 3,
  32. 正規的設計 - (void)start { // start step 1; if (![delegate

    buggyObjectShouldStart:self]) { return; } // start step 2; [delegate buggyObjectDidStart:self]; } Friday, August 3,