Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
AbemaTVにおけるiOSアーキテクチャの課題解決 / Solving problems i...
Search
Yusuke Morishita
January 30, 2020
Technology
1
1.7k
AbemaTVにおけるiOSアーキテクチャの課題解決 / Solving problems in AbemaTV iOS architecture
AbemaTV iOSの5年の運用の中で発生した設計課題とその解決方法について
Yusuke Morishita
January 30, 2020
Tweet
Share
Other Decks in Technology
See All in Technology
2025 AWS Jr. Championが振り返るAWS Summit
kazukiadachi
0
110
NewSQLや分散データベースを支えるRaftの仕組み - 仕組みを理解して知る得意不得意
hacomono
PRO
2
140
生成AI時代の開発組織・技術・プロセス 〜 ログラスの挑戦と考察 〜
itohiro73
1
460
オーティファイ会社紹介資料 / Autify Company Deck
autifyhq
10
130k
Flutter向けPDFビューア、pdfrxのpdfium WASM対応について
espresso3389
0
130
PO初心者が考えた ”POらしさ”
nb_rady
0
210
いつの間にか入れ替わってる!?新しいAWS Security Hubとは?
cmusudakeisuke
0
120
B2C&B2B&社内向けサービスを抱える開発組織におけるサービス価値を最大化するイニシアチブ管理
belongadmin
1
6.9k
MobileActOsaka_250704.pdf
akaitadaaki
0
120
Lazy application authentication with Tailscale
bluehatbrit
0
210
Operating Operator
shhnjk
1
580
freeeのアクセシビリティの現在地 / freee's Current Position on Accessibility
ymrl
2
190
Featured
See All Featured
KATA
mclloyd
30
14k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
16k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
126
53k
Testing 201, or: Great Expectations
jmmastey
43
7.6k
Docker and Python
trallard
44
3.5k
Art, The Web, and Tiny UX
lynnandtonic
299
21k
Designing for Performance
lara
610
69k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
34
5.9k
GraphQLとの向き合い方2022年版
quramy
49
14k
What's in a price? How to price your products and services
michaelherold
246
12k
GraphQLの誤解/rethinking-graphql
sonatard
71
11k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
8
820
Transcript
"CFNB57ʹ͓͚Δ J04ΞʔΩςΫνϟͷ՝ղܾ .77. 6OJP 'MVY :VTVLF.PSJTIJUB!ZZTTLL "CFNB57։ൃہωΠςΟϒج൫
"HFOEB wϓϩμΫτഎܠ wઃܭͷมભ wݱࡏͷ՝
ϓϩμΫτഎܠ
ϓϩμΫτഎܠ w݄͔Β։࢝ͯ͠ݱࡏ wݱࡏ໊Ͱ։ൃ wϨϙδτϦ wJ04"QQ "1*ΫϥΠΞϯτ ϩάͷϞσ ϧʜFUD
ϓϩμΫτഎܠνʔϜਓ ࣌ؒ ਓ ্ཱͪ͛࣌
ઃܭͷมભ
ઃܭͷมભ wୈ̍ੈ'MVY wୈ̎ੈ.77. 'MVY wୈ̏ੈ.77. 6OJP 'MVY
ઃܭͷมભ$POUSJCVUJPOHSBQI ୈ̍ੈ ୈ̎ੈ ୈ̏ੈ
ઃܭͷมભ$POUSJCVUJPOHSBQI ୈ̍ੈ ୈ̎ੈ ୈ̏ੈ ॎԽɺϏσΦػೳ։࢝
ઃܭͷมભ$POUSJCVUJPOHSBQI ୈ̍ੈ ୈ̎ੈ ୈ̏ੈ ΞϓϦίΠϯɺ͛મػೳ։࢝
ઃܭͷมભνʔϜਓ ࣌ؒ ਓ ୈ̍ੈ ୈ̎ੈ ୈ̏ੈ
ୈ̍ੈ w࣌ظ wॳظϦϦʔε w.PUJWBUJPO wෳࡶͳঢ়ଶཧΛΘ͔Γ͘͢ཧ͍ͨ͠ wΫϥΠΞϯτؒ "OESPJEJ048FC ͰઃܭΛ౷Ұͨ͠ ͍
࠾༻'MVY 3Y4XJGU Facebook https://facebook.github.io/flux/docs/in-depth-overview/
ୈ̍ੈ'MVY 6*Πϕϯτ ঢ়ଶΛࢹͯ͠6*ө
ୈ̍ੈ"DUJPO func getElement() { dispatcher.isLoading.dispatch(true) APIClient.getElement() .do(onError: { [weak self]
error in self?.dispatcher.error(error) // Τϥʔ }) .do(onCompleted: { [weak self] in self?.dispatcher.isLoading.dispatch(false) // ྃ }) .subscribe(onNext: { [weak self] element in self?.dispatcher.element.dispatch(element) // ޭ }) .disposed(by: disposeBag) }
ୈ̍ੈ%JTQBUDIFS final class Dispatcher { static let shared = Dispatcher()
let element = DispatchSubject<Element>() let isLoading = DispatchSubject<Bool>() let error = DispatchSubject<Error>() }
ୈ̍ੈ4UPSF final class Store { static let shared = Store()
let isLoading: Property<Bool> let element: Property<Element> init(dispatcher: Dispatcher) { // dispatcher͔ΒྲྀΕ͖ͯͨΠϕϯτΛbind͢Δ } }
ୈ̍ੈ6* override func viewDidLoad() { super.viewDidLoad() // ActionʹΠϕϯτΛൃՐ Action.shared.getElement() //
ߋ৽͞Εͨঢ়ଶΛUIʹbind Store.shared.element.asObservable() .bind(to: titleLabel.rx.text) .diposed(by: disposeBag) }
ୈ̍ੈ'MVY 6*Πϕϯτ ঢ়ଶΛࢹͯ͠6*ө
ୈ̍ੈͷྑ͍ w7JFXؒͷґଘ͕ؔݮΔ w։ൃऀͷ࣮͕౷Ұ͞Ε͍͢ wσʔλͷྲྀΕ͕Θ͔Γ͍͢
ୈ̎ੈ w ࣌ظ w ݄dλςରԠͷ։ൃ։࢝ w ݄dϏσΦػೳͷ։ൃ։࢝
ୈ̍ੈͷ՝ w ػೳͷෳࡶԽʹΑΓɺ4UPSF6*ͷϥΠϑ αΠΫϧͷ߹ϩδοΫ͕7JFX$POUSPMMFS ʹ૿͑ͨ
ୈ̍ੈͷ՝ override func viewDidLoad() { super.viewDidLoad() // ঢ়ଶͷߋ৽·ͨλςϤίͷΓସ͑ΛϑοΫͯ͠tableViewΛϦϩʔυ Observable.combineLatest( Store.shared.repositories.asObservable(),
rx.traitCollectionDidChange.asObservable() ) .observeOn(MainScheduler.instance) .subscribe(onNext: { [weak self] _ in self?.tableView.reloadData() }) .disposed(by: disposeBag) … }
࠾༻.77. 7JFX.PEFM .PEFM 7JFX 7JFX$POUSPMMFS %BUBCJOEJOH
ୈ̎ੈ7JFX4USFBN
ୈ̎ੈ7JFX4USFBN final class ViewStream { // Output let element: Property<Element>
let isLoading: Property<Bool> init( // Input viewDidLayout: Observable<Void>, traitCollectionDidChange: Observable<Void>, // Dependency action: Action = .shared, store: Store = .shared ) { // InputͷΠϕϯτΛͱʹOutputΛ߹͢Δ } }
ୈ̎ੈͷྑ͍ w7JFX$POUSPMMFSͷ࣮Λ6*Πϕϯτͷൖͱঢ়ଶ Λ6*ʹόΠϯυ͢ΔͷΈʹͰ͖ͨ wϓϨθϯςʔγϣϯϩδοΫΛ6*͔ΒͰ͖ɺ ୯ମςετָ͕ʹ͔͚Δ wάϩʔόϧͰอ࣋͢Δඞཁ͕ͳ͍ը໘ͷҰ࣌ঢ়ଶ Λ6*ͷϥΠϑαΠΫϧͰഁغͰ͖Δ
ઃܭͷมભνʔϜਓ ࣌ؒ ਓ ୈ̍ੈ ୈ̎ੈ ୈ̏ੈ
ୈ̎ੈͷ՝ w 7JFX4USFBNͷ࣮ํ๏͕࣮ऀʹΑͬͯ Β͖͕ͭ͋Δ
ୈ̎ੈͷ՝ class SearchViewStream { // Output let repositories: Property<[Repository]> private
let _repositories = BehaviorRelay<[Repository]>(value: []) private let _search = PublishRelay<String>() private let disposeBag = DisposeBag() init( // Dependency action: Action = .shared store: Store = .shared ) { // Input ⇨ Output Logic } // Input func search(_ text: string) { _search.accept(text) } }
ୈ̎ੈͷ՝ class SearchViewStream { // Output let repositories: Property<[Repository]> private
let _repositories = BehaviorRelay<[Repository]>(value: []) private let _search = PublishRelay<String>() private let disposeBag = DisposeBag() init( // Dependency action: Action = .shared store: Store = .shared ) { // Input ⇨ Output Logic } // Input func search(_ text: string) { _search.accept(text) } }
ୈ̎ੈͷ՝ class SearchViewStream { // Output let repositories: Property<[Repository]> private
let _repositories = BehaviorRelay<[Repository]>(value: []) private let disposeBag = DisposeBag() init( // Input search: Observable<String> // Dependency action: Action = .shared store: Store = .shared ) { // Input ⇨ Output Logic } }
ୈ̎ੈͷ՝ class SearchViewStream { // Input let search = PublishRelay<String>()
// Output let repositories: Property<[Repository]> private let _repositories = BehaviorRelay<[Repository]>(value: []) private let _search = PublishRelay<String>() private let disposeBag = DisposeBag() init( // Dependency action: Action = .shared store: Store = .shared ) { // Input ⇨ Output Logic } }
ߟҊ6OJP $SFBUFECZNBSUZTV[VLJ
ୈ̏ੈ w ࣌ظ w "CFNBίΠϯػೳͷ։ൃ w "CFNBαϙʔτ ͛મ ػೳͷ։ൃ
ୈ̏ੈ6OJP class SearchViewModel: UnioStream<SearchViewModel>, SearchViewModelType { struct Input: InputType {
let search = PublishRelay<String>() } typealias State = NoState struct Output: OutputType { let repositories: Observable<[Repository]> } struct Extra: ExtraType { let action: Action let store: Store } static func bind(from dependency: Dependency<Input, State, Extra>, disposeBag: DisposeBag) -> Output { // Dependency ⇨ Output Logic return Output(repositories: ${repositoriesΛฦ͢ม}) } }
ୈੈͷྑ͍ w7JFX.PEFMͷ࣮ͷ౷Ұ͕࣮ݱͰ͖ͨ w7JFX.PEFMͷϩδοΫͷྲྀΕ͕୯Ұํͳ ͷͰՄಡੑ্͕͕ͬͨ
ୈ̏ੈ6OJP
ݱࡏͷ՝
ݱࡏͷ՝ wਐԽ͍ͯ͘͠աఔͰલੈͷ࣮͕͍ͬͯΔ wୈ̍ੈͷ࣮ͰҰ෦'MVYͰ4UPSFUP4UPSF ͳ࣮ʹͳ͍ͬͯͯॲཧͷྲྀΕ͕ෳࡶԽ͍ͯ͠Δ wը໘ͷ୯ମىಈ͕Ͱ͖ͳ͍࣮ʹͳ͍ͬͯΔ w'MVYͷ"DUJPO͕3FTPVSDFࢀর͍ͯ͠Δ
·ͱΊ wઃܭͷมߋͷλΠϛϯάશػೳΛ৽͢ Δͱ͖େ͖ͳػೳ։ൃ͕ೖΔͱ͖ wॳظͷίϯηϓτ͔Βൃੜͨ͠՝ʹରͯ͠ ղܾ͢Δํ๏Λ৽ͨ͠ʹऔΓೖΕͯมԽ ͖ͯͨ͠
ࢀর w 'MVYXJUI3Y4XJGU w IUUQTTQFBLFSEFDLDPNEFLBUPUPSPqVYXJUISYTXJGU w .77. 'MVY w IUUQTTQFBLFSEFDLDPNNBSUZTV[VLJNWWNQMVTqVY
w "CFNBJ04"SDIJUFDUVSF w IUUQTTQFBLFSEFDLDPNUPJLJBCFNBJPTBSDIJUFDUVSF w 6OJP w IUUQTTQFBLFSEFDLDPNNBSUZTV[VLJNWWNGBMTFTIJ[IVBOHXPGVSVGSBNFXPSLXPLBJGBEBP SVTJUJNVEFCBSBUVLJHBBUVUBTIJ[IVBOHXPUPOHTVSV