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

Debug Debug

Debug Debug

How to debug an iOS application

Avatar for Weizhong Yang

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,