Save 37% off PRO during our Black Friday Sale! »

AbemaTVにおけるiOSアーキテクチャの課題解決 / Solving problems in AbemaTV iOS architecture

AbemaTVにおけるiOSアーキテクチャの課題解決 / Solving problems in AbemaTV iOS architecture

AbemaTV iOSの5年の運用の中で発生した設計課題とその解決方法について

D876775710dce31f1793fa7056879c66?s=128

Yusuke Morishita

January 30, 2020
Tweet

Transcript

  1. "CFNB57ʹ͓͚Δ J04ΞʔΩςΫνϟͷ՝୊ղܾ .77. 6OJP  'MVY :VTVLF.PSJTIJUB!ZZTTLL "CFNB57։ൃہωΠςΟϒج൫

  2. "HFOEB wϓϩμΫτഎܠ wઃܭͷมભ wݱࡏͷ՝୊

  3. ϓϩμΫτഎܠ

  4. ϓϩμΫτഎܠ w೥݄͔Β։࢝ͯ͠ݱࡏ೥໨ wݱࡏ͸໊Ͱ։ൃ wϨϙδτϦ wJ04"QQ "1*ΫϥΠΞϯτ ϩάͷϞσ ϧʜFUD

  5. ϓϩμΫτഎܠνʔϜਓ਺    ࣌ؒ ਓ਺  ্ཱͪ͛࣌  

  6. ઃܭͷมભ

  7. ઃܭͷมભ wୈ̍ੈ୅'MVY wୈ̎ੈ୅.77. 'MVY wୈ̏ੈ୅.77. 6OJP  'MVY

  8. ઃܭͷมભ$POUSJCVUJPOHSBQI ୈ̍ੈ୅ ୈ̎ੈ୅ ୈ̏ੈ୅

  9. ઃܭͷมભ$POUSJCVUJPOHSBQI ୈ̍ੈ୅ ୈ̎ੈ୅ ୈ̏ੈ୅ ॎԽɺϏσΦػೳ։࢝

  10. ઃܭͷมભ$POUSJCVUJPOHSBQI ୈ̍ੈ୅ ୈ̎ੈ୅ ୈ̏ੈ୅ ΞϓϦ಺ίΠϯɺ౤͛મػೳ։࢝

  11. ઃܭͷมભνʔϜਓ਺    ࣌ؒ ਓ਺ ୈ̍ੈ୅ ୈ̎ੈ୅ ୈ̏ੈ୅

  12. ୈ̍ੈ୅ w࣌ظ wॳظϦϦʔε w.PUJWBUJPO wෳࡶͳঢ়ଶ؅ཧΛΘ͔Γ΍͘͢؅ཧ͍ͨ͠ wΫϥΠΞϯτؒ "OESPJEJ048FC ͰઃܭΛ౷Ұͨ͠ ͍

  13. ࠾༻'MVY 3Y4XJGU Facebook https://facebook.github.io/flux/docs/in-depth-overview/

  14. ୈ̍ੈ୅'MVY 6*Πϕϯτ ঢ়ଶΛ؂ࢹͯ͠6*΁൓ө

  15. ୈ̍ੈ୅"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) }
  16. ୈ̍ੈ୅%JTQBUDIFS final class Dispatcher { static let shared = Dispatcher()

    let element = DispatchSubject<Element>() let isLoading = DispatchSubject<Bool>() let error = DispatchSubject<Error>() }
  17. ୈ̍ੈ୅4UPSF final class Store { static let shared = Store()

    let isLoading: Property<Bool> let element: Property<Element> init(dispatcher: Dispatcher) { // dispatcher͔ΒྲྀΕ͖ͯͨΠϕϯτΛbind͢Δ } }
  18. ୈ̍ੈ୅6* override func viewDidLoad() { super.viewDidLoad() // ActionʹΠϕϯτΛൃՐ Action.shared.getElement() //

    ߋ৽͞Εͨঢ়ଶΛUIʹbind Store.shared.element.asObservable() .bind(to: titleLabel.rx.text) .diposed(by: disposeBag) }
  19. ୈ̍ੈ୅'MVY 6*Πϕϯτ ঢ়ଶΛ؂ࢹͯ͠6*΁൓ө

  20. ୈ̍ੈ୅ͷྑ͍఺ w7JFXؒͷґଘؔ܎͕ݮΔ w։ൃऀͷ࣮૷͕౷Ұ͞Ε΍͍͢ wσʔλͷྲྀΕ͕Θ͔Γ΍͍͢

  21. ୈ̎ੈ୅ w ࣌ظ w ೥݄dλςରԠͷ։ൃ։࢝ w ೥݄dϏσΦػೳͷ։ൃ։࢝

  22. ୈ̍ੈ୅ͷ՝୊ w ػೳͷෳࡶԽʹΑΓɺ4UPSF΍6*ͷϥΠϑ αΠΫϧͷ߹੒ϩδοΫ͕7JFX$POUSPMMFS ʹ૿͑ͨ

  23. ୈ̍ੈ୅ͷ՝୊ 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) … }
  24. ࠾༻.77. 7JFX.PEFM .PEFM 7JFX 7JFX$POUSPMMFS %BUBCJOEJOH

  25. ୈ̎ੈ୅7JFX4USFBN

  26. ୈ̎ੈ୅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Λ߹੒͢Δ } }
  27. ୈ̎ੈ୅ͷྑ͍఺ w7JFX$POUSPMMFSͷ࣮૷Λ6*Πϕϯτͷ఻ൖͱঢ়ଶ Λ6*ʹόΠϯυ͢ΔͷΈʹͰ͖ͨ wϓϨθϯςʔγϣϯϩδοΫΛ6*͔Β෼཭Ͱ͖ɺ ୯ମςετָ͕ʹ͔͚Δ wάϩʔόϧͰอ࣋͢Δඞཁ͕ͳ͍ը໘ͷҰ࣌ঢ়ଶ Λ6*ͷϥΠϑαΠΫϧͰഁغͰ͖Δ

  28. ઃܭͷมભνʔϜਓ਺    ࣌ؒ ਓ਺ ୈ̍ੈ୅ ୈ̎ੈ୅ ୈ̏ੈ୅

  29. ୈ̎ੈ୅ͷ՝୊ w 7JFX4USFBNͷ࣮૷ํ๏͕࣮૷ऀʹΑͬͯ ͹Β͖͕ͭ͋Δ

  30. ୈ̎ੈ୅ͷ՝୊ 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) } }
  31. ୈ̎ੈ୅ͷ՝୊ 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) } }
  32. ୈ̎ੈ୅ͷ՝୊ 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 } }
  33. ୈ̎ੈ୅ͷ՝୊ 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 } }
  34. ߟҊ6OJP $SFBUFECZNBSUZTV[VLJ

  35. ୈ̏ੈ୅ w ࣌ظ w "CFNBίΠϯػೳͷ։ൃ w "CFNBαϙʔτ ౤͛મ ػೳͷ։ൃ

  36. ୈ̏ੈ୅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Λฦ͢ม਺}) } }
  37. ୈੈ୅ͷྑ͍఺ w7JFX.PEFMͷ࣮૷ͷ౷Ұ͕࣮ݱͰ͖ͨ w7JFX.PEFMͷϩδοΫͷྲྀΕ͕୯Ұํ޲ͳ ͷͰՄಡੑ্͕͕ͬͨ

  38. ୈ̏ੈ୅6OJP

  39. ݱࡏͷ՝୊

  40. ݱࡏͷ՝୊ wਐԽ͍ͯ͘͠աఔͰલੈ୅ͷ࣮૷͕࢒͍ͬͯΔ wୈ̍ੈ୅ͷ࣮૷ͰҰ෦'MVY಺Ͱ4UPSFUP4UPSF ͳ࣮૷ʹͳ͍ͬͯͯॲཧͷྲྀΕ͕ෳࡶԽ͍ͯ͠Δ wը໘ͷ୯ମىಈ͕Ͱ͖ͳ͍࣮૷ʹͳ͍ͬͯΔ w'MVYͷ"DUJPO͕3FTPVSDF΁௚઀ࢀর͍ͯ͠Δ

  41. ·ͱΊ wઃܭͷมߋͷλΠϛϯά͸શػೳΛ࡮৽͢ Δͱ͖΍େ͖ͳػೳ։ൃ͕ೖΔͱ͖ wॳظͷίϯηϓτ͔Βൃੜͨ͠՝୊ʹରͯ͠ ౎౓ղܾ͢Δํ๏Λ৽ͨ͠ʹऔΓೖΕͯมԽ ͖ͯͨ͠

  42. ࢀর w 'MVYXJUI3Y4XJGU w IUUQTTQFBLFSEFDLDPNEFLBUPUPSPqVYXJUISYTXJGU w .77. 'MVY w IUUQTTQFBLFSEFDLDPNNBSUZTV[VLJNWWNQMVTqVY

    w "CFNBJ04"SDIJUFDUVSF w IUUQTTQFBLFSEFDLDPNUPJLJBCFNBJPTBSDIJUFDUVSF w 6OJP w IUUQTTQFBLFSEFDLDPNNBSUZTV[VLJNWWNGBMTFTIJ[IVBOHXPGVSVGSBNFXPSLXPLBJGBEBP SVTJUJNVEFCBSBUVLJHBBUVUBTIJ[IVBOHXPUPOHTVSV