Slide 1

Slide 1 text

8IBU8IZ)PX.77. !OVNB 1

Slide 2

Slide 2 text

ࣗݾ঺հ 2

Slide 3

Slide 3 text

࿩͢͜ͱ 3 8IBU .77.ͱ͸Կͳͷ͔ʁ 8IZ .77.ΛԿނબΜͩͷ͔ʁ )PX .77.ΛͲ͏࣮૷ͨ͠ͷ͔ʁ 5JQTͳͲ

Slide 4

Slide 4 text

࿩͢͜ͱ 4 8IBU .77.ͱ͸Կͳͷ͔ʁ 8IZ .77.ΛԿނબΜͩͷ͔ʁ )PX .77.ΛͲ͏࣮૷ͨ͠ͷ͔ʁ 5JQTͳͲ ✅

Slide 5

Slide 5 text

8IBU .77.ΞʔΩςΫνϟ͸ը໘ͷඳըॲཧͱϓϨθϯςʔγϣϯϩδοΫͱΛ෼཭͢Δ(6*Ξʔ ΩςΫνϟͰ͢ɻ(6*ߏ଄Λ.PEFM7JFX7JFX.PEFMͷ̏ͭʹ෼͚ɺը໘ͷඳըॲཧΛ7JFX ʹɺը໘ඳըϩδοΫΛ7JFX.PEFMͱ͍͏ίϯϙʔωϯτʹด͡ࠐΊ·͢ɻ ͦͯ͠ɺ7JFXͱ7JFX.PEFMΛσʔλόΠϯσΟϯάͱݺ͹ΕΔ࢓૊ΈͰؔ࿈෇͚Δ͜ͱͰɺ 7JFX.PEFMͷঢ়ଶมߋʹಉظͯ͠7JFXͷঢ়ଶ΋ߋ৽͞Εɺը໘ʹ൓ө͞ΕΔ͜ͱ͕ಛ௃Ͱ͢ɻ 5 J04ΞϓϦઃܭύλʔϯೖ໳ୈষ.77.ΑΓҾ༻

Slide 6

Slide 6 text

6*
 &WFOU 8IBU 6 7JFX 7JFX
 .PEFM .PEFM %BUB
 #JOEJOH

Slide 7

Slide 7 text

8IBU w 7JFX w 6*7JFX΍6*7JFX$POUSPMMFSͷαϒΫϥεͰ࣮૷͞ΕΔ w Ϣʔβʔ͕ൃੜͤ͞ΔΞΫγϣϯ΍6*7JFX$POUSPMMFS΍ΞχϝʔγϣϯͷίʔϧόοΫΛड͚औͬͯ 7JFX.PEFMʹ௨஌͢Δɻ w 7JFX.PEFM͕௨஌ͨ͠಺༰Λը໘ʹඳը͢Δ w 7JFX.PEFM w 6*Ͱൃੜͨ͠ΠϕϯτΛड͚औͬͯ.PEFMͰ࣮૷͞ΕͨϏδωεϩδοΫͳͲΛݺͼग़͢ w .PEFM͔ΒಘΒΕͨσʔλΛը໘ͰදࣔͰ͖Δܗࣜʹม׵ͯ͠7JFXʹ఻͑Δ w .PEFM w ϏδωεϩδοΫͷ࣮૷΍%# 8FC"1*ͳͲͷ֎෦γεςϜͷ࿈ܞΛ࣮૷͢Δ w 7JFX.PEFM͔Β఻͑ΒΕͨΠϕϯτ΍ଞͷ.PEFMͰൃੜͨ͠ΠϕϯτΛτϦΨʔʹͯ͠ॲཧΛ։࢝͢Δ w ॲཧͷ݁ՌΛ7JFX.PEFMʹ௨஌͢Δ 7

Slide 8

Slide 8 text

࿩͢͜ͱ 8 8IBU .77.ͱ͸Կͳͷ͔ʁ 8IZ .77.ΛԿނબΜͩͷ͔ʁ )PX .77.ΛͲ͏࣮૷ͨ͠ͷ͔ʁ 5JQTͳͲ ✅

Slide 9

Slide 9 text

8IZ w ͦͦ͜͜ͷن໛ͷϓϩδΣΫτ w ͷը໘ w ϝϯόʔ͸࠷େਓ͘Β͍ 9

Slide 10

Slide 10 text

8IZ w ίϯϑϦΫτΛ๷͍Ͱࣗ෼͕࣮૷Λ͍ͯ͠Δը໘΍ ػೳʹूத͍ͨ͠ w ϝϯόʔ͕૿͑ͯ΋ίʔυཧղ΁ͷίετΛ཈͑ͨ ͍ w ύλʔϯԽʹΑͬͯɺ৽ػೳ௥Ճ΍όάमਖ਼ͷ໨҆ Λཱͯ΍͍ͨ͘͢͠ 10

Slide 11

Slide 11 text

6*
 &WFOU 8IBU 11 7JFX 7JFX
 .PEFM .PEFM %BUB
 #JOEJOH

Slide 12

Slide 12 text

8IZ w ίϯϙʔωϯτ͕෼ׂ͞Ε͍ͯΔͷͰίϯϑϦΫτ ͕ى͜Γʹ͍͘ w ϨΠΞ΢τͱϩδοΫ࣮૷Λฒߦͯ͠Ͱ͖Δ w ίʔυ͕ύλʔϯԽ͢ΔͷͰɺֶशίετ͕௿͍ w ίʔυ͕ύλʔϯԽ͢ΔͷͰ৽نػೳ࡞੒΍όά͕ ى͖ͨͱ͖ʹݟΔ΂͖৔ॴ͕Θ͔Γ΍͍͢ 12

Slide 13

Slide 13 text

࿩͢͜ͱ 13 8IBU .77.ͱ͸Կͳͷ͔ʁ 8IZ .77.ΛԿނબΜͩͷ͔ʁ )PX .77.ΛͲ͏࣮૷ͨ͠ͷ͔ʁ 5JQTͳͲ ✅

Slide 14

Slide 14 text

)PX w 'SBNFXPSLΛ෼͚ͨ w ͢΂ͯͷը໘ͷ6*7JFX$POUSPMMFS7JFX.PEFM 4UPSZCPBSEΛ༻ҙͨ͠ w .PEFMͷઃܭʹ͍ͭͯΑ͘࿩͠߹͍ɺ1SPUPDPM ͱ&OUJUZΛ࡞ͬͨ 14

Slide 15

Slide 15 text

'SBNFXPSLΛ෼͚ͨ w ૝ఆ͠ͳ͍ґଘ͕ੜ·Εͳ͍Α͏ʹ͢ΔͨΊ w FY.PEFMͰ6*Λૢ࡞͍ͯͨ͠Β͓͔͍͠ w Ϗϧυͷߴ଎ԽΛૂͬͨ໘΋͋Δ 15

Slide 16

Slide 16 text

'SBNFXPSLΛ෼͚ͨ w "1*$MJFOU w 4XBHHFSͰੜ੒Λͨ͠"1*ͷΫϥΠΞϯτίʔυ w .PEFM w .PEFM૚΍&OUJUZ 7JFX.PEFMΛؚΉ'SBNFXPSL w 6*,JU΍3Y$PDPBʹ͸جຊతʹґଘ͠ͳ͍ w "QQ w 7JFXʹؔ͢ΔίʔυΛؚΉ'SBNFXPSL w ը૾ͳͲͷϦιʔεΉؚΉ w .PEFMʹґଘ͸͢Δ͕.PEFMͷ࣮ଶʹ͸৮ΒͣʹQSPUPDPMΛ࢖͏ w .BJO w "QQ%FMFHBUFΛؚΜͩ'SBNFXPSL 16

Slide 17

Slide 17 text

7JFX.PEFMͷ࣮૷ public class HogeViewModel { // ύϥϝʔλʹड͚औΔߏ଄ମ // View Ͱൃੜ͢ΔΠϕϯτΛ௨஌͢Δ public struct Inputs { public let tapButton: Single<()> public let inputText: Single public let viewDidLoad: Signal<()> } // ฦΓ஋ͱͯ͠ฦ͢ߏ଄ମ // Model Ͱൃੜͨ͠ΠϕϯτͳͲΛ௨஌ͯ͠ // View Ͱඳը͢Δ public struct Outputs { public let title: Driver public let contents: Driver<[Item]> } 17

Slide 18

Slide 18 text

7JFX.PEFMͷ࣮૷ private let commentModel: CommentModelProtocol private let disposeBag = DisposeBag() public func bind(_ inputs: Inputs) -> Outputs { // UI Ͱൃੜͨ͠ΠϕϯτΛτϦΨʔʹͯ͠ // Model ͷϝιουΛݺͼग़ͯ͠ॲཧ͢Δ inputs .tapButton .withLatestFrom(inputs.inputText) .emit(onNext: { [weak self] text in self?.commentModel.post(comment: text) }) .disposed(by: disposeBag) // ൃੜ͢ΔΠϕϯτ͸Ϣʔβʔͷಈ࡞ʹՃ͑ͯ // UIViewController ͷίʔϧόοΫͷ͜ͱ΋͋Δ inputs .viewDidLoad .emit(onNext: commentModel.fetchComment) .disposed(by: disposeBag) 18

Slide 19

Slide 19 text

7JFX.PEFMͷ࣮૷ return .init( // Model ͔ΒಘΒΕͨσʔλΛը໘ͰදࣔͰ͖Δܗࣜʹ // ม׵ͯ͠ View ʹ௨஌͢Δ contents: commentModel .comments .asDriver() ) } 19

Slide 20

Slide 20 text

7JFX.PEFMͷ࣮૷ public class HogeViewModel { // ೖྗ public struct Inputs { public let tapButton: Single<()> public let inputText: Single public let viewDidLoad: Signal<()> } // ग़ྗ public struct Outputs { public let title: Driver public let contents: Driver<[Item]> } public func bind(_ inputs: Inputs) -> Outputs { // ೖྗΠϕϯτΛग़ྗΠϕϯτʹม׵͢Δ } } 20

Slide 21

Slide 21 text

7JFX.PEFMͷ࣮૷ w *OQVUT w 6*ૢ࡞΍6*7JFX$POUSPMMFSͷίʔϧόοΫͳͲ6*Ͱൃੜ͢ΔΠϕ ϯτΛ0CTFSWBCMFͰ7JFX.PEFMʹ௨஌͢Δ w 0VUQVUT w *OQVUTͰ௨஌͞ΕͨΠϕϯτ΍.PEFMͰ௨஌͞ΕͨΠϕϯτΛ 7JFXͰදࣔͰ͖Δܗࣜͱͯ͠7JFXʹ௨஌͢Δ w CJOE w *OQVUTΛύϥϝʔλʹऔͬͯ.PEFMͷૢ࡞ΛߦͬͨΓ.PEFM͔Β σʔλΛऔಘͯ͠0VUQVUTΛฦ͢ 21

Slide 22

Slide 22 text

7JFX$POUSPMMFSͷ࣮૷ final class HogeViewController: UIViewController { // ը໘͕ભҠ͢Δલʹલͷը໘͔Β ViewModel ͷΠϯελϯεΛηοτͯ͠΋Β͏ var viewModel: HogeViewModel! override func viewDidLoad() { super.viewDidLoad() // UIKit Ͱൃੜͨ͠Πϕϯτ΍ɺίʔϧόοΫΛ ViewModel ʹ఻͑Δ let outputs = viewModel.bind(.init( tapButton: button.rx.tap.asSignal(), inputText: text.rx.value.orEmpty.asSignal(), viewDidLoad: .just() )) // ViewModel ͕௨஌ͨ͠σʔλΛը໘ʹදࣔ͢Δ outputs .title .drive(rx[\.title]).disposed(by: disposeBag) } } 22

Slide 23

Slide 23 text

7JFX$POUSPMMFSͷ࣮૷ w WJFX%JE-PBE @ Ͱ7JFX.PEFMͷCJOEΛ࣮ߦ͢ Δ w 6*͕ൃੜͤ͞ΔΠϕϯτ΍6*7JFX$POUSPMMFS ͷίʔϧόοΫͳͲΛ0CTFSWBCMFԽͯ͠ *OQVUTΛੜ੒ w CJOE *OQVU Λ࣮ߦ w 0VUQVUTΛ6*ͷදࣔʹ݁ͼ͚ͭΔ 23

Slide 24

Slide 24 text

.PEFMͷఆٛ public protocol ContentsModelProtocol { // σʔλͷग़ྗ͸ϓϩύςΟͰఆٛΛ͓ͯ͘͠ public var contents: Observable<[Content]> { get } // id ͳͲͰϑΟϧλʔ͕ඞཁͳ৔߹͸ϝιουͱ͓ͯ͘͠ public func postResult(for contentId: ContentId) -> Observable> // σʔλΛऔಘ͢Δϝιουɻ݁Ռ͸ `contents` ͷೖͬͯ͘Δ public func fetchContents() // σʔλͷॻ͖ࠐΈΛߦ͏ϝιουɻ੒ޭɾࣦഊ͸ `postresult(for:)Ͱऔಘ͢Δ` public func post(content: Content) } 24

Slide 25

Slide 25 text

.PEFMͷ࣮૷ public class ContentsModel: ContentsModelProtocol { // Observable ͷ࣮ଶ͸ BehaviorRelay ͱ͓ͯ͘͠ͱɺͱΓ͋͑ͣ࠷ॳͷ஋Λ௨஌͢Δ͜ͱ͕Ͱ͖Δ // ࠷ޙͷ஋΋ relay ͯ͘͠ΕΔͷͰɺ UI ͰදࣔΛ͢Δͱ͖ʹ౎߹͕ྑ͍ private let contentsSubject = BehaviorRelay<[Content]>(value: []) // Model ͷ֎ଆ͔Βҙਤ͠ͳ͍มߋΛͤ͞ͳ͍ͨΊʹ΋ɺ֎෦ʹ͸ Observable ͱͯ͠ެ։͢Δ public var contents: Observable<[Content]> { return contentsSubject.asObservable() } public func fetchContents() { apiClient .fetchContents // Single Ͱ݁Ռ͕௨஌͞ΕΔ .subscribe(onSuccess: contentsSubject.accept) // ݁ՌΛ௨஌͢Δ .disposed(by: disposeBag) } 25

Slide 26

Slide 26 text

.PEFMͷ࣮૷ // Result Λ௨஌͢Ε͹ ViewModel Ͱ੒ޭɾࣦഊͷϋϯυϦϯά͕Ͱ͖Δ private let postResultSubject = PublishSubject>() // ಛఆͷ݁Ռ͚ͩʹڵຯ͕͋ΔͷͰɺ filter Λ࢖͏ public func postResult(for contentId: ContentId) -> Observable> { return postResultSubject.filter { $0 == contentId }.asObservable() } public func post(content: Content) { apiClient .post(content: Content) // Single Ͱ݁Ռ͕ಘΒΕΔ .map { Result in .success($0) } .catchError { e in .just(.failure(e)) } .subscribe(onSuccess: postResultSubject.accept) // ݁ՌΛ௨஌͢Δ .disposed(by: disposeBag) } 26

Slide 27

Slide 27 text

.PEFMͷ࣮૷ w ϝιου w "1*΁ΞΫηεΛ͢ΔͳͲͷಈ࡞͸ϝιουͰදݱΛ͢Δ w "1*ݺͼग़͠ͷ݁Ռ͸4JOHMFͰಘΒΕΔ͜ͱ͕ଟ͍ͷͰɺແݶͷ σʔλͷ0CTFSCBMF΁ม׵Λ͢Δඞཁ͕͋Δ w ϓϩύςΟ w σʔλͷऔΓग़͠͸ϓϩύςΟͰߦ͏ w ແݶͷ0CTFSWBCMFͱͯ͠௨஌͞Εͯ͘Δ w pMUFS͕ඞཁͳ৔߹͸ϝιουͱ͢Δ͚ΕͲɺجຊతͳߟ͑ํ͸ಉ͡ 27

Slide 28

Slide 28 text

ը໘ભҠͷ࣮૷ w ભҠݩͷ7JFX.PEFMͰભҠઌ7JFX.PEFMͷΠϯ ελϯεΛੜ੒͢Δ w 7JFXͰ͸7JFX.PEFMͷੜ੒͸ߦΘͳ͍ w ભҠݩ6*7JFX$POUSPMMFSʹભҠઌ7JFX.PEFMΛ ௨஌͢Δ w ભҠݩ6*7JFX$POUSPMMFS͸ભҠઌ 6*7JFX$POUSPMMFSʹભҠ͢Δ 28

Slide 29

Slide 29 text

.PEFMͷ࣮૷ // ViewModel func bind(_ inputs: Inputs) -> Outputs { return .init( // ϘλϯͷλοϓΛτϦΨʔͱͯ࣍͠ͷը໘ͷ ViewModel ͷΠϯελϯεΛੜ੒͢Δ // Model ͷΠϯελϯε΋ඞཁͩͬͨΓ͢ΔͷͰɺ init Ͱ౉͢ goToNextPage: inputs .tapNextButton .map { _ in NextPageViewModel(model: NextPageMode.instance) } ) } // ViewController func viewDidLoad() { outputs .goToNextPage() // ViewModel ͕௨஌͞ΕͨΒ࣍ͷը໘΁ભҠ͢Δ .drive(onNext: { [weak self] vm in let next = NextPageViewController() next.viewModel = vm self?.present(next, animated: true) }) .disposed(by: disposeBag) } 29

Slide 30

Slide 30 text

࿩͢͜ͱ 30 8IBU .77.ͱ͸Կͳͷ͔ʁ 8IZ .77.ΛԿނબΜͩͷ͔ʁ )PX .77.ΛͲ͏࣮૷ͨ͠ͷ͔ʁ 5JQTͳͲ

Slide 31

Slide 31 text

࿩ͨ͜͠ͱ 31 8IBU 7JFX7JFX.PEFM.PEFM
 ʹίϯϙʔωϯτΛ෼͚ͨΞʔΩςΫνϟ 8IZ ͦͦ͜͜ͷن໛ͷը໘ͱϝϯόʔ
 Ͱͷ։ൃʹ଱͑ΔͨΊ )PX 'SBNFXPSLͷ෼ׂ
 7JFX7JFX.PEFM.PEFMͷύλʔϯԽ

Slide 32

Slide 32 text

5JQT 32

Slide 33

Slide 33 text

7JFX.PEFM6*,JUͱ͔ 3Y$PDPBʹґଘ͍ͨ͠ w 6*,JU΍3Y$PDPB΁ͷґଘΛ׬શʹഉআ͢Δͷ͸ ແཧ w 6*$PMPSͳΜ͔Λ0VUQVUT͍ͨ͠ w %SJWFS4JHOBM͸3Y$PDPBͰఆٛ͞Ε͍ͯΔ w ΨνͰ΍Ζ͏ͱ͢Δͱ6*,JU΍3Y$PDPBΛந৅ Խ͢Δ૚Λ༻ҙ͢Δඞཁ͕͋ͬͯɺ͔ͳΓେมͦ͏ 33

Slide 34

Slide 34 text

7JFX.PEFM6*,JUͱ͔ 3Y$PDPBʹґଘ͍ͨ͠ // ૂͬͨ Struct/Class/ఆ਺/enum ͷΈΛ import Ͱ͖Δ import struct RxCocoa.Signal import class UIKit.UIColor import var Photos.PHImageManagerMaximumSize 34

Slide 35

Slide 35 text

7JFX.PEFM͔ΒϦιʔεΛ ݟ͍ͨ w 0VUQVUT͔ΒϦιʔεʹؔ͢Δ৘ใΛ௨஌͍ͨ͠ w ৭ɺϑΥϯτɺଟݴޠରԠFUDʜ w Ϧιʔεʹؔ͢Δ৘ใ͸7JFX.PEFMͷ 'SBNFXPSL͔Β͸ࢀর͕Ͱ͖ͳ͍ 35

Slide 36

Slide 36 text

7JFX.PEFM͔ΒϦιʔεΛ ݟ͍ͨ // ViewModel public struct Outputs { // View ʹ͸৭Λ௨஌͍͕ͨ͠ɺ৭Ϧιʔε͸ App Ͱఆٛ͞Ε͍ͯΔ let backgroundColor: Driver } // ϓϩτίϧΛఆٛͯ͠৭ΛऔಘͰ͖ΔΑ͏ʹ͢Δ public protocol ColoConvertable { var color: UIColor { get } } // ৭ͷ৘ใʹม׵Λ͍ͨ͠σʔλͷ extension ͷதͰ ColorConvertable Λ࣮૷͍ͯ͠Δ // ͱ͖͚ͩਖ਼͘͠ಈ࡞͢ΔΑ͏ʹ͢Δ extension ItemError { var color: UIColor { guard let converter = self as? ColorConvertable else { fatalError("ColorConvertable Λ࣮૷͍ͯͩ͘͠͞") } return converter.color } } func bind(_ inputs: Inputs) -> Outputs { return .init(backgroundColor: itemModel .itemError .map { $0.color } // ColorConvertable Λ࢖ͬͯ৭৘ใΛͱΓͩ͢ .asDriver() ) } 36

Slide 37

Slide 37 text

7JFX.PEFM͔ΒϦιʔεΛ ݟ͍ͨ // ViewController // View ଆͰ͸ϦιʔεʹΞΫηε͕Ͱ͖ΔͷͰɺ৭ͷ৘ใΛฦ͢͜ͱ͕Ͱ͖Δ extension ItemError : ColorConvertable { var color: UIColor { switch { case .noItem: return .crimsonFireRed } } } func viewDidAppear() { // ViewModel ͱ bind ͢Δஈ֊Ͱ͸ͨͩ৭ͷ৘ใΛηοτ͢Ε͹͍͍ outputs.backgroundColor.drive(rx[\.backgroundColor]).disposed(by:disposeBag) } 37

Slide 38

Slide 38 text

6*"MFSU$POUSPMMFSΛ ࢖͍͍ͨ w 7JFX.PEFM͔Βͷ௨஌Λड͚ͯ 6*"MFSU$POUSPMMFSΛදࣔ͢Δ w 6*"MFSU"DUJPOͷ݁ՌΛ7JFX.PEFMʹ఻͑Δ 38

Slide 39

Slide 39 text

6*"MFSU$POUSPMMFSΛ ࢖͍͍ͨ let tapConfirmDelete = PublishRelay<()>() let inputs = Inputs( tapConfirmDelete: tapConfirmDelete.asSignal(), tapBottomDeleteButton: tapBottomDeleteButton.asSignal() ) let output = viewModel.bind(inputs: inputs) output.showDeleteConfirm.emit(onNext: { [weak self] _ in guard let self = self else { return } let alert = UIAlertController(title: "౤ߘΛ࡟আ͠·͔͢ʁ", message: nil, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Ωϟϯηϧ", style: .cancel, handler: nil)) alert.addAction(UIAlertAction(title: "࡟আ", style: .destructive) { _ in tapConfirmDelete.accept(()) alert.dismiss(animated: true, completion: nil) }) self.present(alert, animated: true, completion: nil) }).disposed(by: disposeBag) 39

Slide 40

Slide 40 text

7JFX.PEFM*OQVUTJOJU
 ͕QVCMJD͡Όͳ͍໰୊ public class HogeViewModel { // Struct ͳͷͰ init ͕ࣗಈੜ੒͞ΕΔ͕ // σϑΥϧτͷΞΫηεम০ࢠͱͳΔͷͰɺ Framework ͷҧ͏ // View ͔Β͸ init Λݺͼग़͢͜ͱ͕Ͱ͖ͳ͍ public struct Inputs { public let tapButton: Single<()> public let inputText: Single public let viewDidLoad: Signal<()> } 40

Slide 41

Slide 41 text

7JFX.PEFM*OQVUTJOJU
 ͕QVCMJD͡Όͳ͍໰୊ public class HogeViewModel { public struct Inputs { public let tapButton: Single<()> public let inputText: Single public let viewDidLoad: Signal<()> // ͦΕͳΒࣗ෼Ͱॻ͔͘͠ͳ͍͡Όͳ͍ public init( // ςετίʔυͰ༨ܭͳมߋΛ࢈·ͳ͍ͨΊʹɺ // σϑΥϧτͰ never Λ͚ͭΔΑ͏ʹͳͬͨ tapButton: Signal<()> = .never(), inputText: Signal = .never(), viewDidLoad: Signal<()> = .never() ) { self.tapButton = tapButton self.inputText = inputText self.viewDidLoad = viewDidLoad } } 41