Slide 1

Slide 1 text

SOLIDݪଇͷSͱDͱςετͷ࿩ ʮSwiftΒ͘͠ઃܭ͢ΔʯAnother by. 2018/12/21 Roppongi.swift 1

Slide 2

Slide 2 text

takasek @takasek Works OSS: ActionClosurable౳ App: PasteTheType Articles ʮίϯύΠϧΤϥʔ΍ϥϯλΠϜΤϥʔΛ௚͍ͯ͘͠ ͚ͩͰiOSΞϓϦͷ࡞Γํ͕Θ͔ΔϓϩδΣΫτʯ ʮ͓લΒ͕ModelͱݺͿΞϨΛͳΜͱݺͿ΂͖͔ʯ ʮiOSΞϓϦઃܭύλʔϯೖ໳ʯʢڞஶʣ 2

Slide 3

Slide 3 text

ʮiOSΞϓϦઃܭύλʔϯೖ໳ʯ ઌߦൃച഑ૹதʂ Ұൠൃചؒۙʂ takasek୲౰ষ ୈ1ষʮઃܭ͢Δͱ͍͏͜ͱʯ ୈ2ষʮઃܭʹύλʔϯΛద༻͢Δલʹʯ ୈ3ষʮSwiftΒ͘͠ઃܭ͢Δʯ ୈ4ষʮΞʔΩςΫνϟͷύλʔϯΛௗᛌ͢ Δʯ 3

Slide 4

Slide 4 text

App Client Melting Pod #1 1/10ʹ΍Γ·͢ 4

Slide 5

Slide 5 text

ୈ3ষʮSwiftΒ͘͠ઃܭ͢Δʯ SOLIDݪଇ΍ɺProtocol Oriented Programming, Swiftͷݴޠػೳʢenum, generics, etcʣΛ࢖ͬͯ ίʔυΛʮΑ͍ઃܭʯ΁ͱվળ͍ͯ͘͠ষ 5

Slide 6

Slide 6 text

Α͍ઃܭ #ͱ͸ 6

Slide 7

Slide 7 text

ୈ2ষʮઃܭʹύλʔϯΛద༻͢Δલʹʯ ཁ໿ • ໊લʹΑͬͯ੹຿ͷڥքΛ໌ࣔ͢Δ • ෆద੾ͳ੹຿ʢίʔυͷष͍ʣ͸ɺ ɹઃܭͷݪଇʹΑͬͯݴޠԽͰ͖Δ • ඞཁʹԠͯ͡ઃܭͷݪଇɺύλʔϯΛద༻͢Δ͕ɺ΍Γ͗͢͸ې෺ • ઃܭ͸୯७ʹ࢝ΊɺϦϑΝΫλϦϯάΛਐΊͳ͕Β ɹΠςϨʔςΟϒʹਐԽͤ͞Δ • ϦϑΝΫλϦϯάͷͨΊʹ͸ɺςετ͠΍͍͢ઃܭ͕લఏ 7

Slide 8

Slide 8 text

3ষͷվળର৅ίʔυʢൈਮʣ final class MessageSender { private let api = CommonMessageAPI() init(messageType: MessageType) { ... } private var isTextValid: Bool { ... } private var isImageValid: Bool { ... } var isValid: Bool { switch messageType { case .text: return isTextValid case .image: return isTextValid && isImageValid case .official: return false // OfficialMessage͸͋Γ͑ͳ͍ } } ... 8

Slide 9

Slide 9 text

MessageSender ͷ ໰୊఺ όϦσʔγϣϯϩδοΫ͕ MessageSender ͷதͰ ๲ΒΈ͍͗ͯ͢Δ 9

Slide 10

Slide 10 text

όϦσʔγϣϯϩδοΫ ͕๲ΒΈ͍͗ͯ͢Δͱ Կ͕ਏ͍͔ 10

Slide 11

Slide 11 text

MessageSender ͷ ςετΛ ߟ͑ͯΈ·͠ΐ͏ 11

Slide 12

Slide 12 text

let sut = MessageSender(messageType: .image) let cases: [(line: UInt, text: String?, image: UIImage?, expects: Bool)] = [ (line: #line, text: nil, image: UIImage(), expects: true), // text͕͋Δ৔߹ɺ80จࣈҎ಺Ͱͳ͍ͱinvalid (line: #line, text: String(repeating: "x", count: 80), image: UIImage(), expects: true), (line: #line, text: String(repeating: "x", count: 81), image: UIImage(), expects: false), // image͕ͳ͚Ε͹͍͔ͳΔ৔߹΋invalid (line: #line, text: nil, image: UIImage(), expects: false), (line: #line, text: String(repeating: "x", count: 80), image: UIImage(), expects: false), (line: #line, text: String(repeating: "x", count: 81), image: UIImage(), expects: false), ] cases.forEach { sut.text = $0.text sut.image = $0.image XCTAssertEqual(sut.isValid, $0.expects, line: $0.line) } 12

Slide 13

Slide 13 text

໰୊ͳ͍ 13

Slide 14

Slide 14 text

ຊ౰ʹʁ 14

Slide 15

Slide 15 text

MessageSender ͷ ผͷ໰୊఺ API͕۩ମతͳ௨৴ϩδοΫͱ ີ݁߹͍ͯ͠Δ 15

Slide 16

Slide 16 text

ղܾࡦɿ ґଘؔ܎ٯసͷݪଇ ❌ final class MessageSender { private let api = CommonMessageAPI() init(messageType: MessageType) { ... } } 16

Slide 17

Slide 17 text

ղܾࡦɿ ґଘؔ܎ٯసͷݪଇ ⭕ final class MessageSender { private let api: CommonMessageAPIProtocol // protocolԽ init(messageType: MessageType, api: CommonMessageAPIProtocol) { // DI ... } } 17

Slide 18

Slide 18 text

Swift Complier Error 18

Slide 19

Slide 19 text

let sut = MessageSender( api: /* ! ͜͜ʹԿΛೖΕΑ͏…ʁ */, messageType: .image ) let cases = [ ... ] cases.forEach { sut.text = $0.text sut.image = $0.image XCTAssertEqual(sut.isValid, $0.expects, line: $0.line) } 19

Slide 20

Slide 20 text

όϦσʔγϣϯͷςετ Λ͍ͨ͠ͷʹ ؔ܎ͷͳ͍ґଘ͕͋Δ 20

Slide 21

Slide 21 text

ղܾࡦɿ ୯Ұ੹೚ݪଇ • όϦσʔγϣϯϩδοΫΛ੾Γग़͢ • ޓ͍ʹ஌Δඞཁͷͳ͍TextMessageͱImageMessageͷ ValidatorΛ෼ׂ 21

Slide 22

Slide 22 text

struct ImageMessageInputValidator { let image: UIImage? let text: String? var isValid: Bool { if image == nil { return false } if let text = text, text.count > 80 { return false } return true } } ※ iOSઃܭຊͰ͸͞ΒʹઌͷਐԽ͕͋Γ·͢ɻ ɹؾʹͳΔਓ͸ຊΛಡΜͰ͔֬ΈͯΈΖʂ 22

Slide 23

Slide 23 text

վળςετίʔυ // apiͷ͜ͱΛҙࣝ͢Δඞཁ͕ͳ͘ͳͬͨ // messageType΋ཁΒͳ͘ͳͬͨ let cases = [ ... ] cases.forEach { let sut = ImageMessageInputValidator( text: $0.text, image: $0.image ) XCTAssertEqual(sut.isValid, $0.expects, line: $0.line) } 23

Slide 24

Slide 24 text

Happy 24

Slide 25

Slide 25 text

ܕͷ੹຿Λখ͘͞อͭͱ ςετ΋͠΍͍͢͠ංେԽ΋๷͛Δ 25

Slide 26

Slide 26 text

ͱ͜ΖͰ 26

Slide 27

Slide 27 text

Α͘ݟΔίʔυ final class HogeListPresenter { private func didReceiveResponse(hoges: [Hoge]) { updateViewDataArray(with: hoges) } private func updateViewDataArray(with hoges: [Hoge]) { self.viewDataArray = hoges.map { if hoge.isXXX { return HogeViewData(...) } else { return HogeViewData(...) } } } } 27

Slide 28

Slide 28 text

Α͘ݟΔίʔυ final class HogeListPresenter { private func didReceiveResponse(hoges: [Hoge]) { updateViewDataArray(with: hoges) // Ͳ͏͍͏෭࡞༻͕͋Δͷ͔҉໧త } private func updateViewDataArray(with hoges: [Hoge]) { self.viewDataArray = hoges.map { if hoge.isXXX { return HogeViewData(...) } else { return HogeViewData(...) } } } } 28

Slide 29

Slide 29 text

͡Ό͋͜͏͢Δʁ final class HogeListPresenter { private func didReceiveResponse(hoges: [Hoge]) { self.viewDataArray = makeViewDataArray(from: hoges) } private func makeViewDataArray(from hoges: [Hoge]) -> [HogeViewData] { return hoges.map { if hoge.isXXX { return HogeViewData(...) } else { return HogeViewData(...) } } } } 29

Slide 30

Slide 30 text

[Hoge] Λ [HogeViewData] ʹ ม׵͢Δͷ͸ผʹ HogeListPresenter Πϯελϯε͡Όͳͯ͘΋ Ͱ͖Δ 30

Slide 31

Slide 31 text

ͦΕͳΒ͜͏Ͱ͸ final class HogeListPresenter { func didReceiveResponse(hoges: [Hoge]) { self.viewDataArray = hoges.map(HogeViewData.init) } } private extension HogeViewData { init(hoge: Hoge) { ... } } 31

Slide 32

Slide 32 text

• makeViewDataArray(from:) ͱ͍͏Α͏ͳ ৑௕ͳ໊લͰͳ͘ͳͬͨ • HogeListPresenter ʹର͢Δ෭࡞༻͕ͳ͍͜ͱ͕໌֬ • HogeViewData ୯ମͰςετ΋Ͱ͖Δߏ଄ʹͳͬͨ 32

Slide 33

Slide 33 text

੹຿Λਖ਼͘͠อͭ͜ͱͰ ߟ͑Δ͜ͱ͕ݮΓ ςετ͠΍͘͢ͳΓ API͕γϯϓϧʹͳͬͨ 33

Slide 34

Slide 34 text

Happy 34

Slide 35

Slide 35 text

Happy ͳઃܭ͍͖ͯ͠·͠ΐ͏ 35