iOS 除錯篇

極光推送發表於2017-03-20

修復 bug 佔用我們日常開發的大部分時間,熟練的使用除錯工具可以給我們節約大部分的時間。

LLDB 的常用命令

expression

expresion 是一個非常常用的命令,我們可以通過這個命令來執行一些表示式,這樣我們就不需要重寫執行工程了,例如:

 (lldb) expression -- self.view.backgroundColor = [UIColor greenColor]
  (lldb) expression -- (void)[CATransaction flush] // 用於重新整理頁面,

我們也可以使用 expression 來輸出我們關注的資訊

(lldb) expression -O -- self.view //這時候 就會輸出物件 UIView ,注意 -O 代表輸出的是一個物件 ,這裡有另外一種簡寫方式 po.
<UIView: 0x7d2974a0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7d294813>>

有時候我們自定義的類並沒有重寫 description 方法,如果我們直接輸出這個物件的話可能只會顯示這個類名和地址,例如

(lldb) expression -O -- self
<JChatAboutMeViewController: 0x7d2974a0>

這地址並不是我們想要的,我們想要的是這個物件內部資訊,這裡我推薦一個外掛 chisel ,我們可以使用 chisel 提供的命令來列印這個物件, 例如

(lldb) pinternals 0x7d2974a0
(JChatAboutMeViewController) $4 = {
  UIViewController = {
    UIResponder = {
      NSObject = {
        isa = JChatAboutMeViewController
      }
      _hasOverrideClient = ` `
      _hasOverrideHost = ` `
      _hasInputAssistantItem = ` `
    }
    _overrideTransitioningDelegate = nil
    _view = nil
    _tabBarItem = nil
    _navigationItem = 0x7d4abf40
    _toolbarItems = nil
    _title = nil
    _nibName = nil
    _nibBundle = nil
    _parentViewController = 0x7cb84c00
    _childModalViewController = nil
    _parentModalViewController = nil
    _previousRootViewController = nil
    _modalTransitionView = nil
    _modalPreservedFirstResponder = nil
    _dimmingView = nil
    _dropShadowView = nil
    _currentAction = nil
    _storyboard = nil
    _externalObjectsTableForViewLoading = nil
    _topLevelObjectsToKeepAliveFromStoryboard = nil
    _savedHeaderSuperview = nil
    _savedFooterSuperview = nil
    _editButtonItem = nil
    _searchDisplayController = nil
    _strongSearchDisplayController = nil
    _modalTransitionStyle = 0
    _modalPresentationStyle = 0
    _lastKnownInterfaceOrientation = 0
    _popoverController = nil
    _containerViewInSheet = nil
    _recordedContentScrollView = nil
    _afterAppearance = nil
    _explicitAppearanceTransitionLevel = 0
    _interfaceBuilderKeyCommands = nil
    _addedKeyCommands = nil
    _overrideTraitCollections = nil
    _previewSourceViews = nil
    _retainCount = 0
    _ignoreAppSupportedOrientations = ` `
    _viewHostsLayoutEngine = ` `
    _storyboardIdentifier = nil
    _transitioningDelegate = nil
    _frozenTraitCollection = nil
    overrideUseCustomPresentation = ` `
    _modalPresentationCapturesStatusBarAppearance = ` `
    _disablesAutomaticKeyboardDismissal = ` `
    _ignoresParentMargins = ` `
    _childViewControllers = nil
    _customNavigationInteractiveTransitionDuration = 0
    _customNavigationInteractiveTransitionPercentComplete = 0
    _customTransitioningView = nil
    _lastNotifiedTraitCollection = nil
    _presentationController = nil
    _preferredFocusedItem = nil
    _navigationControllerContentOffsetAdjustment = 0
    _contentMargin = 16
    _topLayoutGuide = nil
    _bottomLayoutGuide = nil
    _topBarInsetGuideConstraint = nil
    _bottomBarInsetGuideConstraint = nil
    _storyboardSegueTemplates = nil
    _segueResponsibleForModalPresentation = nil
    _sourceViewControllerIfPresentedViaPopoverSegue = nil
    _modalSourceViewController = nil
    _presentedStatusBarViewController = nil
    _edgesForExtendedLayout = 15
    __childControllerToIgnoreWhileLookingForTransitionCoordinator = nil
    _presentingFocusedItem = nil
    _storyboardPreviewSegueTemplates = nil
    _storyboardCommitSegueTemplates = nil
    _storyboardPreviewingRegistrants = nil
    __embeddedView = 0xffffffff
    __embeddingView = 0x78b909c0
    __embeddedDelegate = 0x00007faa
    _originalPresentationController = 0x78af3630
    _temporaryPresentationController = 0x00007faa
  }
}

這樣我們就能看到這個物件的內部資訊了。 chisel 還提供一些便捷的功能,比如列印 pviews 遞迴列印層級,不過我更喜歡使用 xcode 自帶的 debug view hierarchy ,這樣更加直觀。

我們可以使用 pvc 遞迴輸出試圖控制器的層級關係,例如

(lldb) pvc
<JChatSwift.JChatMainTabViewController 0x7d49d560>, state: appeared, view: <UILayoutContainerView 0x7d479860>
   | <UINavigationController 0x7c3afc00>, state: appeared, view: <UILayoutContainerView 0x7be8e9a0>
   |    | <JChatConversationListViewController 0x7d49d2b0>, state: disappeared, view: <UIView 0x7d375320> not in the window
   |    | <JChatChattingViewController 0x7bfbdde0>, state: appeared, view: <UIView 0x7d373b30>
   | <UINavigationController 0x7eb7a000>, state: disappeared, view: <UILayoutContainerView 0x7d4a2c20> not in the window
   |    | <JChatContactsViewController 0x7bea3d70>, state: disappeared, view: <UILayoutContainerView 0x7d4a1d40> not in the window
   | <UINavigationController 0x7cb84c00>, state: disappeared, view: <UILayoutContainerView 0x7bf87590> not in the window
   |    | <JChatAboutMeViewController 0x7d2974a0>, state: disappeared, view:  (view not loaded)

通過 pvc 和 pinternals 這樣我們就可以在任何地方瞭解我們所有介面狀態了。 在 xcode8 以後,我們也可以通過 debug memory graph 來檢視程式執行的記憶體狀態。

thread

我們可以使用 thread backtrace 來輸出執行緒的堆疊資訊,例如

(lldb) thread backtrace  // 這個命令可以簡寫為 bt
* thread #1: tid = 0x14169a, 0x001273e5 JChatSwift`JChatChattingViewController.viewDidLayoutSubviews(self=0x7b6bc560) -> () + 21 at JChatChattingViewController.swift:48, queue = `com.apple.main-thread`, stop reason = breakpoint 7.1
  * frame #0: 0x001273e5 JChatSwift`JChatChattingViewController.viewDidLayoutSubviews(self=0x7b6bc560) -> () + 21 at JChatChattingViewController.swift:48
    frame #1: 0x00127502 JChatSwift`@objc JChatChattingViewController.viewDidLayoutSubviews() -> () + 34 at JChatChattingViewController.swift:0
    frame #2: 0x039dc811 UIKit`-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1598
    frame #3: 0x0305c1b9 libobjc.A.dylib`-[NSObject performSelector:withObject:] + 59
    frame #4: 0x03791769 QuartzCore`-[CALayer layoutSublayers] + 141
    frame #5: 0x03784a47 QuartzCore`CA::Layer::layout_if_needed(CA::Transaction*) + 401
    frame #6: 0x0378489d QuartzCore`CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 21
    frame #7: 0x0370e49f QuartzCore`CA::Context::commit_transaction(CA::Transaction*) + 339
    frame #8: 0x0373d290 QuartzCore`CA::Transaction::commit() + 498
    frame #9: 0x0373eda0 QuartzCore`CA::Transaction::flush_transaction() + 38
    frame #10: 0x0393685c UIKit`_afterCACommitHandler + 375
    frame #11: 0x022f676e CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 30
    frame #12: 0x022f66c7 CoreFoundation`__CFRunLoopDoObservers + 391
    frame #13: 0x022da3a6 CoreFoundation`__CFRunLoopRun + 1414
    frame #14: 0x022d9bab CoreFoundation`CFRunLoopRunSpecific + 395
    frame #15: 0x022d9a0b CoreFoundation`CFRunLoopRunInMode + 123
    frame #16: 0x06a1ab4c GraphicsServices`GSEventRunModal + 177
    frame #17: 0x06a1a9c7 GraphicsServices`GSEventRun + 80
    frame #18: 0x039077fb UIKit`UIApplicationMain + 148
    frame #19: 0x001a7ce1 JChatSwift`main + 145 at AppDelegate.swift:15
    frame #20: 0x06037799 libdyld.dylib`start + 1

我們可以看到程式停在 JChatChattingViewController.swift:48 這一行

watchpoint

監視某個變數的改變,有時候我們想知道一個物件在什麼時候被修改了,我們可以使用 watchpoint set var,當 var 改變的時候程式就就會停在改變的地方, 如果用 chisel 的話,我們可以使用 wivar 命令來監聽值的變化,例如

(lldb) wivar self name

作者: HuminiOS – 極光

原文:iOS 除錯篇

知乎專欄:極光日報

相關文章