Mobile Act OSAKA #8 https://mobileact.connpass.com/event/114158/
ブログ記事 https://usami-k.hatenablog.com/entry/2019/02/16/191850
ReactorKit Ͱςετ͘͢͢͠ΔUSAMI KosukeFenrir Inc.
View Slide
ࣗݾհ• Ӊࠤݟެี @usamik26• ϑΣϯϦϧגࣜձࣾ• iOS ΞϓϦ։ൃ Swift / C# (Xamarin)
தࠃʹߦ͖ͬͯͨ• ඈՊٕ• iOS ΞϓϦ։ൃϝϯόͱٕज़ަྲྀ• ϝϯόʔ ReactorKit Λ͍ͬͯͨ
ReactorKit ͱ• RxSwift• Fluxʢ୯ํϑϩʔʣ• ܰྔͳϑϨʔϜϫʔΫ
Design Goal• Testability : View ͱϩδοΫͷ• Start Small : ΞϓϦͷҰ෦͚ͩͰద༻Ͱ͖Δ• Less Typing : ίʔυΛෳࡶʹ͠ͳ͍
Testability• ςετՄೳੑɺςετ༰қੑ• ࠓճ Testability ʹͯ͠ ReactorKit ΛݟͯΈΔ
ߏਤ
View ͱ Reactor• View ͷ (1)• UI ૢ࡞ → Action ͱͯ͠ Reactor ʹ͢• Reactor ͷ• Action → ॲཧΛߦ͍ State Λੜ͢Δ• View ͷ (2)• State → UI ʹදࣔ͢Δ
ͷ• UIViewController ʹ View ͷ͚ͩΛ࣋ͨͤΔ• Reactor ผΦϒδΣΫτͱͯ͠ʢͳ͓ɺReactor UIKit Θͳ͍ʣ• ͜ΕʹΑΓ Testability ্͕͢Δ• ࠓճಛʹ View ͷ Testability ΛݟͯΈΔ
View ϓϩτίϧclass MyViewController: View {func bind(reactor: MyReactor) {// ͜͜Ͱ Reactor ͱͷόΠϯσΟϯάΛߦ͏// RxSwift Λ׆༻}}
Reactor ϓϩτίϧclass MyReactor: Reactor {enum Action {case myActioncase myActionWithArg(Int)}struct State {var myValue1 = ""var myValue2 = false}}
View ͷόΠϯσΟϯά : Actionfunc bind(reactor: MyReactor) {myButton.rx.tap.map { Reactor.Action.myAction }.bind(to: reactor.action).disposed(by: self.disposeBag)ʢͳ͓ɺreactor.action ActionSubjectʣ
View ͷόΠϯσΟϯά : Statefunc bind(reactor: MyReactor) {reactor.state.map { $0.myValue1 }.bind(to: myLabel.rx.text).disposed(by: self.disposeBag)ʢͳ͓ɺreactor.state Observableʣ
Reactor ͷॲཧ : mutate()• Action Λड͚औͬͯ Observable Λฦ͢• ۩ମతͳॲཧʢWeb API ࣮ߦɺσʔλϕʔεΞΫηεɺͳͲʣΛߦ͏ Observable Λੜ͢Δ• ࣮ࡍʹॲཧΛߦ͏͔Ͳ͏͔ͷஅͳͲ Reactor ͷ• ͜ΕʹΑΓɺView ଆͰ Action Λੜ͢Δ͔Ͳ͏͔Λஅ͠ͳ͍ͨΊɺView ͷ࣮͕όΠϯσΟϯά͚ͩͰ͢Ή
Reactor ͷॲཧ : reduce()• Mutation ͱલͷ State Λड͚औͬͯ State Λฦ͢• ॲཧͷ݁Ռͱલͷঢ়ଶΛߟྀͯ࣍͠ͷঢ়ଶΛܾΊΔͷ͕• ͜ΕʹΑΓɺView ଆͰલͷঢ়ଶΛߟྀ͢Δͱ͍ͬͨॲཧ͕ෆཁʹͳΔͨΊɺView ͷ࣮͕όΠϯσΟϯά͚ͩͰ͢Ή
View ͷ୯ମςετΛߟ͑Δ• ୯ମςετͰԿΛ͢Δ͖͔ʁ• ʮͦͷΦϒδΣΫτͷΛՌ͍ͨͯ͠Δ͔ʯΛςετ͢Δ• View ͷ (1) : UI ૢ࡞ → Action• View ͷ (2) : State → UI දࣔ
View ͷςετ : Action• ίʔυͰ UI ૢ࡞Λൃੜͤ͞Δ• myButton.sendActions(for .touchUpInside)• ͦͷ݁Ռͱͯ͠ɺReactor ʹ Action ΦϒδΣΫτ͕͞Εͨ͜ͱΛ֬ೝ͍ͨ͠• Ͳ͏ͬͯʁ ʹ Reactor ͷελϒΛ༻ҙ͢Ε͍͍• ࣮ ReactorKit Ͱελϒ͕͢Ͱʹ༻ҙ͞Ε͍ͯΔ
View ͷςετ : Actionfunc testMyAction() {let reactor = MyReactor()reactor.stub.isEnabled = truelet view = MyView()view.reactor = reactorview.myButton.sendActions(for .touchUpInside)XCTAssertEqual(reactor.stub.actions.last, .myAction)}
View ͷςετ : State• Γ Reactor ͷελϒΛ׆༻͢Δ• ίʔυͰ Reactor ͷ State มԽΛى͜͢• ͦͷ݁Ռͱͯ͠ɺUI ද͕ࣔมߋ͞Εͨ͜ͱΛ֬ೝ͢Δ
View ͷςετ : Statefunc testMyAction() {let reactor = MyReactor()reactor.stub.isEnabled = truelet view = MyView()view.reactor = reactorreactor.stub.state.value = MyReactor.State(myValue1: "abc")XCTAssertEqual(view.myLabel.text, "abc")}
View ͷςετ·ͱΊ• Կ͕ςετͰ͖ͨͷ͔ʁ : View ͕ΛՌ͍ͨͯ͠Δ͜ͱ• View ͷ࣮ͱͯ͠όΠϯσΟϯά͕ͪΌΜͱͰ͖͍ͯΔ͜ͱ• ͦͦςετඞཁʁ : Testability ্ͷͨΊγϯϓϧͳΦϒδΣΫτʹͳͬͨ݁Ռɺ୯७͗ͯ͢ςετෆཁͰͱ͍͏Ϩϕϧʹ• ࣮ࡍʹςετίʔυΛॻ͔͘Ͳ͏͔έʔεόΠέʔε
Reactor ͷ୯ମςετʢུ֓ʣ• ʮͦͷΦϒδΣΫτͷΛՌ͍ͨͯ͠Δ͔ʯΛςετ͢Δ• Reactor ͷ : Action → State• ίʔυͰ Action Λൃੜͤͯ͞ɺState มߋΛ֬ೝ͢Δ• Reactor View ͷґଘ͕ͳ͍ͨΊɺಠཱͯ͠ςετՄೳ
Reactor ͷ୯ମςετʢུ֓ʣ• Reactor ͷதͰ Web API ࣮ߦɺσʔλϕʔεΞΫηεɺͳͲ͕͋Δͱ୯ମςετ͠ʹ͘͘ͳΔ• ͦ͜ͰɺͦΕΒΛ Service ͱͯ͠ Reactor ͷ֎ʹΓग़͢• Service ΛελϒԽ͢Δ͜ͱͰ Reactor ͕୯ମςετՄೳ
·ͱΊ• ෳͷΛ͕࣋ͪͪͳ UIViewController ʹ͍ͭͯɺReactorKit Ͱ View ͱ Reactor ʹΛͨ͠• ͜ΕʹΑΓɺView Reactor ͕ςετՄೳʹͳͬͨ