Upgrade to Pro — share decks privately, control downloads, hide ads and more …

VIPERを利用した音声配信アプリの開発の裏側.pdf

 VIPERを利用した音声配信アプリの開発の裏側.pdf

entaku

July 20, 2022
Tweet

More Decks by entaku

Other Decks in Technology

Transcript

  1. VIPERΛར༻ͨ͠Ի੠഑ ৴ΞϓϦͷ։ൃͷཪଆ entaku

  2. ࣗݾ঺հ Name: entaku Job: iOS / AndroidΤϯδχΞͳͲ SIer໿6೥ 2018/3~ εϙʔπϚονϯάΞϓϦ

    2019/3~ CBcloud ෺ྲྀITαʔϏε 2೥൒ 2021/12~ Voicy Twitter: @entaku_0818
  3. Voicy https://corp.voicy.jp/ https://voicy.jp/

  4. VoicyͷΞϓϦ Ϧεφʔ͕࢖͏࠶ੜΞϓϦͱύʔιφϦςΟ͕࢖͏ऩ࿥ΞϓϦͷ2छ ྨ͕͋Γ·͢

  5. ऩ࿥ͱ࠶ੜͰදཪҰମͳVoicyͷػೳ Ի੠࠶ੜԻ੠ऩ࿥ ੜ์ૹ ίϝϯτ ϋογϡλά ݕࡧ ച্੥ٻ ࠩ͠ೖΕ ϓϨϛΞϜ์ૹ

  6. ܰ͘։ൃମ੍ͱྺ࢙ͷઆ໌

  7. iOSΤϯδχΞϝϯόʔߏ੒ ΦϑγϣΞ Voicy • ਖ਼ࣾһ2໊ͱۀ຿ҕୗ·ͨΦϑγϣΞͷϝϯόʔͰߏ੒͞Ε͍ͯ·͢ λον (ొஃͯ͠Δਓ) ۀ຿ҕୗ ๻ 2022/07/20ݱࡏ

  8. iOSΤϯδχΞϝϯόʔߏ੒ Voicy • ໿2೥લʹۀ຿ҕୗϝϯόʔΛத৺ʹΞϓϦͷϦϓϨΠε • ίʔυͷࢸΔॴʹͦͷࠟ੻͕͋Γ·͢ λον (ొஃͯ͠Δਓ) ۀ຿ҕୗ ๻

    ΦϑγϣΞ Voicy
  9. ΞʔΩςΫνϟ

  10. VIPER https://cheesecakelabs.com/blog/ios-project-architecture-using-viper/ • VIPERΛ࠾༻ͯ͠·͢

  11. VIPER ͱ͸ʁ https://cheesecakelabs.com/blog/ios-project-architecture-using-viper/ • VIPER is a backronym for View,

    Interactor, Presenter, Entity and Router. It’s basically an approach that implements the Single Responsibility Principle to create a cleaner and more modular structure for your iOS project. The ideia behind this pattern is to isolate your app’s dependencies, balancing the delegation of responsibilities among the entities. • VIPERͱ͸ɺViewɺInteractorɺPresenterɺEntityɺRouterͷ಄จࣈΛͱͬͨ΋ͷ Ͱ͢ɻجຊతʹ͸ɺSingle Responsibility Principleʢ୯Ұ੹೚ݪଇʣΛ࣮૷͠ɺ iOSϓϩδΣΫτʹΫϦʔϯͰΑΓϞδϡʔϧԽ͞Εͨߏ଄Λ࡞Γग़ͨ͢ΊͷΞϓ ϩʔνͰ͋Δɻ͜ͷύλʔϯͷഎޙʹ͋Δߟ͑ํ͸ɺΞϓϦͷґଘؔ܎Λ෼཭ ͠ɺΤϯςΟςΟؒͷ੹೚ҕৡͷόϥϯεΛͱΔ͜ͱͰ͢ɻ
  12. https://cheesecakelabs.com/blog/ios-project-architecture-using-viper/ ͳΔ΄Ͳ….☺

  13. νϟϯωϧৄࡉը໘ ࣄྫͰݟͯΈΑ͏ʂ

  14. νϟϯωϧৄࡉը໘ /// @mockable protocol ChannelDetailViewProtocol { func setupCustomNavigationBar(channelName: String, channelImageURL:

    URL?) func scrollToTop() ΢ϥଆ protocol ChannelDetailPresenterProtocol { func onViewDidLoad() func onViewWillAppear() func onViewDidAppear() func onViewWillDisappear() func onBackButtonTapped() func onRefreshControlTrigger() func onShareButtonTapped() • ViewͰ͸UI্ͷॲཧʹؔ͢ΔϝιουΛ༻ҙ • εςʔλεόʔͳͲ • Presenter͔Βݺͼग़͢
  15. /// @mockable protocol ChannelDetailInteractorProtocol { var channel: Channel? { get

    } var keyword: String { get } var live: Live? { get } var subscriptionInfo: SubscriptionInfo? { get } func getAllData() ΢ϥଆ protocol ChannelDetailPresenterProtocol { func onViewDidLoad() func onViewWillAppear() func onViewDidAppear() func onViewWillDisappear() func onBackButtonTapped() func onRefreshControlTrigger() func onShareButtonTapped() νϟϯωϧৄࡉը໘ • InteractorͰ৘ใऔಘॲཧ·ͨɺσʔλΛ ެ։ • Presenter͔Βݺͼग़͢
  16. νϟϯωϧৄࡉը໘ ΢ϥଆ

  17. VIPERΞʔΩςΫνϟΛಋೖ͠ ͯ΋೉͍͜͠ͱ͸͋Δ🥺

  18. ࢸΔॴ͔Βݺ͹ΕΔΫϥε🥺

  19. ࢸΔॴ͔Βݺ͹ΕΔΫϥε ͜ͷը໘ͷதʹ ࢸΔॴ͔Βݺ͹ΕΔΫϥε ͷUI͕͋Γ·͢ʂͲΕͰ͠ΐ͏ʁ

  20. ࢸΔॴ͔Βݺ͹ΕΔΫϥε ͜ͷը໘ͷதʹ ࢸΔॴ͔Βݺ͹ΕΔΫϥε ͷUI͕͋Γ·͢ʂͲΕͰ͠ΐ͏ʁ ͜ΕͰ͢

  21. ࢸΔॴ͔Βݺ͹ΕΔΫϥε /// @mockable protocol AudioControllerProtocol: AnyObject { /// Playing settings

    properies var playSpeed: AudioPlaySpeed { get set } var audioSpeaker: AudioSpeaker { get set } var bgm: AudioBGM { get set } var voiceVolume: Float { get set } var bgmVolume: Float { get set } var soundEffectVolume: Float { get set } var isSoundEffectEnabled: Bool { get set } /// Playing info properies var audioStatus: AudioStatus { get } var isPlayingOrLoading: Bool { get } var isVoiceOrBGMPlaying: Bool { get } var elapsedPlaytime: TimeInterval { get set } /// Playlist info properies var audioListStyle: AudioListStyle { get set } var playingChannel: Channel? { get } ΢ϥଆ Ի੠࠶ੜʹؔ͢Δ͜ͱΛ͜͜Ͱશͯ΍͍ͬͯ·͢ όοΫάϥ΢ϯυ࠶ੜ/࿈ଓ࠶ੜ/ϓϨϛΞϜ ࠓޙ΋ػೳ௥Ճͨ͠Βઈର૿͑Δ…
  22. ͳ͔ͥڞ௨Խ͞Ε͍ͯΔBase Ϋϥεͨͪ🥺

  23. ͳ͔ͥڞ௨Խ͞Ε͍ͯΔ BaseΫϥεͨͪ ΢ϥଆ • ڞ௨ͷVIPERͰ෼͚ΒΕͨBase Ϋϥε͕͋Δ

  24. ͳ͔ͥڞ௨Խ͞Ε͍ͯΔ BaseΫϥεͨͪ ΢ϥଆ • ҎલͷϦϓϨΠε࣌ʹ·ͱΊͯ࡞ͬͨڞ௨Խͨ͠ ໛༷

  25. ೉͠͞ʹཱͪ޲͔͏ͨΊͷ ςετ🔥

  26. Test PresenterͷςετΛඞͣॻ͘Α͏ʹ͢Δ

  27. Test ΢ϥଆ https://github.com/Quick/Quick • ར༻ϥΠϒϥϦ • QuickSpec • ςετͷ࣮ߦͰར༻ •

    https://github.com/Quick/Quick • mockolo • ϞοΫͷ࡞੒ʹར༻ • https://github.com/uber/mockolo
  28. Test class ChannelDetailPresenterSpec: QuickSpec { override func spec() { describe("ChannelDetailPresenter")

    { var presenter: ChannelDetailPresenter! var viewMock: ChannelDetailViewProtocolMock! var interactorMock: ChannelDetailInteractorProtocolMock! var routerMock: ChannelDetailRouterProtocolMock! var audioControllerMock: AudioControllerProtocolMock! var deviceManagerMock: DeviceManagerProtocolMock! ʙʙʙ // Prepare let commonSetupCallCount = viewMock.commonSetupCallCount let setContentBottomInsetCallCount = viewMock.setContentBottomInsetCallCount let showLoadingCallCount = viewMock.showLoadingCallCount let getAllDataCallCount = interactorMock.getAllDataCallCount // Action presenter.onViewDidLoad() // Expect expect(viewMock.commonSetupCallCount) == commonSetupCallCount + 1 expect(viewMock.setContentBottomInsetCallCount) == setContentBottomInsetCallCount + 1 expect(viewMock.showLoadingCallCount) == showLoadingCallCount + 1 expect(interactorMock.getAllDataCallCount) == getAllDataCallCount + 1 ΢ϥଆ • MockΛར༻͠ɺPresenter͔Βҙਤͨ͠View/Interractor/Routerͷϝιο υΛݺͼ͍ͩͯ͠Δ͜ͱΛ֬ೝ
  29. Test ΢ϥଆ • MockΛར༻͠ɺPresenter͔Βҙਤͨ͠View/Interractor/Routerͷϝιο υΛݺͼ͍ͩͯ͠Δ͜ͱΛ֬ೝ Test • ֤ը໘ͷઃܭͷݟ௚͠΋ಉ࣌ʹ͍ͯ͠·͢ • ςετΛॻ͘͜ͱͰɺ͜ͷ੹຿͸ຊ౰ʹ͜͜ͳͷ͔ʁͱ໰͏͜ͱʹͳΓ·͢

  30. CI - Bitrise ΢ϥଆ • CIճͯ͠·͢ʂ • ࠷ۙϏϧυ͕࣌ؒ௕͍ͷͰɺॖΊ͍͖͍ͯͨͱࢥ͍·͢

  31. ·ͱΊ • VoicyͰ͸ΞʔΩςΫνϟʹVIPERΛ࠾༻͍ͯ͠·͢ • ΞʔΩςΫνϟͷಋೖΛ͔ͨ͠Βͱ͍ͬͯ׬ᘳͱ͸͍͖·ͤΜ • Ի੠ͷॲཧ͸ࢸΔॴ͔Βݺ͹ΕͨΓ… • ڞ௨Խ͞ΕͨΫϥε͕͋ͬͨΓ •

    PresenterͷςετΛॆ࣮͍ͤͯ͞·͢ • Presenterͷςετ͸վΊͯઃܭΛݟ௚͖͔͚ͬ͢ʹͳ͍ͬͯ ·͢ • BitriseͰઈରʹCIճ͢Α͏ʹͯ͠·͢
  32. એ఻ʂ

  33. https://voicy.jp/channel/1305 VoicyͷΤϯδχΞ͕ӡӦ͍ͯ͠Δνϟϯωϧ voi-chordௌ͍͍ͯͩ͘͞ʂ ΞϓϦ͸ͪ͜Β

  34. iOSDCͰ·͢ʂ ΞϓϦ͸ͪ͜Β https://iosdc.jp/2022/