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

Advanced Debugging & Swift

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.

Dave Lee

October 25, 2018
Tweet

More Decks by Dave Lee

Other Decks in Programming

Transcript

  1. 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
  2. CHALLENGES > Xcode provides only a subset of lldb >

    virtually no lldb add-on ecosystem > lldb commands don't help anything
  3. # ~/.lldbinit command alias alias command alias alias import command

    script import alias source command source alias shell platform shell ...
  4. UNCREATIVE EXAMPLE let button = UIButton(type: .plain) button.setTitle("Tap Me", for:

    .normal) button.setTitleColor(.red, for: .normal) self.view.addSubview(button)
  5. 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)")
  6. 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)
  7. let button = UIButton(type: .plain) button.setTitle("Tap Me", for: .normal) ▶

    button.setTitleColor(.red, for: .normal) self.view.addSubview(button) (lldb) jump +1
  8. 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
  9. 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)
  10. 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)
  11. DISCOURAGEMENT > Helper logic written not in Swift > App

    code as strings > Easy to mess up, easy to give up
  12. OLD ME | | | | | | | |

    | | debug | app | | | | | | | | | | | | |
  13. NEW ME | | | | | | | |

    | | debug | app | | | | | | | | | | | | |
  14. func nudge(_ view: UIView, _ dx: CGFloat, _ dy: CGFloat)

    { view.frame = view.frame.offsetBy(dx: dx, dy: dy) CATransaction.flush() }
  15. ! , 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)
  16. (lldb) call nudge(self.view, 0, 20) error: <EXPR>:3:1: error: use of

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

    dy: CGFloat) { view.frame = view.frame.offsetBy(dx: dx, dy: dy) CATransaction.flush() }
  18. NO $ NEEDED extension UIView { func nudge(_ dx: CGFloat,

    _ dy: CGFloat) { self.frame = self.frame.offsetBy(dx: dx, dy: dy) CATransaction.flush() } }
  19. 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() } }
  20. 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 } }
  21. 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 } } }
  22. (lldb) po UIView.current.tree().first { $0 is UIButton } (lldb) po

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

    return tree() .compactMap { $0 as? UILabel } .filter { $0.text?.range(of: pattern) != nil } } }
  24. 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 } }
  25. extension UIView { func fillText(_ text: String) { if let

    textField = UITextField.first(in: self) { textField.text = text CATransaction.flush() } } }
  26. 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)) } }
  27. (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>
  28. 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
  29. 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) }