Share
Embed
iOSアプリの開発速度を170%に向上させたデバッグノウハウ / Debugging knowhow that improved our development velocity to 170%
開発時間に占めるデバッグ時間の割合は少なくないため、この時間の短縮は開発速度を上げるためにとても重要です。この発表では、実際の中規模なアプリ開発で70%増もの開発速度向上を支えたデバッグノウハウを初心者にもわかりやすく紹介します。
https://fortee.jp/iosdc-japan-2018/proposal/7286f755-e980-4f6f-b268-2c56a224b727
Transcript
251 slides
-
J04 ΞϓϦͷ։ൃΛ ʹ্ͤͨ͞ σόοάϊϋ Kuniwak - DeNA Co.,Ltd. 2018.09.02
iOSDC Japan 2018 -
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) } ᶃؔΛ࣮ߦ ίʔϧελοΫ -
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ؔ ͕ؔݺͼग़͞ΕΔͱɺ ελοΫϑϨʔϜ͕࡞͞Εɺ ίʔϧελοΫʹੵ·ΕΔ -
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ؔ -
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ؔ ݺͼग़͞ΕͨؔͷதͰ ͞Βʹผͷ͕ؔݺΕͯ ίʔϧελοΫʹੵ·ΕΔ -
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ؔ -
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[ؔ ίʔϧελοΫʹੵ·ΕΔ -
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[ؔ -
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[ؔ ࢭ·ͬͨ࣌ʹݟ͍͑ͯΔͷ -
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[ؔ ࣮શ෦--%#͔ΒݟΒΕΔ -
(lldb) help Debugger commands: apropos -- List debugger commands related
to a breakpoint -- Commands for operating on breakpoint shorthand.) ... IFMQΛ࣮ߦ͢Δͱɺ࣮ߦͰ͖Δૢ࡞ͷҰཡ͕දࣔ͞ΕΔ -
(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Λ͏ ҙ༁: ݱࡏͷεϨουͰίʔυΛධՁ͠ɺ݁Ռͷ ΛਓؒʹಡΈ͍͢ܗͰදࣔ͠·͢ɻ -
(lldb) po print("Hello, World") error: use of undeclared identifier 'print'
QPίϚϯυͰ)FMMP 8PSMEΛදࣔͯ͠ΈΑ͏ "QQ$PEFͷਓ͜ͷΤϥʔ͕ग़ͳ͍͔ -
(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ͷҰཡΛදࣔ -
(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) දࣔ͞Ε͍ͯΔΫϥεͷܕϝϞϦΞυϨεΛೖखͰ͖Δ -
(lldb) po $label <UILabel: 0x7fbee0610fe0; frame = (4 0; 250
37); text ΠϯελϯεΛऔಘͰ͖͔ͨͲ͏͔֬ೝͯ͠ΈΑ͏ -
5*14 ελοΫϑϨʔϜΛม͑ΔҎ֎ͷํ๏ ઌ΄ͲελοΫϑϨʔϜΛ4XJGUͷͷʹ ߹Θͤͳ͍ͱΤϥʔʹͳ͍ͬͯͨ ࣮ɺԼͷΑ͏ʹ࣮ߦ͢ΔͱɺελοΫϑϨʔϜΛ ม͑ͳͯ͘Τϥʔൃੜ͠ͳ͍ ҙຯhelp expr ͰௐͯΈΑ͏ (lldb)
expr -l swift -o -- print("Hello, World") ൃ ද Ͱ ׂ Ѫ -
struct Example { static func intToString(_ i: Int?) -> String
{ let x: Int! = i return "\(x)" } } ྫ͑ɺΛਐදهͷจࣈྻ ม͢ΔίʔυΛ࣮ͨ͠ͱ͢Δ ྫ͑ɺΛҾʹ͢Εɺਐ จࣈྻͰ͋Δ͕ฦ͖ͬͯͯ΄͍͠ -
struct Example { static func intToString(_ i: Int?) -> String
{ let x: Int! = i return "\(x)" } } όάΛݟ͚ͭͨΒલͷ࣮ՕॴʹΖ͏ -
struct Example { static func intToString(_ i: Int?) -> String
{ let x: Int = i! return "\(x)" } } Int!ΛจࣈྻԽ͢Δͱ"Optional(...)"ʹ ͳͬͯ͠·͏ͷͰɺIntʹͳΔΑ͏मਖ਼͢Δ -
struct Example { static func intToString(_ i: Int?) -> String
{ let x: Int! = i return "\(x)" } } ࣮ޙʹσόοάͨ͠ͷͰɺ ݪҼՕॴ͕͙ۙ͘͢ʹݟ͔ͭͬͨ -
5*14 --%#ͰCSFBLQPJOUΛޮ͔ͤΔํ๏ --%#ͷpoexprʹΑΔ࣮ߦͰCSFBLQPJOU͕ ޮ͔ͳͯ͘ࠔΔ͜ͱ͕͋Δ ͦ͜Ͱɺexprͷ-iΦϓγϣϯΛfalseʹ͢Δͱɺ --%#͔Βͷ࣮ߦͰCSFBLQPJOU͕ޮ͘Α͏ʹͳΔ (lldb) expr -l swift
-o -i false -- Example.intToString(42) ൃ ද Ͱ ׂ Ѫ -
కΊΓաͷ࠷ޙͷҰԡ͍͍ͩͨ͠खΓ ࣮ ࣮ ࣮ ϨϏϡʔ ϨϏϡʔ ϨϏϡʔ ಈ࡞֬ೝ खΓରԠ ಈ࡞֬ೝ
ਓͰεέʔϧ͠ͳ͍ $͞Μ ,VOJXBL "͞Μ #͞Μ -
ෆ҆ͳίʔυͷྫ struct Example { static func intToString(_ i: Int?) ->
String { let x: Int! = i return "\(x)" } } ͨͱ͑ɺઌ΄Ͳͷ͍ίʔυͷதʹ ෆ҆ͳॴ͕͋ͬͨ ࢲͷෆ҆ϙΠϯτ -
w සൟʹ௨ա͢Δذʢ͜͜ʹόά͕͋Δͱਃ͠༁ͳ͍ʣ if (x < 0)ͷذͰxʹͲΜͳͷ͕དྷΔ͔ Θ͔Βͳ͍ͳΒɺগͳ͘ͱਖ਼ͱෛͷ྆ํΛࢼ͢ w ذ݅ͷڥ x
< 0ͳΒڥɺྻۭͷͱ͖ʹڥʹͳΓ͍͢ DoubleͳΒ+0 -0 +infinity -infinity nanͳͲ w ཧղͰ͖͍ͯͳ͍ίϯϙʔωϯτͷར༻ ྫ͑ɺඪ४ϥΠϒϥϦͷ6*5BCMF7JFX͍ํ͕ ͱͯෳࡶͳͷͰ͍ͭؒҧ͑ͯ͠·͏ ࢲͷ࠷ۙͷෆ҆ϙΠϯτ ൃ ද Ͱ ׂ Ѫ -
#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Λ࡞͢Δ ൃ ද Ͱ ׂ Ѫ -
#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"ʹͳΔ͜ͱΛ֬ೝ͍ͯ͠Δ ൃ ද Ͱ ׂ Ѫ -
#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ؔΛ࣮͢Δ ൃ ද Ͱ ׂ Ѫ -
@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Λ࣮ߦ ൃ ද Ͱ ׂ Ѫ -
5*14 ࢦఆͨ͠ίʔυ͚ͩಈ࡞֬ೝ ݸผͷಈ࡞֬ೝ͚ͩΛ࣮ߦ͍ͨ͜͠ͱΑ͋͘Δ ͦ͏͍͏߹ɺ--%#͔Β໊લΛࢦఆͯ͠ݸผʹ ಈ࡞֬ೝΛ࣮ߦͰ͖Δ ಈ࡞֬ೝͷίʔυΛྻͰͳ͘%JDUJPOBSZʹ ֨ೲͨ͠ͷ͜Ε͕త (lldb) po debugTargets["intToString"]!()
Fatal error: intToString(42) ͕ "42" Λฦ͞ͳ͔ͬͨ: ൃ ද Ͱ ׂ Ѫ -
ϊϋʵ# ඪ४ϥΠϒϥϦͰಈ࡞֬ೝ ৽͍͠5BSHFUΛ࡞͢Δ ಈ࡞֬ೝͷίʔυΛॻ͘ ಈ࡞֬ೝͷίʔυͷ5BSHFUΛ ઌ΄Ͳ࡞ͨ͠ͷʹ͢Δ J04ʹಈ࡞֬ೝ༻ͷඪ४ϥΠϒϥϦʮ9$5FTUʯ͕
༻ҙ͞Ε͍ͯΔ ͜ΕͰ"QQ%FMFHBUFʹ͋ͬͨෳࣦഊͨ͠ͱ͖ͷ ΛղܾͰ͖Δ͕ɺ࣍ͷΑ͏ͳϏϧυઃఆ͕ඞཁɿ -
import XCTest @testable import IOSDC2018Debugging class ExampleTest: XCTestCase { func
testIntToString() { let str = Example.intToString(42) XCTAssertEqual(str, "42") } } ඪ४ϥΠϒϥϦΛ͏߹ͷ ಈ࡞֬ೝͷίʔυ͜͏ͳΔ -
import XCTest @testable import IOSDC2018Debugging class ExampleTest: XCTestCase { func
testIntToString() { let str = Example.intToString(42) XCTAssertEqual(str, "42") } } ಈ࡞֬ೝͷίʔυͷUBSHFU ઌ΄Ͳ࡞ͨ͠UBSHFUʹ͢Δ -
9DPEF ᶃ࡞ͨ͠5BSHFUΛબ "QQ$PEF ൃ ද Ͱ ׂ Ѫ ᶃ.BOBHF5BSHFUTΛબ
ᶄ࡞ͨ͠5BSHFUΛબ 5BSHFUͷࢦఆͷΓํ -
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·Ͱݟ͑ΔΑ͏ʹͳΔ -
import XCTest @testable import IOSDC2018Debugging class ExampleTest: XCTestCase { func
testIntToString() { let str = Example.intToString(42) XCTAssertEqual(str, "42") } } ಈ࡞֬ೝͷίʔυXCTestCaseͱ͍͏ ΫϥεͰάϧʔϐϯάͰ͖ΔΑ͏ʹͳ͍ͬͯΔ ಈ࡞֬ೝͷίʔυΛॻ͘߹ʹɺ֬ೝରͷ Ϋϥεؔ୯ҐͰXCTestCaseΛ͚Δͱ ಈ࡞֬ೝͷ݁Ռ͕Θ͔Γ͘͢ͳΔ -
import XCTest @testable import IOSDC2018Debugging class ExampleTest: XCTestCase { func
testIntToString() { let str = Example.intToString(42) XCTAssertEqual(str, "42") } } ࣮ࡍͷಈ࡞֬ೝͷίʔυɺΠϯελϯεϝιουͱ࣮ͯ͢͠Δ ͜ͷϝιουͷ໊લઌ಄ʹtestͱ͚ͭͳ͍ͱ࣮ߦ͞Εͳ͍ͷͰҙ -
import XCTest @testable import IOSDC2018Debugging class ExampleTest: XCTestCase { func
testIntToString() { let str = Example.intToString(42) XCTAssertEqual(str, "42") } } ಈ࡞֬ೝͷରΛ࣮ࡍʹ࣮ߦ͢Δ -
import XCTest @testable import IOSDC2018Debugging class ExampleTest: XCTestCase { func
testIntToString() { let str = Example.intToString(42) XCTAssertEqual(str, "42") } } ਖ਼ޡఆXCTAssertEqualͳͲͷؔΛ͏ ͜ͷؔɺͭͷҾ͕͘͠ͳ͍ͱɺ ಈ࡞֬ೝΛࣦഊͱΈͳ͢Α͏ʹͳ͍ͬͯΔ -
*%ΛݸผͷTUSVDU͚ͨྫ // BAD: औΓҧ͑ͯؾ͖ͮͮΒ͍ let userID: Int = 1234 let
productID: Int = 5678 // GOOD: औΓҧ͑ΔͱίϯύΠϧΤϥʔʹͳΔ let userID = UserID(number: 1234) let productID = ProductID(number: 5678) ൃ ද Ͱ ׂ Ѫ -
ϊϋʵ# ίʔυΛࣗಈੜ͢Δ Α͋͘ΔόάͷݪҼʹ6*4UPSZCPBSE6*/JCͷ ໊લࢦఆͷϛεϦωʔϜΕ͕͋Δ ͜ΕΒͷίʔυΛࣗಈੜͯ͘͠ΕΔπʔϧ͕ ͍͔ͭ͋͘ΔͷͰɺͲΕ͔Λ͑͜ͷछͷόάΛ ࣄલʹ༧Ͱ͖Δɿ w HJUIVCDPNNBDDBJO3TXJGU w
HJUIVCDPN4XJGU(FO4XJGU(FO ൃ ද Ͱ ׂ Ѫ -
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)!" } } } ಈ࡞֬ೝͷࣗಈԽ͕͔ͳΓ͍͠ྫ -
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)!" } } } Ϙλϯ͕λοϓ͞ΕͨΒ αʔόͱ௨৴Λ࢝ΊΔ ൃ ද Ͱ ׂ Ѫ -
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Ͱදࣔ ൃ ද Ͱ ׂ Ѫ -
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)!" } } } αʔόʔ͔ΒϢʔβʔ໊͕ ਖ਼ৗʹฦ͖ͬͯͨΒදࣔ ൃ ද Ͱ ׂ Ѫ -
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")) } } ͱΓ͋͑ͣಈ࡞֬ೝͷίʔυΛॻ͍ͯΈͨ ൃ ද Ͱ ׂ Ѫ -
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ΛॳظԽ ൃ ද Ͱ ׂ Ѫ -
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Λ࣮ߦͯ͠ϘλϯλοϓΛ࠶ݱ ൃ ද Ͱ ׂ Ѫ -
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͕Ϣʔβʔ໊ʹมΘͬͨ͜ͱΛ֬ೝ ൃ ද Ͱ ׂ Ѫ -
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 ൃ ද Ͱ ׂ Ѫ -
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͕ଘࡏ͍ͯ͠ͳ͍ ݪҼɿ ൃ ද Ͱ ׂ Ѫ -
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 ൃ ද Ͱ ׂ Ѫ -
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ΛΓग़ͨ͠Ϋϥεʹ͓ͯ͘͜͠ͱ ൃ ද Ͱ ׂ Ѫ -
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, ൃ ද Ͱ ׂ Ѫ -
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͔Β͞Εͨίʔυ ൃ ද Ͱ ׂ Ѫ -
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)!" } } } ɿ͜ͷ໊લదͳͷͰɺ࣮ࡍ֤ʑͷ ΞʔΩςΫνϟʹԊ໋໊ͬͨʹ͍ͯͩ͘͠͞ ൃ ද Ͱ ׂ Ѫ -
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 ൃ ද Ͱ ׂ Ѫ -
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ͷॲཧΛͦͷ··Ҡಈ ൃ ද Ͱ ׂ Ѫ -
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͕ଘࡏ͠ͳ͍ ʢ͜Εগ͠ޙͰमਖ਼͢Δʣ ൃ ද Ͱ ׂ Ѫ -
मਖ਼ޙͷಈ࡞֬ೝίʔυ 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")) } } ൃ ද Ͱ ׂ Ѫ -
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Ͱͳ͘ɺ Γग़ͨ͠ΫϥεͷํΛ ಈ࡞֬ೝ͢ΔΑ͏ʹ͢Δ ൃ ද Ͱ ׂ Ѫ -
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͕͞ΕΔͷΛ࠶ݱ ൃ ද Ͱ ׂ Ѫ -
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ͰλοϓΛ࠶ݱ͢Δ෦ Γग़ͨ͠Ϋϥεͷํͷϝιουͷ࣮ߦͰସ ൃ ද Ͱ ׂ Ѫ -
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͕·ͩ ଘࡏ͠ͳ͍ΛղܾͰ͖Δ ൃ ද Ͱ ׂ Ѫ -
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͔Β͞Εͨίʔυ ൃ ද Ͱ ׂ Ѫ -
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͕ଘࡏ͠ͳ͍ͷղܾ ൃ ද Ͱ ׂ Ѫ -
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Λ ผͷΫϥεͰୟ͚ΔΑ͏ʹ͢ΔΫϥε ൃ ද Ͱ ׂ Ѫ -
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ʹ͢Δʣ ൃ ද Ͱ ׂ Ѫ -
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͕࣮ߦ͞ΕΔ ൃ ද Ͱ ׂ Ѫ -
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͔Β͞Εͨίʔυ ൃ ද Ͱ ׂ Ѫ -
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Λड͚औΔ ൃ ද Ͱ ׂ Ѫ -
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ͷݺͼग़͠ม͑Δ ൃ ද Ͱ ׂ Ѫ -
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")) } } मਖ਼ޙͷಈ࡞֬ೝίʔυ ൃ ද Ͱ ׂ Ѫ -
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Λ࡞͢Δ ൃ ද Ͱ ׂ Ѫ -
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͔Β Γग़ͨ͠Ϋϥε͢ ൃ ද Ͱ ׂ Ѫ -
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")) } } Ϗϧυ͕௨ΔΑ͏ʹͳͬͨ ൃ ද Ͱ ׂ Ѫ -
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)") ൃ ද Ͱ ׂ Ѫ -
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ΛݟΔͱ ॳظͷ··ʹͳ͍ͬͯͨ Ϣʔβʔ໊͕·ͩදࣔ͞Ε͍ͯͳ͍Α͏ͩ ൃ ද Ͱ ׂ Ѫ -
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Λߋ৽Ͱ͖͍ͯͳ͍ ൃ ද Ͱ ׂ Ѫ -
Ұൠతʹɺ"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 } " # ൃ ද Ͱ ׂ Ѫ -
ਖ਼ղɺʮ"ͱ#ͷͲͪΒ͋Γ͑Δʯ ͭͷ"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͕ ޙʹྃ͢Ε# ൃ ද Ͱ ׂ Ѫ -
ݟͨͷߋ৽Λ"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) ൃ ද Ͱ ׂ Ѫ -
ݟͨͱཪଆͷͷͨΊͷ४උ 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) } ൃ ද Ͱ ׂ Ѫ -
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) } ཪଆͷࣄͷ४උ͕ͬͨͱ͖ʹୟ͍ͯΒ͏ ݟͨͷҰׅөϝιου ൃ ද Ͱ ׂ Ѫ -
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Ͱදݱ ൃ ද Ͱ ׂ Ѫ -
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ͳ ΤϥʔͷܕΛ࡞͓ͯ͘͠ ൃ ද Ͱ ׂ Ѫ -
ཪଆͷࣄΛ͓͜ͳ͏Ϋϥε 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)) } } } } ൃ ද Ͱ ׂ Ѫ -
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)) } } } } ɿ͜ͷ໊લదͳͷͰɺ࣮ࡍ֤ʑͷ ΞʔΩςΫνϟʹԊ໋໊ͬͨʹ͍ͯͩ͘͠͞ ൃ ද Ͱ ׂ Ѫ -
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Λ࣮͢Δ͜ͱʹͳΔ ൃ ද Ͱ ׂ Ѫ -
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ϝιουͷ࣮ߦͰ͡ΊΔ ൃ ද Ͱ ׂ Ѫ -
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௨͢Δ ൃ ද Ͱ ׂ Ѫ -
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)!" } } } ൃ ද Ͱ ׂ Ѫ -
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Λ࣮ͤ͞Δ ൃ ද Ͱ ׂ Ѫ -
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)!" } } } දࣔͷҰׅߋ৽ϝιουΛ࣮ ʢ༰मਖ਼લͱಉ͡ʣ ൃ ද Ͱ ׂ Ѫ -
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͔Βͨ͠Ϋϥε ൃ ද Ͱ ׂ Ѫ -
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͔Βͯ͠Β͏ͱɺͷͪ ͷָͪʹͳΔ ൃ ද Ͱ ׂ Ѫ -
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 import UIKit class ExampleViewController: UIViewController { // লུ override
func viewDidLoad() { super.viewDidLoad() self.actions = ExampleViewsAction( label: self.label, modalPresenter: ModalPresenter(willPresentOn: self), model: ExampleModel() ) } } ൃ ද Ͱ ׂ Ѫ -
import UIKit class ExampleViewController: UIViewController { // লུ override func
viewDidLoad() { super.viewDidLoad() self.actions = ExampleViewsAction( label: self.label, modalPresenter: ModalPresenter(willPresentOn: self), model: ExampleModel() ) } } UILabelͳͲΛ͍ͭ͢Ͱʹཪଆͷ ࣄΫϥε࡞͓ͯͯ͘͠͠ ൃ ද Ͱ ׂ Ѫ -
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")) } } मਖ਼ޙͷಈ࡞֬ೝίʔυ ൃ ද Ͱ ׂ Ѫ -
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")) } } Ϙλϯͷλοϓͷ࠶ݱͰͳ͘ɺ ݟͨͷөϝιουΛୟ͘ ൃ ද Ͱ ׂ Ѫ -
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 ཪͷࣄΫϥε͕ݺΕͨΒɺඞͣྃ͢Δ ͜ͱΛ֬ೝ͢ΔίʔυΛՃ ͦΕͧΕͷৄࡉεϥΠυͷຕͷࣄͰׂѪ͢Δ͕ɺ Ҏ߱ͷεϥΠυͷ͚ࣝͩͰ࣮Ͱ͖Δ ൃ ද Ͱ ׂ Ѫ -
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")) } } Ͱಈ࡞֬ೝΛ࣮ߦͯ͠ΈΑ͏ ൃ ද Ͱ ׂ Ѫ -
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")) } } ͜͜·Ͱ͖ͯͬͱޭ͠·͢ ൃ ද Ͱ ׂ Ѫ -
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*ݺͼग़͕͠Τϥʔʹ ͳͬͨ߹ͷಈ࡞֬ೝΛͰ͖͍ͯͳ͍ʜ ൃ ද Ͱ ׂ Ѫ -
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Ͱςετ༰қͳઃܭΛ ࣮ݱ͢ΔͨΊͷσβΠϯύλʔϯʯͰ αϯϓϧίʔυͱ߹Θͤͯղઆ͍ͯ͠Δ ൃ ද Ͱ ׂ Ѫ -
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 ݟͨͷҰׅөΛίʔυ͔Β ࣮ߦͰ͖ΔΑ͏ʹͨ͠ ࣗಈͰͷಈ࡞֬ೝʹඞཁͳࡉ͔ͳ͕ࣝ ͱͯଟ͍͜ͱ͕Θ͔Δ ൃ ද Ͱ ׂ Ѫ -
։ൃͷ૿Ճྔ ࣌ؒܦա --%#ͷ
׆༻ มߋޙͷ σόοά ෆ҆ۦಈ νΣοΫ ಈ࡞֬ೝͷ ࣗಈԽ ؒҧ͑ʹ͍͘ ઃܭ -
։ൃͷ૿Ճྔ ͜͜·Ͱ͢ΜͳΓ͍͘ ࣌ؒܦա --%#ͷ ׆༻ มߋޙͷ σόοά ෆ҆ۦಈ
νΣοΫ ಈ࡞֬ೝͷ ࣗಈԽ ؒҧ͑ʹ͍͘ ઃܭ -
։ൃͷ૿Ճྔ ࣌ؒܦա --%#ͷ ׆༻ มߋޙͷ σόοά ෆ҆ۦಈ νΣοΫ ಈ࡞֬ೝͷ ࣗಈԽ
ؒҧ͑ʹ͍͘ ઃܭ ͋ΕʁࣗಈԽ͕͍ͧ͠ʜʁ ͰηϧϑνΣοΫͪΌΜͱ ͬͯΔ͔Β͕͔͔࣌ؒΔʜ -
։ൃͷ૿Ճྔ ࣌ؒܦա --%#ͷ ׆༻ มߋޙͷ σόοά ෆ҆ۦಈ νΣοΫ ಈ࡞֬ೝͷ ࣗಈԽ
ؒҧ͑ʹ͍͘ ઃܭ ͏ͪΐͬͱؤுͬͯΈ͚ͨͲɺ ͕͔͔࣌ؒΓ͗͢Δʜʜ -
։ൃͷ૿Ճྔ ࣌ؒܦա --%#ͷ ׆༻ มߋޙͷ σόοά ෆ҆ۦಈ νΣοΫ ಈ࡞֬ೝͷ ࣗಈԽ
ؒҧ͑ʹ͍͘ ઃܭ ͩΊͩɺࣗಈԽແཧͩʜ ͜Μͳঢ়گͰෆ҆ͳൣғ ͯ͢ͷ֬ೝͳΜͯແཧʜ -
։ൃͷ૿Ճྔ ࣌ؒܦա --%#ͷ ׆༻ มߋޙͷ σόοά ෆ҆ۦಈ νΣοΫ
ಈ࡞֬ೝͷ ࣗಈԽ ؒҧ͑ʹ͍͘ ઃܭ ͠ɺޮతʹಈ࡞֬ೝΛ ࣗಈԽ͢ΔͨΊͷࣝΛ ͍࣋ͬͯͨΒʜ -
։ൃͷ૿Ճྔ ࣌ؒܦա --%#ͷ ׆༻ มߋޙͷ σόοά ෆ҆ۦಈ νΣοΫ ಈ࡞֬ೝͷ ࣗಈԽ
ؒҧ͑ʹ͍͘ ઃܭ ͏ͪΐͬͱؤுͬͯΈ͚ͨͲɺ ͕͔͔࣌ؒΓ͗͢Δʜʜ -
։ൃͷ૿Ճྔ ࣌ؒܦա --%#ͷ ׆༻ มߋޙͷ σόοά ෆ҆ۦಈ νΣοΫ ಈ࡞֬ೝͷ ࣗಈԽ
ؒҧ͑ʹ͍͘ ઃܭ ͕͔͔͍࣌ؒͬͯΔͷɺ ͜ͷςΫχοΫ͑ গ͠ϚγʹͳΔͣʜ -
։ൃͷ૿Ճྔ ࣌ؒܦա --%#ͷ ׆༻ มߋޙͷ σόοά ෆ҆ۦಈ νΣοΫ ಈ࡞֬ೝͷ ࣗಈԽ
ؒҧ͑ʹ͍͘ ઃܭ ͋ɺͳΜָ͔ʹͳ͖ͬͯͨ -
։ൃͷ૿Ճྔ ࣌ؒܦա --%#ͷ ׆༻ มߋޙͷ σόοά ෆ҆ۦಈ νΣοΫ ಈ࡞֬ೝͷ ࣗಈԽ
ؒҧ͑ʹ͍͘ ઃܭ ΊͬͪΌָʹͳͬͨ -
։ൃͷ૿Ճྔ ࣌ؒܦա --%#ͷ ׆༻ มߋޙͷ σόοά ෆ҆ۦಈ νΣοΫ ಈ࡞֬ೝͷ ࣗಈԽ
ؒҧ͑ʹ͍͘ ઃܭ ͋ͱ͔ΒৼΓฦΔͱɺ ͜͜ʹͭΒ͍࣌ظ͕͋ͬͨ -
։ൃͷ૿Ճྔ ࣌ؒܦա --%#ͷ ׆༻ มߋޙͷ σόοά ෆ҆ۦಈ νΣοΫ ಈ࡞֬ೝͷ ࣗಈԽ
ؒҧ͑ʹ͍͘ ઃܭ ࣗಈԽͷ୩ -
։ൃͷ૿Ճྔ ࣌ؒܦա --%#ͷ ׆༻ มߋޙͷ σόοά ෆ҆ۦಈ νΣοΫ ಈ࡞֬ೝͷ ࣗಈԽ
ؒҧ͑ʹ͍͘ ઃܭ ࣗಈԽͷ୩Λӽ͑ΔͨΊʹɺ ͏·͘Γൈ͚ΔͨΊͷ͕ࣝ ͱͯେࣄͩͱ͍͏͜ͱ