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

Advanced Debugging & Swift

956ea68076aef74ad082570b4da8eb54?s=47 Dave Lee
October 25, 2018

Advanced Debugging & Swift

Debugging is one of those skills that it pays to build up over time. This talk will demonstrate debugging techniques using Swift, that are not widely known. Learn how to reuse your Swift knowledge to write lldb helpers for debugging and development.

956ea68076aef74ad082570b4da8eb54?s=128

Dave Lee

October 25, 2018
Tweet

Transcript

  1. ADVANCED DEBUGGING & SWIFT

  2. DAVE LEE, @KASTIGLIONE IOS @ LYFT

  3. CHISEL 1 GITHUB.COM/FACEBOOK/CHISEL 1 Check out findinstances

  4. ADVANCED DEBUGGING @ WWDC 2018 DEVELOPER.APPLE.COM/VIDEOS/PLAY/WWDC2018/412

  5. THINS TO KEEP IN MIND > the debugger has bugs

    too > try something, might just work > 2 steps forward, 1 step back > pays off over time
  6. CHALLENGES > Xcode provides only a subset of lldb >

    virtually no lldb add-on ecosystem > lldb commands don't help anything
  7. None
  8. command alias alias command alias

  9. # ~/.lldbinit command alias alias command alias alias import command

    script import alias source command source alias shell platform shell ...
  10. # ~/.lldbinit settings set target.language swift

  11. <REFRESH>2 2 tell the people why

  12. > doing more > doing less > doing different

  13. ▶ CODE INJECTION TOOL

  14. UNCREATIVE EXAMPLE let button = UIButton(type: .plain) button.setTitle("Tap Me", for:

    .normal) button.setTitleColor(.red, for: .normal) self.view.addSubview(button)
  15. (lldb) expression

  16. (lldb) expression

  17. (lldb) call

  18. let button = UIButton(type: .plain) button.setTitle("Tap Me", for: .normal) button.setTitleColor(.red,

    for: .normal) ▶ self.view.addSubview(button) (lldb) call print("log tap me \(button)")
  19. let button = UIButton(type: .plain) button.setTitle("Tap Me", for: .normal) button.setTitleColor(.red,

    for: .normal) ▶ self.view.addSubview(button) (lldb) call button.setTitle("where's the adidas store?", for: .normal)
  20. (lldb) thread jump --by N

  21. (lldb) thread jump --by N

  22. (lldb) jump +N

  23. let button = UIButton(type: .plain) button.setTitle("Tap Me", for: .normal) ▶

    button.setTitleColor(.red, for: .normal) self.view.addSubview(button) (lldb) jump +1
  24. let button = UIButton(type: .plain) button.setTitle("Tap Me", for: .normal) ▶

    if halloween && holidayThemeEnabled { button.setTitleColor(.orange, for: .normal) } else { button.setTitleColor(.red, for: .normal) } self.view.addSubview(button) (lldb) jump +1 (lldb) jump +3
  25. let button = UIButton(type: .plain) button.setTitle("Tap Me", for: .normal) button.setTitleColor(.red,

    for: .normal) ▶ self.view.addSubview(button) (lldb) jump +1 (lldb) call self.bottomBar.addSubview(button)
  26. LIVE CODING (lldb) call view.center.y = 300 (lldb) call CATransaction.flush()

  27. REUSABLE HELPERS

  28. PYTHON

  29. NUDGE.PY SMALL EXCERPT # Fetch view.center and extract x and

    y member values. centerExpression = "(CGPoint)[(UIView *)%s center]" %(self.target_view.GetValue()) centerValue = target.EvaluateExpression(centerExpression, exprOptions) center_x = float(centerValue.GetChildMemberWithName('x').GetValue()) center_y = float(centerValue.GetChildMemberWithName('y').GetValue()) if self.original_center is None: self.original_center = (center_x, center_y) # Adjust the x,y center values by adding the offsets. center_x += x_offset center_y += y_offset # Set the new view.center. setExpression = "(void)[(UIView *)%s setCenter:(CGPoint){%f, %f}]" %(self.target_view.GetVal nter_x, center_y) target.EvaluateExpression(setExpression, exprOptions) # Tell CoreAnimation to flush view updates to the screen. target.EvaluateExpression("(void)[CATransaction flush]", exprOptions)
  30. DISCOURAGEMENT > Helper logic written not in Swift > App

    code as strings > Easy to mess up, easy to give up
  31. </REFRESH>

  32. OLD ME | | | | | | | |

    | | debug | app | | | | | | | | | | | | |
  33. NEW ME | | | | | | | |

    | | debug | app | | | | | | | | | | | | |
  34. nudge, A COMMAND TO DO THIS: view.frame = view.frame.offsetBy(dx: _,

    dy: _) CATransaction.flush()
  35. FUNCTIONS

  36. func nudge(_ view: UIView, _ dx: CGFloat, _ dy: CGFloat)

    { view.frame = view.frame.offsetBy(dx: dx, dy: dy) CATransaction.flush() }
  37. ! , BACK AT # load_swift.py import lldb @lldb.command("load_swift") def

    load_swift(debugger, path, ctx, result, _): with open(path) as f: contents = f.read() ctx.frame.EvaluateExpression(contents)
  38. LOADING SWIFT HELPERS3 # ~/.lldbinit import ~/load_swift.py load_swift ~/helpers.swift 3

    See the alias from ~/.lldbinit slide
  39. (lldb) call nudge(self.view, 0, 20) error: <EXPR>:3:1: error: use of

    unresolved identifier 'nudge' nudge(self.view, 0, 20) ^~~~~
  40. NEED $ func $nudge(_ view: UIView, _ dx: CGFloat, _

    dy: CGFloat) { view.frame = view.frame.offsetBy(dx: dx, dy: dy) CATransaction.flush() }
  41. (lldb) call $nudge(self.view, 0, 20)

  42. EXTENSIONS

  43. NO $ NEEDED extension UIView { func nudge(_ dx: CGFloat,

    _ dy: CGFloat) { self.frame = self.frame.offsetBy(dx: dx, dy: dy) CATransaction.flush() } }
  44. (lldb) call self.view.nudge(0, 20)

  45. COMPARE (lldb) call self.view.nudge(0, 20) (lldb) nudge 0 20 self.view

  46. EASY TO EXTEND extension UIView { func nudge(_ dx: CGFloat,

    _ dy: CGFloat) { UIView.animate(withDuration: 0.2) { self.frame = self.frame.offsetBy(dx: dx, dy: dy) } CATransaction.flush() } }
  47. BUILDING BLOCKS

  48. extension UIView { static var root: UIView { return UIApplication.shared.keyWindow!.rootViewController!.view

    } }
  49. (lldb) po UIView.root

  50. extension UIViewController { static var current: UIViewController { var controller

    = UIApplication.shared.keyWindow!.rootViewController! if let child = (controller as? UINavigationController)?.topViewController { controller = child } if let child = (controller as? UITabBarController)?.selectedViewController { controller = child } return controller } }
  51. (lldb) po UIViewController.current

  52. extension UIView { static var current: UIView { return UIViewController.current.view

    } }
  53. (lldb) po UIView.current

  54. extension UIView { func tree() -> UnfoldSequence<UIView, [UIView]> { return

    sequence(state: [self]) { state in guard let view = state.popLast() else { return nil } state.append(contentsOf: view.subviews.reversed()) return view } } }
  55. (lldb) po UIView.current.tree().first { $0 is UIButton } (lldb) po

    UIView.current.tree().filter { $0 is UIButton }
  56. extension UIView { func grep(_ pattern: String) -> [UILabel] {

    return tree() .compactMap { $0 as? UILabel } .filter { $0.text?.range(of: pattern) != nil } } }
  57. (lldb) po UIView.current.grep("message")

  58. extension UIView { static func first(in root: UIView) -> Self?

    { return root.first { _ in true } } func first<T: UIView>(where match: (T) -> Bool) -> T? { return self.tree() .compactMap { $0 as? T } .filter(match) .first } }
  59. (lldb) po UIButton.first(in: UIView.current) (lldb) po UIView.current.first { (b: UIButton)

    in b.currentTitle == "Tap Me" }
  60. extension UIView { func fillText(_ text: String) { if let

    textField = UITextField.first(in: self) { textField.text = text CATransaction.flush() } } }
  61. (lldb) call UIView.current.fillText("185 Berry")

  62. extension UIView { func screenshot(_ path: String) { let renderer

    = UIGraphicsImageRenderer(bounds: self.bounds) let png = renderer.pngData { _ in self.drawHierarchy(in: self.bounds, afterScreenUpdates: false) } try! png.write(to: URL(fileURLWithPath: path)) } }
  63. (lldb) call myView.screenshot("/Users/me/snap.png") (lldb) shell open /Users/me/snap.png

  64. AUTOMATE STUFF YOU DO A LOT

  65. (lldb) breakpoint set

  66. (lldb) breakpoint set

  67. (lldb) b

  68. (lldb) b MyViewController.viewDidLoad Breakpoint 1: 2 locations.

  69. (lldb) b 1: name = 'MyViewController.viewDidAppear', language = swift 1.1:

    MyApp.MyViewController.viewDidAppear(Bool) -> () at MyViewController.swift:23 1.2: @objc MyApp.MyViewController.viewDidAppear(Bool) -> () at <compiler-generated>
  70. (lldb) b MyViewController.viewDidLoad (lldb) b -r ^MyApp.MyViewController.viewDidLoad

  71. Is this a "shell" script? # ~/login.lldb # When the

    phone VC is shown, enter a phone number. b -r ^MyApp.PhoneLoginViewController.viewDidAppear action -o 'call $fillPhoneNumber("4032421999")' autocontinue # When the verify VC is shown, enter the verification. b -r ^MyApp.PhoneVerifyViewController.viewDidAppear action -o 'call $fillVerify("1234")' autocontinue # Automation setup complete, now go. continue
  72. # ~/.lldbinit alias action breakpoint command add alias autocontinue breakpoint

    modify --auto-continue true
  73. (lldb) source ~/login.lldb

  74. let $touchUpInside = UIControl.Event(rawValue: 64) func $fillPhoneNumber(_ phone: String) {

    let textField = UITextField.first(in: UIView.current) textField.text = phone let submitButton = UIButton.first(in: UIView.current) submitButton.sendActions(for: $touchUpInside) }
  75. (lldb) thread step-out

  76. (lldb) thread step-out

  77. (lldb) finish

  78. THANKS TO PG HERVEOU for his lldb and Swift explorations.