Slide 1

Slide 1 text

දࣔϩδοΫʹ͓͚Δ
 ςελϏϦςΟͷ֫ಘྫ JTILBXB

Slide 2

Slide 2 text

w גࣜձࣾ9$50 w 4XJGU,PUMJO(P+BWB4DSJQU w "1*,JU%*,JU%BUB4PVSDF,JU w 4XJGU࣮ફೖ໳J041SPHSBNNJOH

Slide 3

Slide 3 text

ςελϏϦςΟͷ࿩

Slide 4

Slide 4 text

w ςετΛॻ͖ͮΒ͍Օॴ͕͋Δ w ίετ͕ݟ߹͍ͬͯͳ͍ؾ͕͢Δ w ϝϯς͕ͭΒ͘ͳ͖ͬͯͨ w ։ൃ଎౓͕མ͖ͪͯͨ

Slide 5

Slide 5 text

զʑ͕औΓ૊ΜͩྫΛ঺հ͠·͢ ʢϕετϓϥΫςΟεͱ͸ݶΒͳ͍ʣ

Slide 6

Slide 6 text

ྫ୊

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

w ը໘දࣔ࣌ w ΠϯδέʔλʔΛදࣔ w ಡΈࠐΈΛ։࢝ w ಡΈࠐΈ׬ྃ࣌ w ΠϯδέʔλʔΛඇදࣔʹ w ϦετΛදࣔ͢Δ w ⭐ͷλοϓ࣌ w Ϙλϯͷঢ়ଶΛ൓స w αʔόʔʹ൓సޙͷঢ়ଶΛૹ৴

Slide 9

Slide 9 text

"1*$MJFOU 3FQPTJUPSJFT7JFX$POUSPMMFS 6*$PMMFDUJPO7JFX 3FQPTJUPSZ$FMM &NQUZ$FMM 3FQPTJUPSZ

Slide 10

Slide 10 text

"1*$MJFOU 3FQPTJUPSJFT7JFX$POUSPMMFS 6*$PMMFDUJPO7JFX 3FQPTJUPSZ$FMM &NQUZ$FMM 3FQPTJUPSZ ্͔ΒԼ·ͰҰؾʹ ςετ͢Δͷ͸େม

Slide 11

Slide 11 text

6*ςετ͸࣮૷΋࣮ߦ΋ߴίετ λΠϜϥΠϯͷ੍ޚ͕೉͍͠ ςετର৅ͷঢ়گΛ༻ҙͮ͠Β͍

Slide 12

Slide 12 text

6*ςετ͸࣮૷΋࣮ߦ΋ߴίετ λΠϜϥΠϯͷ੍ޚ͕೉͍͠ ςετର৅ͷঢ়گΛ༻ҙͮ͠Β͍ w 6*ͷཁૉΛ௨ͯ͡ঢ়ଶΛݕূ͢Δඞཁ͕͋Δ w ଴͕ͪଟ͘ͳΔͨΊ൓෮࣮ߦʹ޲͔ͳ͍

Slide 13

Slide 13 text

6*ςετ͸࣮૷΋࣮ߦ΋ߴίετ λΠϜϥΠϯͷ੍ޚ͕೉͍͠ ςετର৅ͷঢ়گΛ༻ҙͮ͠Β͍ w 6*΍֎෦γεςϜͷΠϕϯτ͸׬શʹ͸੍ޚͰ͖ͳ͍ w ࿈ଧͳͲͷࠐΈೖͬͨঢ়گΛ࠶ݱͰ͖ͳ͍

Slide 14

Slide 14 text

6*ςετ͸࣮૷΋࣮ߦ΋ߴίετ λΠϜϥΠϯͷ੍ޚ͕೉͍͠ ςετର৅ͷঢ়گΛ༻ҙͮ͠Β͍ w ֎෦γεςϜͷԠ౴Λ੾Γସ͑Δඞཁ͕͋Δ w ؀ڥʹΑͬͯෆ҆ఆͳ݁ՌʹͳΔ

Slide 15

Slide 15 text

ͭͣͭղফ͢Δ

Slide 16

Slide 16 text

6*ςετ͸࣮૷΋࣮ߦ΋ߴίετ

Slide 17

Slide 17 text

ݕূ͠΍͍͢ϞσϧͰදݱ͢Δ

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

struct State { var repositories = [] as [Repository] var isLoading = false var cells: [Cell] { if repositories.isEmpty { return [.empty(isLoading: isLoading)] } else { return repositories.map { .repository($0) } } } } enum Cell: Equatable { case empty(isLoading: Bool) case repository(Repository) }

Slide 20

Slide 20 text

struct State { var repositories = [] as [Repository] var isLoading = false var cells: [Cell] { if repositories.isEmpty { return [.empty(isLoading: isLoading)] } else { return repositories.map { .repository($0) } } } } enum Cell: Equatable { case empty(isLoading: Bool) case repository(Repository) } σʔλΛอ࣋͢ΔϓϩύςΟ

Slide 21

Slide 21 text

struct State { var repositories = [] as [Repository] var isLoading = false var cells: [Cell] { if repositories.isEmpty { return [.empty(isLoading: isLoading)] } else { return repositories.map { .repository($0) } } } } enum Cell: Equatable { case empty(isLoading: Bool) case repository(Repository) } ը໘ͷঢ়ଶΛදݱ͢ΔϓϩύςΟ

Slide 22

Slide 22 text

struct State { var repositories = [] as [Repository] var isLoading = false var cells: [Cell] { if repositories.isEmpty { return [.empty(isLoading: isLoading)] } else { return repositories.map { .repository($0) } } } } enum Cell: Equatable { case empty(isLoading: Bool) case repository(Repository) } ը໘ͷঢ়ଶΛදݱ͢ΔϓϩύςΟ ηϧΛදݱ͢Δܕ

Slide 23

Slide 23 text

struct State { var repositories = [] as [Repository] var isLoading = false var cells: [Cell] { if repositories.isEmpty { return [.empty(isLoading: isLoading)] } else { return repositories.map { .repository($0) } } } } enum Cell: Equatable { case empty(isLoading: Bool) case repository(Repository) }

Slide 24

Slide 24 text

struct State { var repositories = [] as [Repository] var isLoading = false var cells: [Cell] { if repositories.isEmpty { return [.empty(isLoading: isLoading)] } else { return repositories.map { .repository($0) } } } } enum Cell: Equatable { case empty(isLoading: Bool) case repository(Repository) }

Slide 25

Slide 25 text

XCTAssertEqual(state.cells, [.empty(isLoading: true)])

Slide 26

Slide 26 text

XCTAssertEqual(state.cells, [.empty(isLoading: false)])

Slide 27

Slide 27 text

XCTAssertEqual(state.cells, [ .repository(repository1), .repository(repository2), .repository(repository3), ... ])

Slide 28

Slide 28 text

ঢ়ଶΛݕূ͠΍͘͢ͳͬͨ

Slide 29

Slide 29 text

ঢ়ଶΛݕূ͠΍͘͢ͳͬͨ ʢ࣮ࡍͷදࣔ͸ݕূ͠ͳ͍͔Βʣ

Slide 30

Slide 30 text

"1*$MJFOU 3FQPTJUPSJFT7JFX$POUSPMMFS 6*$PMMFDUJPO7JFX 3FQPTJUPSZ$FMM &NQUZ$FMM 3FQPTJUPSZ 6*ςετ

Slide 31

Slide 31 text

"1*$MJFOU 3FQPTJUPSJFT7JFX$POUSPMMFS 6*$PMMFDUJPO7JFX 3FQPTJUPSZ$FMM &NQUZ$FMM 3FQPTJUPSZ ಺෦ঢ়ଶͷϢχοτςετ

Slide 32

Slide 32 text

6*ςετ͸͍Βͳ͍ʁ

Slide 33

Slide 33 text

ͦΜͳ͜ͱ͸ͳ͍

Slide 34

Slide 34 text

w 6*ςετͰͳ͚Ε͹ݕূͰ͖ͳ͍͜ͱ΋͋Δ w ঢ়گʹԠͯ͡࢖͍෼͚Δͱྑ͍

Slide 35

Slide 35 text

λΠϜϥΠϯͷ੍ޚ͕೉͍͠

Slide 36

Slide 36 text

Ծ૝্࣌ؒͰΠϕϯτΛѻ͏

Slide 37

Slide 37 text

3Y4XJGU

Slide 38

Slide 38 text

3FBDUJWF4XJGU

Slide 39

Slide 39 text

let starredIndex = scheduler.createHotObservable([ next(1, 3), next(2, 4), next(3, 5), ])

Slide 40

Slide 40 text

Ծ૝࣌ؒ͸୯ͳΔ਺஋ͳͷͰ ࣗ༝ʹ੍ޚͰ͖Δ

Slide 41

Slide 41 text

Ծ૝ͳͷͰඵ΋Ұॠ

Slide 42

Slide 42 text

݁Ռ΋Ծ૝࣌ؒͰݕূͰ͖Δ

Slide 43

Slide 43 text

XCTAssertEqual(cells.events, [ next(0, [ ... .repository(repository4), .repository(repository5), .repository(repository6), ... ]), next(1, [ ... .repository(repository4Starred), .repository(repository5), .repository(repository6), ... ]), next(2, [ ... .repository(repository4Starred), .repository(repository5Starred), .repository(repository6), ... ]), next(3, [ ... .repository(repository4Starred), .repository(repository5Starred), .repository(repository6Starred), ... ]), ])

Slide 44

Slide 44 text

ςετର৅ͷঢ়گΛ༻ҙͮ͠Β͍

Slide 45

Slide 45 text

ґଘΛϓϩτίϧʹͯ͠ελϒԽ͢Δ

Slide 46

Slide 46 text

protocol APIClient { func sendRequest(_ request: Request) -> Single } final class AppAPIClient: APIClient {...} final class TestAPIClient: APIClient {...}

Slide 47

Slide 47 text

protocol APIClient { func sendRequest(_ request: Request) -> Single } final class AppAPIClient: APIClient {...} final class TestAPIClient: APIClient {...} w ΞϓϦͰ͸ͪ͜ΒΛ࢖͏ w ࣮ࡍͷαʔόʔʹΞΫηε͢Δ

Slide 48

Slide 48 text

protocol APIClient { func sendRequest(_ request: Request) -> Single } final class AppAPIClient: APIClient {...} final class TestAPIClient: APIClient {...} w ςετͰ͸ͪ͜ΒΛ࢖͏ w ςετίʔυͰࢦఆͨ͠ϨεϙϯεΛฦ͢ w ࣮ࡍͷαʔόʔʹ͸ΞΫηε͠ͳ͍

Slide 49

Slide 49 text

ελϒ͚ͩͳΒ࣮૷͸؆୯ ʢϥΠϒϥϦΛ࢖ͬͯ΋ྑ͍͚Ͳʣ

Slide 50

Slide 50 text

final class TestAPIClient: APIClient { private var stubs = [] as [(request: Any, response: Any)] func stub(request: Request, response: Single) { stubs.append((request: request, response: response)) } func sendRequest(_ request: Request) -> Single { if let index = stubs.firstIndex(where: { ($0.request as? Request) == request }) { let stub = stubs.remove(at: index) return stub.response as! Single } else { return Single.error(RxError.unknown) } } }

Slide 51

Slide 51 text

final class TestAPIClient: APIClient { private var stubs = [] as [(request: Any, response: Any)] func stub(request: Request, response: Single) { stubs.append((request: request, response: response)) } func sendRequest(_ request: Request) -> Single { if let index = stubs.firstIndex(where: { ($0.request as? Request) == request }) { let stub = stubs.remove(at: index) return stub.response as! Single } else { return Single.error(RxError.unknown) } } } ελϒԽ͢ΔϦΫΤετͱ ϨεϙϯεͷϖΞΛొ࿥͓ͯ͘͠

Slide 52

Slide 52 text

final class TestAPIClient: APIClient { private var stubs = [] as [(request: Any, response: Any)] func stub(request: Request, response: Single) { stubs.append((request: request, response: response)) } func sendRequest(_ request: Request) -> Single { if let index = stubs.firstIndex(where: { ($0.request as? Request) == request }) { let stub = stubs.remove(at: index) return stub.response as! Single } else { return Single.error(RxError.unknown) } } } ϦΫΤετ͕དྷͨ࣌ʹελϒͱ
 Ϛον͢Δ΋ͷ͕͋Ε͹ฦ͢

Slide 53

Slide 53 text

let apiClient = TestAPIClient() apiClient.stub( request: ListRepositoriesRequest(), response: Single .just(ListRepositoriesResponse(repositories: [])) .delay(5, scheduler: scheduler)) let viewController = RepositoriesViewController(apiClient: apiClient)

Slide 54

Slide 54 text

ςετέʔε͝ͱʹ ೚ҙͷϨεϙϯε͕ฦͤΔ

Slide 55

Slide 55 text

ৼΓฦΓ

Slide 56

Slide 56 text

6*ςετ͸࣮૷΋࣮ߦ΋ߴίετ
 ˠঢ়ଶΛݕূ͠΍͍͢ϞσϧͰදݱ͢Δ λΠϜϥΠϯͷ੍ޚ͕೉͍͠
 ˠԾ૝্࣌ؒͰΠϕϯτΛѻ͏ ςετର৅ͷঢ়گΛ༻ҙͮ͠Β͍
 ˠґଘΛϓϩτίϧʹͯ͠ελϒԽ͢Δ

Slide 57

Slide 57 text

%FNP

Slide 58

Slide 58 text

ςετ͠΍͍͢౔ඨͰઓ͓͏"

Slide 59

Slide 59 text

IUUQTHJUIVCDPNJTILBXBJPT@NWWN@UFTU@FYBNQMF