Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

ࣗݾ঺հ Name: entaku Job: iOS / AndroidΤϯδχΞͳͲ SIer໿6೥ 2018/3~ εϙʔπϚονϯάΞϓϦ 2019/3~ CBcloud ෺ྲྀITαʔϏε 2೥൒ 2021/12~ Voicy Twitter: @entaku_0818

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

ΞʔΩςΫνϟ

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

νϟϯωϧৄࡉը໘ /// @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͔Βݺͼग़͢

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

ࢸΔॴ͔Βݺ͹ΕΔΫϥε /// @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 } ΢ϥଆ Ի੠࠶ੜʹؔ͢Δ͜ͱΛ͜͜Ͱશͯ΍͍ͬͯ·͢ όοΫάϥ΢ϯυ࠶ੜ/࿈ଓ࠶ੜ/ϓϨϛΞϜ ࠓޙ΋ػೳ௥Ճͨ͠Βઈର૿͑Δ…

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Test ΢ϥଆ https://github.com/Quick/Quick • ར༻ϥΠϒϥϦ • QuickSpec • ςετͷ࣮ߦͰར༻ • https://github.com/Quick/Quick • mockolo • ϞοΫͷ࡞੒ʹར༻ • https://github.com/uber/mockolo

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Test ΢ϥଆ • MockΛར༻͠ɺPresenter͔Βҙਤͨ͠View/Interractor/Routerͷϝιο υΛݺͼ͍ͩͯ͠Δ͜ͱΛ֬ೝ Test • ֤ը໘ͷઃܭͷݟ௚͠΋ಉ࣌ʹ͍ͯ͠·͢ • ςετΛॻ͘͜ͱͰɺ͜ͷ੹຿͸ຊ౰ʹ͜͜ͳͷ͔ʁͱ໰͏͜ͱʹͳΓ·͢

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

·ͱΊ • VoicyͰ͸ΞʔΩςΫνϟʹVIPERΛ࠾༻͍ͯ͠·͢ • ΞʔΩςΫνϟͷಋೖΛ͔ͨ͠Βͱ͍ͬͯ׬ᘳͱ͸͍͖·ͤΜ • Ի੠ͷॲཧ͸ࢸΔॴ͔Βݺ͹ΕͨΓ… • ڞ௨Խ͞ΕͨΫϥε͕͋ͬͨΓ • PresenterͷςετΛॆ࣮͍ͤͯ͞·͢ • Presenterͷςετ͸վΊͯઃܭΛݟ௚͖͔͚ͬ͢ʹͳ͍ͬͯ ·͢ • BitriseͰઈରʹCIճ͢Α͏ʹͯ͠·͢

Slide 32

Slide 32 text

એ఻ʂ

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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