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

    View Slide

  2. ࣗݾ঺հ
    Name: entaku


    Job: iOS / AndroidΤϯδχΞͳͲ


    SIer໿6೥


    2018/3~ εϙʔπϚονϯάΞϓϦ


    2019/3~ CBcloud ෺ྲྀITαʔϏε 2೥൒


    2021/12~ Voicy


    Twitter: @entaku_0818

    View Slide

  3. Voicy
    https://corp.voicy.jp/
    https://voicy.jp/

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  7. iOSΤϯδχΞϝϯόʔߏ੒
    ΦϑγϣΞ
    Voicy
    • ਖ਼ࣾһ2໊ͱۀ຿ҕୗ·ͨΦϑγϣΞͷϝϯόʔͰߏ੒͞Ε͍ͯ·͢
    λον


    (ొஃͯ͠Δਓ)
    ۀ຿ҕୗ

    2022/07/20ݱࡏ

    View Slide

  8. iOSΤϯδχΞϝϯόʔߏ੒
    Voicy
    • ໿2೥લʹۀ຿ҕୗϝϯόʔΛத৺ʹΞϓϦͷϦϓϨΠε


    • ίʔυͷࢸΔॴʹͦͷࠟ੻͕͋Γ·͢
    λον


    (ొஃͯ͠Δਓ)
    ۀ຿ҕୗ

    ΦϑγϣΞ
    Voicy

    View Slide

  9. ΞʔΩςΫνϟ

    View Slide

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

    View Slide

  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ϓϩδΣΫτʹΫϦʔϯͰΑΓϞδϡʔϧԽ͞Εͨߏ଄Λ࡞Γग़ͨ͢ΊͷΞϓ
    ϩʔνͰ͋Δɻ͜ͷύλʔϯͷഎޙʹ͋Δߟ͑ํ͸ɺΞϓϦͷґଘؔ܎Λ෼཭
    ͠ɺΤϯςΟςΟؒͷ੹೚ҕৡͷόϥϯεΛͱΔ͜ͱͰ͢ɻ

    View Slide

  12. https://cheesecakelabs.com/blog/ios-project-architecture-using-viper/
    ͳΔ΄Ͳ….☺

    View Slide

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

    View Slide

  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͔Βݺͼग़͢

    View Slide

  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͔Βݺͼग़͢

    View Slide

  16. νϟϯωϧৄࡉը໘ ΢ϥଆ

    View Slide

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

    View Slide

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

    View Slide

  19. ࢸΔॴ͔Βݺ͹ΕΔΫϥε
    ͜ͷը໘ͷதʹ


    ࢸΔॴ͔Βݺ͹ΕΔΫϥε


    ͷUI͕͋Γ·͢ʂͲΕͰ͠ΐ͏ʁ

    View Slide

  20. ࢸΔॴ͔Βݺ͹ΕΔΫϥε
    ͜ͷը໘ͷதʹ


    ࢸΔॴ͔Βݺ͹ΕΔΫϥε


    ͷUI͕͋Γ·͢ʂͲΕͰ͠ΐ͏ʁ
    ͜ΕͰ͢

    View Slide

  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 }




    ΢ϥଆ
    Ի੠࠶ੜʹؔ͢Δ͜ͱΛ͜͜Ͱશͯ΍͍ͬͯ·͢


    όοΫάϥ΢ϯυ࠶ੜ/࿈ଓ࠶ੜ/ϓϨϛΞϜ


    ࠓޙ΋ػೳ௥Ճͨ͠Βઈର૿͑Δ…

    View Slide

  22. ͳ͔ͥڞ௨Խ͞Ε͍ͯΔBase
    Ϋϥεͨͪ🥺

    View Slide

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

    View Slide

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

    View Slide

  25. ೉͠͞ʹཱͪ޲͔͏ͨΊͷ


    ςετ🔥

    View Slide

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

    View Slide

  27. Test ΢ϥଆ
    https://github.com/Quick/Quick
    • ར༻ϥΠϒϥϦ


    • QuickSpec


    • ςετͷ࣮ߦͰར༻


    • https://github.com/Quick/Quick


    • mockolo


    • ϞοΫͷ࡞੒ʹར༻


    • https://github.com/uber/mockolo

    View Slide

  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ͷϝιο
    υΛݺͼ͍ͩͯ͠Δ͜ͱΛ֬ೝ

    View Slide

  29. Test ΢ϥଆ
    • MockΛར༻͠ɺPresenter͔Βҙਤͨ͠View/Interractor/Routerͷϝιο
    υΛݺͼ͍ͩͯ͠Δ͜ͱΛ֬ೝ
    Test
    • ֤ը໘ͷઃܭͷݟ௚͠΋ಉ࣌ʹ͍ͯ͠·͢


    • ςετΛॻ͘͜ͱͰɺ͜ͷ੹຿͸ຊ౰ʹ͜͜ͳͷ͔ʁͱ໰͏͜ͱʹͳΓ·͢

    View Slide

  30. CI - Bitrise ΢ϥଆ
    • CIճͯ͠·͢ʂ


    • ࠷ۙϏϧυ͕࣌ؒ௕͍ͷͰɺॖΊ͍͖͍ͯͨͱࢥ͍·͢

    View Slide

  31. ·ͱΊ
    • VoicyͰ͸ΞʔΩςΫνϟʹVIPERΛ࠾༻͍ͯ͠·͢


    • ΞʔΩςΫνϟͷಋೖΛ͔ͨ͠Βͱ͍ͬͯ׬ᘳͱ͸͍͖·ͤΜ


    • Ի੠ͷॲཧ͸ࢸΔॴ͔Βݺ͹ΕͨΓ…


    • ڞ௨Խ͞ΕͨΫϥε͕͋ͬͨΓ


    • PresenterͷςετΛॆ࣮͍ͤͯ͞·͢


    • Presenterͷςετ͸վΊͯઃܭΛݟ௚͖͔͚ͬ͢ʹͳ͍ͬͯ
    ·͢


    • BitriseͰઈରʹCIճ͢Α͏ʹͯ͠·͢

    View Slide

  32. એ఻ʂ

    View Slide

  33. https://voicy.jp/channel/1305
    VoicyͷΤϯδχΞ͕ӡӦ͍ͯ͠Δνϟϯωϧ


    voi-chordௌ͍͍ͯͩ͘͞ʂ
    ΞϓϦ͸ͪ͜Β

    View Slide

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

    View Slide