iOSアプリの開発速度を170%に向上させたデバッグノウハウ / Debugging knowhow that improved our development velocity to 170%

151a0b14f5914e786e2e104cfb3a9b2f?s=47 Kuniwak
September 02, 2018

iOSアプリの開発速度を170%に向上させたデバッグノウハウ / Debugging knowhow that improved our development velocity to 170%

開発時間に占めるデバッグ時間の割合は少なくないため、この時間の短縮は開発速度を上げるためにとても重要です。この発表では、実際の中規模なアプリ開発で70%増もの開発速度向上を支えたデバッグノウハウを初心者にもわかりやすく紹介します。

https://fortee.jp/iosdc-japan-2018/proposal/7286f755-e980-4f6f-b268-2c56a224b727

151a0b14f5914e786e2e104cfb3a9b2f?s=128

Kuniwak

September 02, 2018
Tweet

Transcript

  1. 19.

    func foo(_ fooArg: Int) { let fooVar = fooArg +

    5 bar(fooVar) } func bar(_ barArg: Int) { let barVar = barArg * 7 baz(barVar) } func baz(_ bazArg: Int) { let bazVar = "\(bazArg)" print(bazVar) } ᶃؔ਺Λ࣮ߦ ίʔϧελοΫ
  2. 20.

    func foo(_ fooArg: Int) { let fooVar = fooArg +

    5 bar(fooVar) } func bar(_ barArg: Int) { let barVar = barArg * 7 baz(barVar) } func baz(_ bazArg: Int) { let bazVar = "\(bazArg)" print(bazVar) } ίʔϧελοΫ Ҿ਺ ϩʔΧϧม਺ GPP"SH GPP7BS GPPؔ਺ ؔ਺͕ݺͼग़͞ΕΔͱɺ
 ελοΫϑϨʔϜ͕࡞੒͞Εɺ
 ίʔϧελοΫʹੵ·ΕΔ
  3. 21.

    func foo(_ fooArg: Int) { let fooVar = fooArg +

    5 bar(fooVar) } func bar(_ barArg: Int) { let barVar = barArg * 7 baz(barVar) } func baz(_ bazArg: Int) { let bazVar = "\(bazArg)" print(bazVar) } ᶄ಺෦Ͱؔ਺Λ࣮ߦ ίʔϧελοΫ Ҿ਺ ϩʔΧϧม਺ GPP"SH GPP7BS GPPؔ਺
  4. 22.

    func foo(_ fooArg: Int) { let fooVar = fooArg +

    5 bar(fooVar) } func bar(_ barArg: Int) { let barVar = barArg * 7 baz(barVar) } func baz(_ bazArg: Int) { let bazVar = "\(bazArg)" print(bazVar) } ίʔϧελοΫ Ҿ਺ ϩʔΧϧม਺ GPP"SH GPP7BS GPPؔ਺ Ҿ਺ ϩʔΧϧม਺ CBS"SH CBS7BS CBSؔ਺ ݺͼग़͞Εͨؔ਺ͷதͰ
 ͞Βʹผͷؔ਺͕ݺ͹Εͯ΋
 ίʔϧελοΫʹੵ·ΕΔ
  5. 23.

    func foo(_ fooArg: Int) { let fooVar = fooArg +

    5 bar(fooVar) } func bar(_ barArg: Int) { let barVar = barArg * 7 baz(barVar) } func baz(_ bazArg: Int) { let bazVar = "\(bazArg)" print(bazVar) } ᶅ͞Βʹ಺෦Ͱؔ਺Λ࣮ߦ ίʔϧελοΫ Ҿ਺ ϩʔΧϧม਺ GPP"SH GPP7BS GPPؔ਺ Ҿ਺ ϩʔΧϧม਺ CBS"SH CBS7BS CBSؔ਺
  6. 24.

    func foo(_ fooArg: Int) { let fooVar = fooArg +

    5 bar(fooVar) } func bar(_ barArg: Int) { let barVar = barArg * 7 baz(barVar) } func baz(_ bazArg: Int) { let bazVar = "\(bazArg)" print(bazVar) } ίʔϧελοΫ Ҿ਺ ϩʔΧϧม਺ GPP"SH GPP7BS GPPؔ਺ Ҿ਺ ϩʔΧϧม਺ CBS"SH CBS7BS CBSؔ਺ Ҿ਺ ϩʔΧϧม਺ CB["SH CB[7BS CB[ؔ਺ ίʔϧελοΫʹੵ·ΕΔ
  7. 25.

    func foo(_ fooArg: Int) { let fooVar = fooArg +

    5 bar(fooVar) } func bar(_ barArg: Int) { let barVar = barArg * 7 baz(barVar) } func baz(_ bazArg: Int) { let bazVar = "\(bazArg)" print(bazVar) } ᶆϒϨʔΫϙΠϯτͰࢭΊΔ ίʔϧελοΫ Ҿ਺ ϩʔΧϧม਺ GPP"SH GPP7BS GPPؔ਺ Ҿ਺ ϩʔΧϧม਺ CBS"SH CBS7BS CBSؔ਺ Ҿ਺ ϩʔΧϧม਺ CB["SH CB[7BS CB[ؔ਺
  8. 26.

    func foo(_ fooArg: Int) { let fooVar = fooArg +

    5 bar(fooVar) } func bar(_ barArg: Int) { let barVar = barArg * 7 baz(barVar) } func baz(_ bazArg: Int) { let bazVar = "\(bazArg)" print(bazVar) } ίʔϧελοΫ Ҿ਺ ϩʔΧϧม਺ GPP"SH GPP7BS GPPؔ਺ Ҿ਺ ϩʔΧϧม਺ CBS"SH CBS7BS CBSؔ਺ Ҿ਺ ϩʔΧϧม਺ CB["SH CB[7BS CB[ؔ਺ ࢭ·ͬͨ࣌ʹݟ͍͑ͯΔ΋ͷ
  9. 27.

    func foo(_ fooArg: Int) { let fooVar = fooArg +

    5 bar(fooVar) } func bar(_ barArg: Int) { let barVar = barArg * 7 baz(barVar) } func baz(_ bazArg: Int) { let bazVar = "\(bazArg)" print(bazVar) } ίʔϧελοΫ Ҿ਺ ϩʔΧϧม਺ GPP"SH GPP7BS GPPؔ਺ Ҿ਺ ϩʔΧϧม਺ CBS"SH CBS7BS CBSؔ਺ Ҿ਺ ϩʔΧϧม਺ CB["SH CB[7BS CB[ؔ਺ ࣮͸શ෦--%#͔ΒݟΒΕΔ
  10. 36.

    (lldb) help Debugger commands: apropos -- List debugger commands related

    to a breakpoint -- Commands for operating on breakpoint shorthand.) ... IFMQΛ࣮ߦ͢Δͱɺ࣮ߦͰ͖Δૢ࡞ͷҰཡ͕දࣔ͞ΕΔ
  11. 37.

    (lldb) help po
 Evaluate an expression on the current
 thread.

    Displays any returned value
 with formatting controlled by the type's
 author. Expects 'raw' input (see 'help
 raw-input'.) ࠓճ͸--%#ܦ༝ͰίʔυΛ࣮ߦ͢ΔQPΛ࢖͏ ҙ༁: ݱࡏͷεϨουͰίʔυΛධՁ͠ɺ݁Ռͷ
 ஋ΛਓؒʹಡΈ΍͍͢ܗͰදࣔ͠·͢ɻ
  12. 38.

    (lldb) po print("Hello, World") error: use of undeclared identifier 'print'

    QPίϚϯυͰ)FMMP 8PSMEΛදࣔͯ͠ΈΑ͏ "QQ$PEFͷਓ͸͜ͷΤϥʔ͕ग़ͳ͍͔΋
  13. 42.

    (lldb) po UIApplication.shared.keyWindow!
 .value(forKey: "recursiveDescription")! <UIWindow: 0x7fbee05517b0; frame = (0

    0; 320 568); autoresize = W+H; g | <UIView: 0x7fbee0719bc0; frame = (0 0; 320 568); autoresize = W+H | | <UIView: 0x7fbee071daf0; frame = (31.5 247.5; 257 93); autor | | | <UIButton: 0x7fbee050de30; frame = (48.5 53; 160 40); o | | | | <UIButtonLabel: 0x7fbee0773930; frame = (4 6.5; 15 | | | <UILabel: 0x7fbee0610fe0; frame = (4 0; 250 37); text = | | | | <_UILabelContentLayer: 0x6040006336e0> (layer) ·ͣɺදࣔ͞Ε͍ͯΔ6*7JFXͷҰཡΛදࣔ
  14. 43.

    (lldb) po UIApplication.shared.keyWindow!
 .value(forKey: "recursiveDescription")! <UIWindow: 0x7fbee05517b0; frame = (0

    0; 320 568); autoresize = W+H; g | <UIView: 0x7fbee0719bc0; frame = (0 0; 320 568); autoresize = W+H | | <UIView: 0x7fbee071daf0; frame = (31.5 247.5; 257 93); autor | | | <UIButton: 0x7fbee050de30; frame = (48.5 53; 160 40); o | | | | <UIButtonLabel: 0x7fbee0773930; frame = (4 6.5; 15 | | | <UILabel: 0x7fbee0610fe0; frame = (4 0; 250 37); text = | | | | <_UILabelContentLayer: 0x6040006336e0> (layer) දࣔ͞Ε͍ͯΔΫϥεͷܕ΍ϝϞϦΞυϨεΛೖखͰ͖Δ
  15. 46.

    (lldb) po $label <UILabel: 0x7fbee0610fe0; frame = (4 0; 250

    37); text ΠϯελϯεΛऔಘͰ͖͔ͨͲ͏͔֬ೝͯ͠ΈΑ͏
  16. 57.

    struct Example { static func intToString(_ i: Int?) -> String

    { let x: Int! = i return "\(x)" } } ྫ͑͹ɺ੔਺Λਐ਺දهͷจࣈྻ΁
 ม׵͢ΔίʔυΛ࣮૷ͨ͠ͱ͢Δ ྫ͑͹ɺΛҾ਺ʹ͢Ε͹ɺਐ਺
 จࣈྻͰ͋Δ͕ฦ͖ͬͯͯ΄͍͠
  17. 60.

    struct Example { static func intToString(_ i: Int?) -> String

    { let x: Int! = i return "\(x)" } } όάΛݟ͚ͭͨΒ௚લͷ࣮૷Օॴʹ໭Ζ͏
  18. 61.

    struct Example { static func intToString(_ i: Int?) -> String

    { let x: Int = i! return "\(x)" } } Int!ΛจࣈྻԽ͢Δͱ"Optional(...)"ʹ
 ͳͬͯ͠·͏ͷͰɺIntʹͳΔΑ͏मਖ਼͢Δ
  19. 62.

    struct Example { static func intToString(_ i: Int?) -> String

    { let x: Int! = i return "\(x)" } } ࣮૷௚ޙʹσόοάͨ͠ͷͰɺ
 ݪҼՕॴ͕͙ۙ͘͢ʹݟ͔ͭͬͨ
  20. 83.

    ෆ҆ͳίʔυͷྫ struct Example { static func intToString(_ i: Int?) ->

    String { let x: Int! = i return "\(x)" } } ͨͱ͑͹ɺઌ΄Ͳͷ୹͍ίʔυͷதʹ΋
 ෆ҆ͳ৔ॴ͕͋ͬͨ ࢲͷෆ҆ϙΠϯτ
  21. 85.

    w සൟʹ௨ա͢Δ෼ذʢ͜͜ʹόά͕͋Δͱਃ͠༁ͳ͍ʣ if (x < 0)ͷ෼ذͰxʹͲΜͳ΋ͷ͕དྷΔ͔
 Θ͔Βͳ͍ͳΒɺগͳ͘ͱ΋ਖ਼ͱෛͷ྆ํΛࢼ͢ w ෼ذ৚݅ͷڥ໨ x

    < 0ͳΒڥ໨͸ɺ഑ྻ͸ۭͷͱ͖ʹڥ໨ʹͳΓ΍͍͢
 DoubleͳΒ+0 -0 +infinity -infinity nanͳͲ w ཧղͰ͖͍ͯͳ͍ίϯϙʔωϯτͷར༻ ྫ͑͹ɺඪ४ϥΠϒϥϦͷ6*5BCMF7JFX͸࢖͍ํ͕
 ͱͯ΋ෳࡶͳͷͰ͍ͭ΋ؒҧ͑ͯ͠·͏ ࢲͷ࠷ۙͷෆ҆ϙΠϯτ ൃ ද Ͱ ͸ ׂ Ѫ
  22. 98.

    #if DEBUG import Foundation let debugTargets: [String: () -> Void]

    = [ "intToString": { let string = Example.intToString(42) guard string == "48" else { fatalError("intToString(42) ͕ \"42\" Λฦ͞ͳ͔ͬͨ") } }, ] func debugAll() { let targets = Array(debugTargets.values) DispatchQueue.concurrentPerform(iterations: targets.count) { index in targets[index]() } } #endif ಈ࡞֬ೝͷίʔυΛ·ͱΊΔ%JDUJPOBSZΛ࡞੒͢Δ ൃ ද Ͱ ͸ ׂ Ѫ
  23. 99.

    #if DEBUG import Foundation let debugTargets: [String: () -> Void]

    = [ "intToString": { let string = Example.intToString(42) guard string == "42" else { fatalError("intToString(42) ͕ \"42\" Λฦ͞ͳ͔ͬͨ") } }, ] func debugAll() { let targets = Array(debugTargets.values) DispatchQueue.concurrentPerform(iterations: targets.count) { index in targets[index]() } } #endif ಈ࡞֬ೝͷίʔυΛॻ͘ ͜ͷίʔυ͸ɺintToString(42)ͷ
 ݁Ռ͕"42"ʹͳΔ͜ͱΛ֬ೝ͍ͯ͠Δ ൃ ද Ͱ ͸ ׂ Ѫ
  24. 100.

    #if DEBUG import Foundation let debugTargets: [String: () -> Void]

    = [ "intToString": { let string = Example.intToString(42) guard string == "48" else { fatalError("intToString(42) ͕ \"42\" Λฦ͞ͳ͔ͬͨ") } }, ] func debugAll() { let targets = Array(debugTargets.values) DispatchQueue.concurrentPerform(iterations: targets.count) { index in targets[index]() } } #endif ͢΂ͯͷಈ࡞֬ೝίʔυΛ࣮ߦ͢Δ
 EFCVH"MMؔ਺Λ࣮૷͢Δ ൃ ද Ͱ ͸ ׂ Ѫ
  25. 105.

    @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func

    application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: #if DEBUG if ProcessInfo.processInfo.environment["AUTO_DEBUG"] == "1" { debugAll() return true } #endif // ... return true } } "QQ%FMFHBUFʹ؀ڥม਺ͷ෼ذΛ௥Ճ AUTO_DEBUG=1ͷͱ͖ͷΈɺ
 ઌ΄ͲͷdebugAllΛ࣮ߦ ൃ ද Ͱ ͸ ׂ Ѫ
  26. 118.

    import XCTest @testable import IOSDC2018Debugging class ExampleTest: XCTestCase { func

    testIntToString() { let str = Example.intToString(42) XCTAssertEqual(str, "42") } } ඪ४ϥΠϒϥϦΛ࢖͏৔߹ͷ
 ಈ࡞֬ೝͷίʔυ͸͜͏ͳΔ
  27. 119.

    import XCTest @testable import IOSDC2018Debugging class ExampleTest: XCTestCase { func

    testIntToString() { let str = Example.intToString(42) XCTAssertEqual(str, "42") } } ಈ࡞֬ೝͷίʔυͷUBSHFU͸
 ઌ΄Ͳ࡞੒ͨ͠UBSHFUʹ͢Δ
  28. 120.
  29. 121.

    import XCTest @testable import IOSDC2018Debugging class ExampleTest: XCTestCase { func

    testIntToString() { let str = Example.intToString(42) XCTAssertEqual(str, "42") } } ඪ४ϥΠϒϥϦ9$5FTUΛಡΈࠐΉ ΞϓϦͷίʔυ͸ผͷ5BSHFUͳͷͰ
 ࢖͏ࡍʹ͸import͕ඞཁ ී௨ʹimport͢Δͱpublicͷ΋ͷ͔͠
 ݟ͑ͳ͘ͳͬͯ͠·͏͕ɺ@testableΛͭ ͚Δͱinternal·Ͱݟ͑ΔΑ͏ʹͳΔ
  30. 122.

    import XCTest @testable import IOSDC2018Debugging class ExampleTest: XCTestCase { func

    testIntToString() { let str = Example.intToString(42) XCTAssertEqual(str, "42") } } ಈ࡞֬ೝͷίʔυ͸XCTestCaseͱ͍͏
 ΫϥεͰάϧʔϐϯάͰ͖ΔΑ͏ʹͳ͍ͬͯΔ ಈ࡞֬ೝͷίʔυΛॻ͘৔߹ʹ͸ɺ֬ೝର৅ͷ
 Ϋϥε΍ؔ਺୯ҐͰXCTestCaseΛ෼͚Δͱ
 ಈ࡞֬ೝͷ݁Ռ͕Θ͔Γ΍͘͢ͳΔ
  31. 123.

    import XCTest @testable import IOSDC2018Debugging class ExampleTest: XCTestCase { func

    testIntToString() { let str = Example.intToString(42) XCTAssertEqual(str, "42") } } ࣮ࡍͷಈ࡞֬ೝͷίʔυ͸ɺΠϯελϯεϝιουͱ࣮ͯ͠૷͢Δ ͜ͷϝιουͷ໊લ͸ઌ಄ʹtestͱ͚ͭͳ͍ͱ࣮ߦ͞Εͳ͍ͷͰ஫ҙ
  32. 124.

    import XCTest @testable import IOSDC2018Debugging class ExampleTest: XCTestCase { func

    testIntToString() { let str = Example.intToString(42) XCTAssertEqual(str, "42") } } ಈ࡞֬ೝͷର৅Λ࣮ࡍʹ࣮ߦ͢Δ
  33. 125.

    import XCTest @testable import IOSDC2018Debugging class ExampleTest: XCTestCase { func

    testIntToString() { let str = Example.intToString(42) XCTAssertEqual(str, "42") } } ਖ਼ޡ൑ఆ͸XCTAssertEqualͳͲͷؔ਺Λ࢖͏ ͜ͷؔ਺͸ɺͭͷҾ਺͕౳͘͠ͳ͍ͱɺ
 ಈ࡞֬ೝΛࣦഊͱΈͳ͢Α͏ʹͳ͍ͬͯΔ
  34. 133.

    *%ΛݸผͷTUSVDU΁෼͚ͨྫ // BAD: औΓҧ͑ͯ΋ؾ͖ͮͮΒ͍ let userID: Int = 1234 let

    productID: Int = 5678 // GOOD: औΓҧ͑ΔͱίϯύΠϧΤϥʔʹͳΔ let userID = UserID(number: 1234) let productID = ProductID(number: 5678) ൃ ද Ͱ ͸ ׂ Ѫ
  35. 147.

    import UIKit class ExampleViewController: UIViewController { @IBOutlet weak var label:

    UILabel! @IBAction func buttonTapped(_ sender: Any) { APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, error) in guard let `self` = self else { return } if let error = error { dump(error) let alertViewController = UIAlertController( title: "Τϥʔ͕ൃੜ͠·ͨ͠", message: nil, preferredStyle: .alert ) alertViewController.addAction(UIAlertAction(title: "OK", style: .default)) self.present(alertViewController, animated: true) return } self.label.text = "Hello, \(user!.name)!" } } } ಈ࡞֬ೝͷࣗಈԽ͕͔ͳΓ೉͍͠ྫ
  36. 149.

    import UIKit class ExampleViewController: UIViewController { @IBOutlet weak var label:

    UILabel! @IBAction func buttonTapped(_ sender: Any) { APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, error) in guard let `self` = self else { return } if let error = error { dump(error) let alertViewController = UIAlertController( title: "Τϥʔ͕ൃੜ͠·ͨ͠", message: nil, preferredStyle: .alert ) alertViewController.addAction(UIAlertAction(title: "OK", style: .default)) self.present(alertViewController, animated: true) return } self.label.text = "Hello, \(user!.name)!" } } } Ϙλϯ͕λοϓ͞ΕͨΒ
 αʔόͱ௨৴Λ࢝ΊΔ ൃ ද Ͱ ͸ ׂ Ѫ
  37. 150.

    import UIKit class ExampleViewController: UIViewController { @IBOutlet weak var label:

    UILabel! @IBAction func buttonTapped(_ sender: Any) { APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, error) in guard let `self` = self else { return } if let error = error { dump(error) let alertViewController = UIAlertController( title: "Τϥʔ͕ൃੜ͠·ͨ͠", message: nil, preferredStyle: .alert ) alertViewController.addAction(UIAlertAction(title: "OK", style: .default)) self.present(alertViewController, animated: true) return } self.label.text = "Hello, \(user!.name)!" } } } αʔόʔͱͷ௨৴͕
 Τϥʔʹͳͬͨ৔߹͸
 6*"MFSU$POUSPMMFSͰදࣔ ൃ ද Ͱ ͸ ׂ Ѫ
  38. 151.

    import UIKit class ExampleViewController: UIViewController { @IBOutlet weak var label:

    UILabel! @IBAction func buttonTapped(_ sender: Any) { APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, error) in guard let `self` = self else { return } if let error = error { dump(error) let alertViewController = UIAlertController( title: "Τϥʔ͕ൃੜ͠·ͨ͠", message: nil, preferredStyle: .alert ) alertViewController.addAction(UIAlertAction(title: "OK", style: .default)) self.present(alertViewController, animated: true) return } self.label.text = "Hello, \(user!.name)!" } } } αʔόʔ͔ΒϢʔβʔ໊͕
 ਖ਼ৗʹฦ͖ͬͯͨΒදࣔ ൃ ද Ͱ ͸ ׂ Ѫ
  39. 152.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let viewController = UIStoryboard( name: "ExampleViewController", bundle: Bundle(for: ExampleViewController.self) ).instantiateInitialViewController() as! ExampleViewController let dummySender = NSObject() viewController.buttonTapped(dummySender) XCTAssertTrue(viewController.label?.text?.contains("Kuniwak")) } } ͱΓ͋͑ͣಈ࡞֬ೝͷίʔυΛॻ͍ͯΈͨ ൃ ද Ͱ ͸ ׂ Ѫ
  40. 153.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let viewController = UIStoryboard( name: "ExampleViewController", bundle: Bundle(for: ExampleViewController.self) ).instantiateInitialViewController() as! ExampleViewController let dummySender = NSObject() viewController.buttonTapped(dummySender) XCTAssertTrue(viewController.label!.text!.contains("Kuniwak")) } } 4UPSZCPBSE͔Β7JFX$POUSPMMFSΛॳظԽ ൃ ද Ͱ ͸ ׂ Ѫ
  41. 154.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let viewController = UIStoryboard( name: "ExampleViewController", bundle: Bundle(for: ExampleViewController.self) ).instantiateInitialViewController() as! ExampleViewController let dummySender = NSObject() viewController.buttonTapped(dummySender) XCTAssertTrue(viewController.label!.text!.contains("Kuniwak")) } } @IBActionΛ࣮ߦͯ͠ϘλϯλοϓΛ࠶ݱ ൃ ද Ͱ ͸ ׂ Ѫ
  42. 155.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let viewController = UIStoryboard( name: "ExampleViewController", bundle: Bundle(for: ExampleViewController.self) ).instantiateInitialViewController() as! ExampleViewController let dummySender = NSObject() viewController.buttonTapped(dummySender) XCTAssertTrue(viewController.label!.text!.contains("Kuniwak")) } } UILabel͕Ϣʔβʔ໊ʹมΘͬͨ͜ͱΛ֬ೝ ൃ ද Ͱ ͸ ׂ Ѫ
  43. 156.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let viewController = UIStoryboard( name: "ExampleViewController", bundle: Bundle(for: ExampleViewController.self) ).instantiateInitialViewController() as! ExampleViewController let dummySender = NSObject() viewController.buttonTapped(dummySender) XCTAssertTrue(viewController.label!.text!.contains("Kuniwak")) } } viewController.label͕nil ൃ ද Ͱ ͸ ׂ Ѫ
  44. 157.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let viewController = UIStoryboard( name: "ExampleViewController", bundle: Bundle(for: ExampleViewController.self) ).instantiateInitialViewController() as! ExampleViewController let dummySender = NSObject() viewController.buttonTapped(dummySender) XCTAssertTrue(viewController.label!.text!.contains("Kuniwak")) } } viewDidLoadΛ଴ͬͯͳ͍ͷͰɺ
 ·ͩUILabel͕ଘࡏ͍ͯ͠ͳ͍ ݪҼɿ ൃ ද Ͱ ͸ ׂ Ѫ
  45. 158.

    class ExampleViewController: UIViewController { private var actions: ExampleViewsAction? @IBOutlet weak

    var label: UILabel! override func viewDidLoad() { super.viewDidLoad() self.actions = ExampleViewsAction( label: self.label ) } @IBAction func buttonTapped(_ sender: Any) { self.actions?.buttonDidTapped() } } मਖ਼ޙͷ7JFX$POUSPMMFS ൃ ද Ͱ ͸ ׂ Ѫ
  46. 159.

    class ExampleViewController: UIViewController { private var actions: ExampleViewsAction? @IBOutlet weak

    var label: UILabel! override func viewDidLoad() { super.viewDidLoad() self.actions = ExampleViewsAction( label: self.label ) } @IBAction func buttonTapped(_ sender: Any) { self.actions?.buttonDidTapped() } } viewDidLoadҎ߱ͷॲཧΛผͷΫϥεʹ੾Γग़͢ ʢͳͥ͜͏͢Δͷ͔͸΋͏গ͠ޙͰΘ͔Γ·͢ʣ ॏཁͳͷ͸ɺviewDidLoadͰॳظԽ͞Εͨ
 UILabelΛ੾Γग़ͨ͠Ϋϥεʹ౉͓ͯ͘͜͠ͱ ൃ ද Ͱ ͸ ׂ Ѫ
  47. 160.

    class ExampleViewController: UIViewController { private var actions: ExampleViewsAction? @IBOutlet weak

    var label: UILabel! override func viewDidLoad() { super.viewDidLoad() self.actions = ExampleViewsAction( label: self.label ) } @IBAction func buttonTapped(_ sender: Any) { self.actions?.buttonDidTapped() } } Ϙλϯͷλοϓॲཧ͸
 @IBActionΛԣྲྀ͢͠Ε͹0, ൃ ද Ͱ ͸ ׂ Ѫ
  48. 161.

    import UIKit class ExampleViewsAction { private let label: UILabel init(label:

    UILabel) { self.label = label } func buttonDidTapped() { APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, error) in guard let `self` = self else { return } if let error = error { // লུ self.present(alertViewController, animated: true) return } self.label.text = "Hello, \(user!.name)!" } } } 7JFX$POUSPMMFS͔Β෼཭͞Εͨίʔυ ൃ ද Ͱ ͸ ׂ Ѫ
  49. 162.

    import UIKit class ExampleViewsAction { private let label: UILabel init(label:

    UILabel) { self.label = label } func buttonDidTapped() { APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, error) in guard let `self` = self else { return } if let error = error { // লུ self.present(alertViewController, animated: true) return } self.label.text = "Hello, \(user!.name)!" } } } ஫ɿ͜ͷ໊લ͸ద౰ͳͷͰɺ࣮ࡍ͸֤ʑͷ
 ΞʔΩςΫνϟʹԊ໋໊ͬͨʹ͍ͯͩ͘͠͞ ൃ ද Ͱ ͸ ׂ Ѫ
  50. 163.

    import UIKit class ExampleViewsAction { private let label: UILabel init(label:

    UILabel) { self.label = label } func buttonDidTapped() { APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, error) in guard let `self` = self else { return } if let error = error { // লུ self.present(alertViewController, animated: true) return } self.label.text = "Hello, \(user!.name)!" } } } 7JFX$POUSPMMFS͔Β౉͞ΕͨUILabel ൃ ද Ͱ ͸ ׂ Ѫ
  51. 164.

    import UIKit class ExampleViewsAction { private let label: UILabel init(label:

    UILabel) { self.label = label } func buttonDidTapped() { APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, error) in guard let `self` = self else { return } if let error = error { // লུ self.present(alertViewController, animated: true) return } self.label.text = "Hello, \(user!.name)!" } } } 7JFX$POUSPMMFSͷॲཧΛͦͷ··Ҡಈ ൃ ද Ͱ ͸ ׂ Ѫ
  52. 165.

    import UIKit class ExampleViewsAction { private let label: UILabel init(label:

    UILabel) { self.label = label } func buttonDidTapped() { APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, error) in guard let `self` = self else { return } if let error = error { // লུ self.present(alertViewController, animated: true) return } self.label.text = "Hello, \(user!.name)!" } } } self.present͕ଘࡏ͠ͳ͍ ʢ͜Ε͸গ͠ޙͰमਖ਼͢Δʣ ൃ ද Ͱ ͸ ׂ Ѫ
  53. 166.

    मਖ਼ޙͷಈ࡞֬ೝίʔυ import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest:

    XCTestCase { func testMyNameIsVisible() { let label = UILabel() let actions = ExampleViewsAction(label: label) actions.buttonDidTapped() XCTAssertTrue(label.text?.contains("Kuniwak")) } } ൃ ද Ͱ ͸ ׂ Ѫ
  54. 167.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let label = UILabel() let actions = ExampleViewsAction(label: label) actions.buttonDidTapped() XCTAssertTrue(label.text?.contains("Kuniwak")) } } 7JFX$POUSPMMFSͰ͸ͳ͘ɺ
 ੾Γग़ͨ͠ΫϥεͷํΛ
 ಈ࡞֬ೝ͢ΔΑ͏ʹ͢Δ ൃ ද Ͱ ͸ ׂ Ѫ
  55. 168.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let label = UILabel() let actions = ExampleViewsAction(label: label) actions.buttonDidTapped() XCTAssertTrue(label.text?.contains("Kuniwak")) } } 7JFX$POUSPMMFS͔ΒUILabel͕౉͞ΕΔͷΛ࠶ݱ ൃ ද Ͱ ͸ ׂ Ѫ
  56. 169.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let label = UILabel() let actions = ExampleViewsAction(label: label) actions.buttonDidTapped() XCTAssertTrue(label.text?.contains("Kuniwak")) } } @IBActionͰλοϓΛ࠶ݱ͢Δ෦෼͸ ੾Γग़ͨ͠Ϋϥεͷํͷϝιουͷ࣮ߦͰ୅ସ ൃ ද Ͱ ͸ ׂ Ѫ
  57. 170.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let label = UILabel() let actions = ExampleViewsAction(label: label) actions.buttonDidTapped() XCTAssertTrue(label.text?.contains("Kuniwak")) } } ઌ΄Ͳ౉ͨ͠UILabelͷ಺༰͕
 มΘ͍ͬͯΔ͔͔֬ΊΒΕ͍ͯΔ ͜͏͢Δ͜ͱͰUILabel͕·ͩ
 ଘࡏ͠ͳ͍໰୊ΛղܾͰ͖Δ ൃ ද Ͱ ͸ ׂ Ѫ
  58. 171.

    import UIKit class ExampleViewsAction { private let label: UILabel init(label:

    UILabel) { self.label = label } func buttonDidTapped() { APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, error) in guard let `self` = self else { return } if let error = error { // লུ self.present(alertViewController, animated: true) return } self.label.text = "Hello, \(user!.name)!" } } } self.present͕ଘࡏ͠ͳ͍ͷͰ
 ·ͩϏϧυ͕௨Βͳ͍ 7JFX$POUSPMMFS͔Β෼཭͞Εͨίʔυ ൃ ද Ͱ ͸ ׂ Ѫ
  59. 172.

    import UIKit class ModalPresenter { private weak var viewController: UIViewController?

    init(willPresentOn viewController: UIViewController) { self.viewController = viewController } func present(_ viewController: UIViewController, animated: Bool) { self.viewController?.present(viewController, animated: animated) } } self.present͕ଘࡏ͠ͳ͍໰୊ͷղܾ ൃ ද Ͱ ͸ ׂ Ѫ
  60. 173.

    import UIKit class ModalPresenter { private weak var viewController: UIViewController?

    init(willPresentOn viewController: UIViewController) { self.viewController = viewController } func present(_ viewController: UIViewController, animated: Bool) { self.viewController?.present(viewController, animated: animated) } } 7JFX$POUSPMMFSͷpresentΛ
 ผͷΫϥεͰ΋ୟ͚ΔΑ͏ʹ͢ΔΫϥε ൃ ද Ͱ ͸ ׂ Ѫ
  61. 174.

    import UIKit class ModalPresenter { private weak var viewController: UIViewController?

    init(willPresentOn viewController: UIViewController) { self.viewController = viewController } func present(_ viewController: UIViewController, animated: Bool) { self.viewController?.present(viewController, animated: animated) } } presentʹ࢖͏7JFX$POUSPMMFSΛ಺෦ʹอ࣋͢Δ
 ʢϝϞϦϦʔΫΛආ͚ΔͨΊʹweakʹ͢Δʣ ൃ ද Ͱ ͸ ׂ Ѫ
  62. 175.

    import UIKit class ModalPresenter { private weak var viewController: UIViewController?

    init(willPresentOn viewController: UIViewController) { self.viewController = viewController } func present(_ viewController: UIViewController, animated: Bool) { self.viewController?.present(viewController, animated: animated) } } ͜ͷΫϥεͷpresentΛ࣮ߦ͢Δͱɺอ͍࣋ͯͨ͠
 7JFX$POUSPMMFSͷpresent͕࣮ߦ͞ΕΔ ൃ ද Ͱ ͸ ׂ Ѫ
  63. 176.

    import UIKit class ExampleViewsAction { private let label: UILabel private

    let modalPresenter: ModalPresenter init(label: UILabel, modalPresenter: ModalPresenter) { self.label = label self.modalPresenter = modalPresenter } func buttonDidTapped() { APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, error) in guard let `self` = self else { return } if let error = error { // লུ self.modalPresenter.present(alertViewController, animated: true) return } self.label.text = "Hello, \(user!.name)!" } } } मਖ਼ޙͷ7JFX$POUSPMMFS͔Β෼཭͞Εͨίʔυ ൃ ද Ͱ ͸ ׂ Ѫ
  64. 177.

    import UIKit class ExampleViewsAction { private let label: UILabel private

    let modalPresenter: ModalPresenter init(label: UILabel, modalPresenter: ModalPresenter) { self.label = label self.modalPresenter = modalPresenter } func buttonDidTapped() { APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, error) in guard let `self` = self else { return } if let error = error { // লུ self.modalPresenter.present(alertViewController, animated: true) return } self.label.text = "Hello, \(user!.name)!" } } } 7JFX$POUSPMMFS͔ΒUILabelͱҰॹʹ
 ઌ΄ͲͷModalPresenterΛड͚औΔ ൃ ද Ͱ ͸ ׂ Ѫ
  65. 178.

    import UIKit class ExampleViewsAction { private let label: UILabel private

    let modalPresenter: ModalPresenter init(label: UILabel, modalPresenter: ModalPresenter) { self.label = label self.modalPresenter = modalPresenter } func buttonDidTapped() { APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, error) in guard let `self` = self else { return } if let error = error { // লུ self.modalPresenter.present(alertViewController, animated: true) return } self.label.text = "Hello, \(user!.name)!" } } } self.presentΛModalPresenterͷ
 presentͷݺͼग़͠΁ม͑Δ ൃ ද Ͱ ͸ ׂ Ѫ
  66. 179.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter( willPresentOn: UIViewController() ) let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter ) actions.buttonDidTapped() XCTAssertTrue(label.text?.contains("Kuniwak")) } } मਖ਼ޙͷಈ࡞֬ೝίʔυ ൃ ද Ͱ ͸ ׂ Ѫ
  67. 180.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter( willPresentOn: UIViewController() ) let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter ) actions.buttonDidTapped() XCTAssertTrue(label.text?.contains("Kuniwak")) } } Ϗϧυ͕௨Γ͑͢͞Ε͹͍͍ͷͰɺ
 ద౰ͳModalPresenterΛ࡞੒͢Δ ൃ ද Ͱ ͸ ׂ Ѫ
  68. 181.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter( willPresentOn: UIViewController() ) let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter ) actions.buttonDidTapped() XCTAssertTrue(label.text?.contains("Kuniwak")) } } 7JFX$POUSPMMFS͔Β
 ੾Γग़ͨ͠Ϋϥε΁౉͢ ൃ ද Ͱ ͸ ׂ Ѫ
  69. 182.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter( willPresentOn: UIViewController() ) let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter ) actions.buttonDidTapped() XCTAssertTrue(label.text?.contains("Kuniwak")) } } Ϗϧυ͕௨ΔΑ͏ʹͳͬͨ ൃ ද Ͱ ͸ ׂ Ѫ
  70. 183.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter( willPresentOn: UIViewController() ) let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter ) actions.buttonDidTapped() XCTAssertTrue(label.text?.contains("Kuniwak")) } } XCTAssertEqual failed: ("nil") is
 not equal to ("Optional(true)") ൃ ද Ͱ ͸ ׂ Ѫ
  71. 184.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter( willPresentOn: UIViewController() ) let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter ) actions.buttonDidTapped() XCTAssertTrue(label.text?.contains("Kuniwak")) } } ͜ͷ࣌ͷlabel.textΛݟΔͱ
 ॳظ஋ͷ··ʹͳ͍ͬͯͨ Ϣʔβʔ໊͕·ͩදࣔ͞Ε͍ͯͳ͍Α͏ͩ ൃ ද Ͱ ͸ ׂ Ѫ
  72. 185.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter( willPresentOn: UIViewController() ) let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter ) actions.buttonDidTapped() XCTAssertTrue(label.text?.contains("Kuniwak")) } } ݪҼɿ αʔόʔ͔Βͷฦ৴͕·ͩ
 ฦ͖͍ͬͯͯͳ͍ͷͰɺ
 UILabelΛߋ৽Ͱ͖͍ͯͳ͍ ൃ ද Ͱ ͸ ׂ Ѫ
  73. 186.

    Ұൠతʹɺ"1*͕ฦ͖ͬͯͨޙͷ7JFXͷ
 ߋ৽ͷ׬ྃΛݕ஌͢Δͷ͸೉͍͠ ྫ͑͹ɺ࣍ͷίʔυͰ7JFXͷߋ৽͕
 ׬ྃ͢Δͷ͸"ͱ#ͷͲͪΒʁ if isAdminMode { callAdminAPI { [weak

    self] (admin, error) in guard let `self` = self else { return } self.label.color = admin != nil ? .red : .blue } } callUserAPI { [weak self] (user, error) in guard let `self` = self else { return } self.label.text = user.name } " # ൃ ද Ͱ ͸ ׂ Ѫ
  74. 187.

    ਖ਼ղ͸ɺʮ"ͱ#ͷͲͪΒ΋͋Γ͑Δʯ ͭͷ"1*ͷฦͬͯ͘ΔλΠϛϯάʹΑͬͯ
 "ͷͱ͖΋͋Ε͹#ͷͱ͖΋͋Δ if isAdminMode { callAdminAPI { [weak self]

    (admin, error) in guard let `self` = self else { return } self.label.color = admin != nil ? .red : .blue } } callUserAPI { [weak self] (user, error) in guard let `self` = self else { return } self.label.text = user.name } callAdminAPI͕
 ޙʹ׬ྃ͢Ε͹" callUserAPI͕
 ޙʹ׬ྃ͢Ε͹# ൃ ද Ͱ ͸ ׂ Ѫ
  75. 188.

    ݟͨ໨ͷߋ৽Λ"1*ͳͲͷཪଆͷࣄ৘͔Β੾Γ཭͢ͱղܾͰ͖Δ  ݟͨ໨Λ࢘ΔίϯϙʔωϯτʹදࣔΛ
 ҰׅͰ൓ө͢Δ͚ͩͷϝιουΛੜ΍͢  "1*ݺͼग़͠౳Ͱ͢΂ͯͷ৘ใ͕ἧͬͨΒ
 ϝιουΛݺͼग़͢ઃܭن໿ʹ͢Δ ͜͏͍͏ͱ͖͸ func apply(userName:

    String, isAdmin: Bool) { self.label.text = userName self.label.backgroundColor = isAdmin ? .red : .black } // API ͷ྆ํ͕ฦ͖͔ͬͯͯΒදࣔΛ൓ө͢Δ myView.apply(userName: "Kuniwak", isAdmin: false) ൃ ද Ͱ ͸ ׂ Ѫ
  76. 189.

    ݟͨ໨ͱཪଆͷ෼཭ͷͨΊͷ४උ protocol ExampleModelDelegate: class { func apply(state: ExampleModelState) } enum

    ExampleModelState: Equatable { case success(userName: String) case failure(error: ExampleModelUpdateError) } enum ExampleModelUpdateError: Error, Equatable { case unspecified(debugInfo: String) } ൃ ද Ͱ ͸ ׂ Ѫ
  77. 190.

    protocol ExampleModelDelegate: class { func apply(state: ExampleModelState) } enum ExampleModelState:

    Equatable { case success(userName: String) case failure(error: ExampleModelUpdateError) } enum ExampleModelUpdateError: Error, Equatable { case unspecified(debugInfo: String) } ཪଆͷ࢓ࣄͷ४උ͕੔ͬͨͱ͖ʹୟ͍ͯ΋Β͏
 ݟͨ໨ͷҰׅ൓өϝιου ൃ ද Ͱ ͸ ׂ Ѫ
  78. 191.

    protocol ExampleModelDelegate: class { func apply(state: ExampleModelState) } enum ExampleModelState:

    Equatable { case success(userName: String) case failure(error: ExampleModelUpdateError) } enum ExampleModelUpdateError: Error, Equatable { case unspecified(debugInfo: String) } ཪଆͷ࠷ऴతͳ݁Ռ͸ɺ w ੒ޭͯ͠userName w ࣦഊͯ͠error ͷͲͪΒ͔ͳͷͰɺFOVNͰදݱ ൃ ද Ͱ ͸ ׂ Ѫ
  79. 192.

    protocol ExampleModelDelegate: class { func apply(state: ExampleModelState) } enum ExampleModelState:

    Equatable { case success(userName: String) case failure(error: ExampleModelUpdateError) } enum ExampleModelUpdateError: Error, Equatable { case unspecified(debugInfo: String) } ཪଆͷ࢓ࣄͷ݁Ռ͕Equatableͩͱ
 ޙͰศརʹͳΔͷͰɺEquatableͳ
 ΤϥʔͷܕΛ࡞੒͓ͯ͘͠ ൃ ද Ͱ ͸ ׂ Ѫ
  80. 193.

    ཪଆͷ࢓ࣄΛ͓͜ͳ͏Ϋϥε class ExampleModel { weak var delegate: ExampleModelDelegate? func update()

    { APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, erro guard let `self` = self else { return } if let error = error { self.delegate?.apply(state: .failure(error: .unspecified(debugInfo: "\(error)") } else if let user = user { self.delegate?.apply(state: .success(userName: user.name)) } } } } ൃ ද Ͱ ͸ ׂ Ѫ
  81. 194.

    class ExampleModel { weak var delegate: ExampleModelDelegate? func update() {

    APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, erro guard let `self` = self else { return } if let error = error { self.delegate?.apply(state: .failure(error: .unspecified(debugInfo: "\(error)") } else if let user = user { self.delegate?.apply(state: .success(userName: user.name)) } } } } ஫ɿ͜ͷ໊લ͸ద౰ͳͷͰɺ࣮ࡍ͸֤ʑͷ
 ΞʔΩςΫνϟʹԊ໋໊ͬͨʹ͍ͯͩ͘͠͞ ൃ ද Ͱ ͸ ׂ Ѫ
  82. 195.

    class ExampleModel { weak var delegate: ExampleModelDelegate? func update() {

    APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, erro guard let `self` = self else { return } if let error = error { self.delegate?.apply(state: .failure(error: .unspecified(debugInfo: "\(error)") } else if let user = user { self.delegate?.apply(state: .success(userName: user.name)) } } } } ཪଆͷ४උ͕੔ͬͨΒ௨஌͢Δઌͷ%FMFHBUF ͜ͷྫͰ͸ɺ7JFX$POUSPMMFS͔Β੾Γग़ͨ͠
 Ϋϥε͕͜ͷ%FMFHBUFΛ࣮૷͢Δ͜ͱʹͳΔ ൃ ද Ͱ ͸ ׂ Ѫ
  83. 196.

    class ExampleModel { weak var delegate: ExampleModelDelegate? func update() {

    APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, erro guard let `self` = self else { return } if let error = error { self.delegate?.apply(state: .failure(error: .unspecified(debugInfo: "\(error)") } else if let user = user { self.delegate?.apply(state: .success(userName: user.name)) } } } } "1*ͷݺͼग़͠͸ɺ͜ͷΫϥεͷ
 updateϝιουͷ࣮ߦͰ͸͡ΊΔ ൃ ද Ͱ ͸ ׂ Ѫ
  84. 197.

    class ExampleModel { weak var delegate: ExampleModelDelegate? func update() {

    APIClient.shared.get(type: User.self, url: URLs.getSomething) { [weak self] (user, erro guard let `self` = self else { return } if let error = error { self.delegate?.apply(state: .failure(error: .unspecified(debugInfo: "\(error)") } else if let user = user { self.delegate?.apply(state: .success(userName: user.name)) } } } } "1*ͷ݁Ռ͕ࣦഊͷΑ͏ͳΒ.failure Λ %FMFHBUF΁௨஌͠ɺ੒ޭ͍ͯ͠ΔΑ͏
 ͳΒ .successΛ%FMFHBUF΁௨஌͢Δ ൃ ද Ͱ ͸ ׂ Ѫ
  85. 198.

    7JFX$POUSPMMFS͔Β෼཭ͨ͠ΫϥεͷFYUFOTJPO extension ExampleViewsAction: ExampleModelDelegate { func apply(state: ExampleModelState) { switch

    state { case .failure(let error): dump(error) let alertViewController = UIAlertController( title: "Τϥʔ͕ൃੜ͠·ͨ͠", message: nil, preferredStyle: .alert ) alertViewController.addAction(UIAlertAction(title: "OK", style: .default)) self.modalPresenter.present(alertViewController, animated: true) case .success(let userName): self.label.text = "Hello, \(userName)!" } } } ൃ ද Ͱ ͸ ׂ Ѫ
  86. 199.

    extension ExampleViewsAction: ExampleModelDelegate { func apply(state: ExampleModelState) { switch state

    { case .failure(let error): dump(error) let alertViewController = UIAlertController( title: "Τϥʔ͕ൃੜ͠·ͨ͠", message: nil, preferredStyle: .alert ) alertViewController.addAction(UIAlertAction(title: "OK", style: .default)) self.modalPresenter.present(alertViewController, animated: true) case .success(let userName): self.label.text = "Hello, \(userName)!" } } } ઌ΄Ͳͷ%FMFHBUFΛ࣮૷ͤ͞Δ ൃ ද Ͱ ͸ ׂ Ѫ
  87. 200.

    extension ExampleViewsAction: ExampleModelDelegate { func apply(state: ExampleModelState) { switch state

    { case .failure(let error): dump(error) let alertViewController = UIAlertController( title: "Τϥʔ͕ൃੜ͠·ͨ͠", message: nil, preferredStyle: .alert ) alertViewController.addAction(UIAlertAction(title: "OK", style: .default)) self.modalPresenter.present(alertViewController, animated: true) case .success(let userName): self.label.text = "Hello, \(userName)!" } } } දࣔͷҰׅߋ৽ϝιουΛ࣮૷
 ʢ಺༰͸मਖ਼લͱಉ͡ʣ ൃ ද Ͱ ͸ ׂ Ѫ
  88. 201.

    class ExampleViewsAction { private let label: UILabel private let modalPresenter:

    ModalPresenter private let model: ExampleModel init(label: UILabel, modalPresenter: ModalPresenter, model: ExampleModel) { self.label = label self.modalPresenter = modalPresenter self.model = model } func buttonDidTapped() { self.model.update() } } 7JFX$POUSPMMFS͔Β෼཭ͨ͠Ϋϥε ൃ ද Ͱ ͸ ׂ Ѫ
  89. 202.

    class ExampleViewsAction { private let label: UILabel private let modalPresenter:

    ModalPresenter private let model: ExampleModel init(label: UILabel, modalPresenter: ModalPresenter, model: ExampleModel) { self.label = label self.modalPresenter = modalPresenter self.model = model } func buttonDidTapped() { self.model.update() } } ཪଆͷ࢓ࣄΫϥεͷ࡞੒Λ͜ͷதͰ΍ͬͯ΋͍͍ ͕ɺ7JFX$POUSPMMFS͔Β౉ͯ͠΋Β͏ͱɺͷͪ ͷָͪʹͳΔ ൃ ද Ͱ ͸ ׂ Ѫ
  90. 203.

    class ExampleViewsAction { private let label: UILabel private let modalPresenter:

    ModalPresenter private let model: ExampleModel init(label: UILabel, modalPresenter: ModalPresenter, model: ExampleModel) { self.label = label self.modalPresenter = modalPresenter self.model = model } func buttonDidTapped() { self.model.update() } } ϘλϯͷλοϓͰཪଆͷ࢓ࣄΛ࢝ΊΔ ൃ ද Ͱ ͸ ׂ Ѫ
  91. 204.

    मਖ਼ޙͷ7JFX$POUSPMMFS import UIKit class ExampleViewController: UIViewController { // লུ override

    func viewDidLoad() { super.viewDidLoad() self.actions = ExampleViewsAction( label: self.label, modalPresenter: ModalPresenter(willPresentOn: self), model: ExampleModel() ) } } ൃ ද Ͱ ͸ ׂ Ѫ
  92. 205.

    import UIKit class ExampleViewController: UIViewController { // লུ override func

    viewDidLoad() { super.viewDidLoad() self.actions = ExampleViewsAction( label: self.label, modalPresenter: ModalPresenter(willPresentOn: self), model: ExampleModel() ) } } UILabelͳͲΛ౉͍ͭ͢Ͱʹཪଆͷ
 ࢓ࣄΫϥε΋࡞੒ͯ͠౉͓ͯ͘͠ ൃ ද Ͱ ͸ ׂ Ѫ
  93. 206.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter(willPresentOn: UIViewControlle let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter, model: ExampleModel() ) actions.apply(state: .success(userName: "Kuniwak")) XCTAssertTrue(label.text?.contains("Kuniwak")) } } मਖ਼ޙͷಈ࡞֬ೝίʔυ ൃ ද Ͱ ͸ ׂ Ѫ
  94. 207.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter(willPresentOn: UIViewControlle let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter, model: ExampleModel() ) actions.apply(state: .success(userName: "Kuniwak")) XCTAssertTrue(label.text?.contains("Kuniwak")) } } Ϙλϯͷλοϓͷ࠶ݱͰ͸ͳ͘ɺ
 ௚઀ݟͨ໨ͷ൓өϝιουΛୟ͘ ൃ ද Ͱ ͸ ׂ Ѫ
  95. 208.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter(willPresentOn: UIViewControlle let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter, model: ExampleModel() ) actions.apply(state: .success(userName: "Kuniwak")) XCTAssertTrue(label.text?.contains("Kuniwak")) } } ͔͠͠ɺ͜͏ͯ͠͠·͏ͱϘλϯΛλοϓͯ͠
 ද͕ࣔߋ৽͞ΕΔ͜ͱͷ֬ೝʹͳΒͳ͍ͷͰ͸ʁ
 ͱ͍͏ෆ҆͸࣍ͷͭͷํ๏Ͱ؇࿨Ͱ͖Δɿ w Ϙλϯ͕λοϓ͞ΕͨΒɺཪͷ࢓ࣄΫϥε͕
 ݺ͹ΕΔ͜ͱΛ֬ೝ͢ΔίʔυΛ௥Ճ w ཪͷ࢓ࣄΫϥε͕ݺ͹ΕͨΒɺඞͣ׬ྃ͢Δ
 ͜ͱΛ֬ೝ͢ΔίʔυΛ௥Ճ ͦΕͧΕͷৄࡉ͸εϥΠυͷຕ਺ͷࣄ৘ͰׂѪ͢Δ͕ɺ Ҏ߱ͷεϥΠυͷ஌͚ࣝͩͰ࣮૷Ͱ͖Δ ൃ ද Ͱ ͸ ׂ Ѫ
  96. 209.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter(willPresentOn: UIViewControlle let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter, model: ExampleModel() ) actions.apply(state: .success(userName: "Kuniwak")) XCTAssertTrue(label.text?.contains("Kuniwak")) } } Ͱ͸ಈ࡞֬ೝΛ࣮ߦͯ͠ΈΑ͏ ൃ ද Ͱ ͸ ׂ Ѫ
  97. 210.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter(willPresentOn: UIViewControlle let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter, model: ExampleModel() ) actions.apply(state: .success(userName: "Kuniwak")) XCTAssertTrue(label.text?.contains("Kuniwak")) } } ͜͜·Ͱ͖ͯ΍ͬͱ੒ޭ͠·͢ ൃ ද Ͱ ͸ ׂ Ѫ
  98. 211.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter(willPresentOn: UIViewControlle let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter, model: ExampleModel() ) actions.apply(state: .success(userName: "Kuniwak")) XCTAssertTrue(label.text?.contains("Kuniwak")) } } ͔͠͠ɺ·ͩ"1*ݺͼग़͕͠Τϥʔʹ ͳͬͨ৔߹ͷಈ࡞֬ೝΛͰ͖͍ͯͳ͍ʜ ൃ ද Ͱ ͸ ׂ Ѫ
  99. 212.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter(willPresentOn: UIViewControlle let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter, model: ExampleModel() ) actions.apply(state: .success(userName: "Kuniwak")) XCTAssertTrue(label.text?.contains("Kuniwak")) } } ͜Ε΋ίʔυͰಈ࡞֬ೝͰ͖Δ͕ɺ
 εϥΠυͷຕ਺ͷ౎߹ͰׂѪ͢Δ ͜ͷ࡞ۀͷώϯτΛॻ͍͓ͯ͘ɿ w "1*ͷ݁ՌΛಈ࡞֬ೝίʔυ͔Β
 ࢦఆͰ͖ΔΑ͏ʹ͢Δ ‎ *OQVU0CKFDU*OKFDUJPO w .PEBM1SFTFOUFSͷϝιουݺͼग़͠Λ
 ه࿥͢ΔِͷΦϒδΣΫτΛ࡞੒͢Δ ‎ 0VUQVU0CKFDU*OKFDUJPO ͦΕͧΕͷৄࡉ͸ɺεϥΠυͷޙํͰ
 ঺հ͢ΔʮJ04Ͱςετ༰қͳઃܭΛ
 ࣮ݱ͢ΔͨΊͷσβΠϯύλʔϯʯͰ
 αϯϓϧίʔυͱ߹Θͤͯղઆ͍ͯ͠Δ ൃ ද Ͱ ͸ ׂ Ѫ
  100. 213.

    import XCTest import UIKit @testable import IOSDC2018Debugging class ExampleViewControllerTest: XCTestCase

    { func testMyNameIsVisible() { let dummyModalPresenter = ModalPresenter(willPresentOn: UIViewControlle let label = UILabel() let actions = ExampleViewsAction( label: label, modalPresenter: dummyModalPresenter, model: ExampleModel() ) actions.apply(state: .success(userName: "Kuniwak")) XCTAssertTrue(label.text?.contains("Kuniwak")) } } ͜Ε·Ͱͷ࡞ۀͰ΍ͬͨ͜ͱɿ w 7JFX$POUSPMMFSͷॲཧΛผ΁੾Γग़ͨ͠ w ผͷΫϥεͰUIViewController.presentΛ
 ࢖͑ΔΑ͏ʹͨ͠ w ݟͨ໨ͷҰׅ൓өΛίʔυ͔Β
 ࣮ߦͰ͖ΔΑ͏ʹͨ͠ ࣗಈͰͷಈ࡞֬ೝʹඞཁͳࡉ͔ͳ஌͕ࣝ
 ͱͯ΋ଟ͍͜ͱ͕Θ͔Δ ൃ ද Ͱ ͸ ׂ Ѫ
  101. 215.

    ։ൃ଎౓ͷ૿Ճྔ ࣌ؒܦա        --%#ͷ


    ׆༻ มߋ௚ޙͷ
 σόοά ෆ҆ۦಈ
 νΣοΫ ಈ࡞֬ೝͷ
 ࣗಈԽ ؒҧ͑ʹ͍͘
 ઃܭ
  102. 217.

    ։ൃ଎౓ͷ૿Ճྔ ࣌ؒܦա --%#ͷ
 ׆༻ มߋ௚ޙͷ
 σόοά ෆ҆ۦಈ
 νΣοΫ ಈ࡞֬ೝͷ
 ࣗಈԽ

    ؒҧ͑ʹ͍͘
 ઃܭ ͋ΕʁࣗಈԽ͕೉͍ͧ͠ʜʁ
 Ͱ΋ηϧϑνΣοΫͪΌΜͱ
 ΍ͬͯΔ͔Β͕͔͔࣌ؒΔʜ  
  103. 218.

    ։ൃ଎౓ͷ૿Ճྔ ࣌ؒܦա --%#ͷ
 ׆༻ มߋ௚ޙͷ
 σόοά ෆ҆ۦಈ
 νΣοΫ ಈ࡞֬ೝͷ
 ࣗಈԽ

    ؒҧ͑ʹ͍͘
 ઃܭ ΋͏ͪΐͬͱؤுͬͯΈ͚ͨͲɺ
 ͕͔͔࣌ؒΓ͗͢Δʜʜ  
  104. 219.

    ։ൃ଎౓ͷ૿Ճྔ ࣌ؒܦա --%#ͷ
 ׆༻ มߋ௚ޙͷ
 σόοά ෆ҆ۦಈ
 νΣοΫ ಈ࡞֬ೝͷ
 ࣗಈԽ

    ؒҧ͑ʹ͍͘
 ઃܭ ͩΊͩɺࣗಈԽແཧͩʜ ͜Μͳঢ়گͰෆ҆ͳൣғ
 ͢΂ͯͷ֬ೝͳΜͯແཧʜ  
  105. 220.

    ։ൃ଎౓ͷ૿Ճྔ ࣌ؒܦա   --%#ͷ
 ׆༻ มߋ௚ޙͷ
 σόοά ෆ҆ۦಈ
 νΣοΫ

    ಈ࡞֬ೝͷ
 ࣗಈԽ ؒҧ͑ʹ͍͘
 ઃܭ ΋͠ɺޮ཰తʹಈ࡞֬ೝΛ
 ࣗಈԽ͢ΔͨΊͷ஌ࣝΛ
 ͍࣋ͬͯͨΒʜ
  106. 221.

    ։ൃ଎౓ͷ૿Ճྔ ࣌ؒܦա --%#ͷ
 ׆༻ มߋ௚ޙͷ
 σόοά ෆ҆ۦಈ
 νΣοΫ ಈ࡞֬ೝͷ
 ࣗಈԽ

    ؒҧ͑ʹ͍͘
 ઃܭ ΋͏ͪΐͬͱؤுͬͯΈ͚ͨͲɺ
 ͕͔͔࣌ؒΓ͗͢Δʜʜ  
  107. 222.

    ։ൃ଎౓ͷ૿Ճྔ ࣌ؒܦա --%#ͷ
 ׆༻ มߋ௚ޙͷ
 σόοά ෆ҆ۦಈ
 νΣοΫ ಈ࡞֬ೝͷ
 ࣗಈԽ

    ؒҧ͑ʹ͍͘
 ઃܭ ͕͔͔͍࣌ؒͬͯΔͷ͸ɺ
 ͜ͷςΫχοΫ࢖͑͹
 গ͠ϚγʹͳΔ͸ͣʜ  
  108. 225.

    ։ൃ଎౓ͷ૿Ճྔ ࣌ؒܦա --%#ͷ
 ׆༻ มߋ௚ޙͷ
 σόοά ෆ҆ۦಈ
 νΣοΫ ಈ࡞֬ೝͷ
 ࣗಈԽ

    ؒҧ͑ʹ͍͘
 ઃܭ ͋ͱ͔ΒৼΓฦΔͱɺ
 ͜͜ʹͭΒ͍࣌ظ͕͋ͬͨ  
  109. 227.

    ։ൃ଎౓ͷ૿Ճྔ ࣌ؒܦա --%#ͷ
 ׆༻ มߋ௚ޙͷ
 σόοά ෆ҆ۦಈ
 νΣοΫ ಈ࡞֬ೝͷ
 ࣗಈԽ

    ؒҧ͑ʹ͍͘
 ઃܭ   ࣗಈԽͷ୩Λӽ͑ΔͨΊʹ͸ɺ
 ͏·͘੾Γൈ͚ΔͨΊͷ஌͕ࣝ
 ͱͯ΋େࣄͩͱ͍͏͜ͱ