Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
ちいさく始めるレイヤードアーキテクチャ
Search
tondol
October 02, 2017
Programming
2.1k
7
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
ちいさく始めるレイヤードアーキテクチャ
俺コン Vol.1 / Day.1 Track C で発表した資料になります。
#orecon_ios #kyobashiswift
tondol
October 02, 2017
More Decks by tondol
See All by tondol
RxSwift 3.3.0: Observable のフレンズが増えました!!
tondol
2
2.8k
Amazon Cloud Driveのご紹介
tondol
0
580
自家製オタクソリューションの紹介
tondol
1
570
ドはDockerのド
tondol
1
2.9k
Other Decks in Programming
See All in Programming
AI に Inclusive UI を書かせよう — Design Rules Skill で Compose UI を作り直す
theoriatec2024
1
490
What We Talk About When We Talk About XP
m_seki
2
630
AI Agent時代のリアーキテクチャ戦略と実践
hokaccha
9
4.5k
Building an Out-of-Order CPU
latte72
1
780
Claude Codeを組織的に動かして月400PRを実現した話
happy_ryo
0
280
From 6 People Classroom Meetup to 100 People Regional Conference / FOSS4G Hiroshima 2026
furukawayasuto
0
200
AWS DevOps Agentで インシデント対応をAIに任せたい
honmarkhunt
7
2.9k
JPUG勉強会 OSSデータベースの内部構造を理解しよう(第2回)
oga5
0
230
Omarchy Tokyo やると聞いて UMPC 買ってセットアップしてきた
mtsmfm
0
140
数年滞っていたダークモード対応をおよそ2週間で完了させる
chigichan24
0
710
TiDB Cloudのカスタムコントローラーによるオートスケール対応
takaidohigasi
0
120
WebMCP Challenge に星空観察アプリで参加した話
okajun35
0
170
Featured
See All Featured
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
380
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.6k
Joys of Absence: A Defence of Solitary Play
codingconduct
1
520
Leo the Paperboy
mayatellez
10
2.3k
Heart Work Chapter 1 - Part 1
lfama
PRO
10
36k
Prompt Engineering for Job Search
mfonobong
0
450
The Invisible Side of Design
smashingmag
301
52k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
880
Done Done
chrislema
186
16k
Navigating Weather and Climate Data
rabernat
0
520
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
500
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2.1k
Transcript
ちいさく始める レイヤード アーキテクチャ @tondol from Kyobashi.swift 俺コン Vol.1 / Day.1
– 2017.10.2 (Mon.) 1
このトークでは • 先⼈から受け継いだコードベースに、 レイヤードアーキテクチャを導⼊したときに 考えたことをお話します • 俺が考えた最強のアーキテクチャ!ってよりは、 実際にやるならこんぐらいのバランスも いいんでないの、くらいのテンション感で 紹介していきます
2
こんな話はしません • RxSwift の使い⽅ • サンプルコード中に⾃然に出てきます • 知らない⼈は、Observable<T> は Promise<T>
的な ものだと思って⾒てください • DDD (Domain-Driven-Design) の紹介や説明 • Tech Boosterさんの同⼈誌を読んだ程度なので、 そもそも語れるほど理解していません・・・ • レイヤードアーキテクチャetc.は 「DDD を実装に落とし込む際に便利なテク」 くらいの捉え⽅をしています 3
About Me • リクルートマーケティングパートナーズ • iOSエンジニア (ときどきJava/Kotlinも) • Kyobashi.swift 4
Kyobashi.swift • リクルートマーケティングパートナーズの オフィスが東京・京橋にあります • オフィス内のスペースで Swift/iOS の勉強会を 不定期で開催しています •
AKIBA.swift (クラスメソッドさん) や Otemachi.swift (⽇経新聞さん) とコラボも! • 最新情報は • https://kyobashi-swift.connpass.com/ 5
ここから本題 6
巷にあふれるアーキテクチャ の話題・・・ MVC MVP MVVM Flux Redux Clean Architecture DDD
Layered Architecture Fat ViewController 絶対に 殺すマン 7
iOSDCのCfPでもこんなに! アーキテクチャを移⾏する by @d_date Layered Architecture x RxSwiftを活⽤した 適切なエラーハンドリング by
@nonchalant0303 Redux+Rxを活⽤したアプリアーキテクチャ by @susieyy MVC→MVP→MVVM→Fluxの 実装の違いを⽐較してみる by @marty_suzuki 節⼦、それViewControllerやない...、 FatViewControllerや...。 by @ktanaka117 漸進的にViewControllerの肥⼤化を防ぐ by @kazuhiro494949 レッドオーシャン感 8
Clean Architecture Presenter View UseCase Translator Model DataStore Entity Presentation
Domain Infrastructure Repository 9
1画⾯に必要なクラス数 FooViewController FooPresenter FooUseCase FooTranslator FooModel FooRepository FooDataStore FooEntity Presentation/Domain
Domain/Infrastructure 8コンポーネント! 10 5分⽬安
本当にこんなに必要? • そもそも何のためにやるんだっけ? • 新規の開発ならともかく、 既存アプリのリファクタリングのために、 たくさんのクラスを実装するのは⾟い・・・ • メンテナンス性と開発速度のバランス 11
ちいさく始める レイヤード アーキテクチャ ちいさく始める: 必要最⼩限のコードで「欲しいもの」を 実現するアーキテクチャを考える 12
リファクタ前の状況 ⼤量のソースに分割されているが 密結合しまくりで 単体テストが書けないクラス群 通信も UI 更新も全部やる、 絵に描いたような FatViewController 機能追加のついでに
モダンアーキテクチャを 導⼊しよう! 設 計 13
欲しいもの • テスト可能な実装 • UIViewController や UIView のテストは厳しい • これらのコンポーネントに依存しないクラスに
アプリのロジックを書きたい • 依存するコンポーネントは外部から注⼊可能に • ⼊れ替え可能なインフラレイヤー • 永続化する先は API かもしれないし、 ローカル DB かもしれない • テストの観点からも⼊れ替え可能であるべき 14
割り切りポイント (1) • Model と Entity を共通化 • 表⽰⽤に必要なプロパティは Extension
にすれば、 ⼀旦実装を分離できる • Translator などが不要になり、 ボイラープレートコードを減らすことができる Presenter View UseCase Translator DataStore Presentation Domain Infrastructure Repository Model 共通化 15
割り切りポイント (2) • Repository を省略 • DataStore を動的に切り替えたりしないのなら、 UseCase に直接
DataStore を注⼊すれば⼗分 Presenter View UseCase DataStore Presentation Domain Infrastructure Model Repository 省略 16
割り切りポイント (3) • RxSwift の導⼊ • 「ちいさく」はないかもしれないが、 中⻑期的に⾒れば開発速度の向上に寄与するはず Presenter View
UseCase DataStore Presentation Domain Infrastructure Model この辺のやりとりに RxSwift を導⼊ 17
こうなりました Presenter View Controller UseCase DataStore Presentation Domain Infrastructure Model
18
サンプルアプリ • ニュースの⼀覧画⾯と詳細画⾯ • ⼀覧画⾯と詳細画⾯に Like ボタン 19
Presenter の実装 class NewsListPresenter: NSObject { let newsList = Variable([News]())
let showDetailViewControllerMessage = PublishSubject<Int>() private let useCase: NewsUseCase private let bag = DisposeBag() init(useCase: NewsUseCase) { self.useCase = useCase } extension NewsListPresenter: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.newsList.value.count } 最後の [News] を保持 更新時にイベント発⽣ 詳細画⾯の 表⽰イベント 依存 UseCase を イニシャライザから 注⼊ [News] を参照し Cell を提供する UITableViewDataSource 20 10分⽬安
UI と Presenter のバインド class NewsListViewController: UIViewController { private var
presenter: NewsListPresenter! ... private func setupBindings() { self.presenter.newsList .asDriver() .drive(onNext: { [unowned self] _ -> Void in self.tableView.reloadData() }) .disposed(by: self.bag) self.presenter.showDetailViewControllerMessage .asDriver(onErrorDriveWith: Driver.empty()) .drive(onNext: { [unowned self] id -> Void in self.showDetailViewController(id: id) }) .disposed(by: self.bag) } 新しい [News] が来たら reloadData を発⾏ イベントが来たら 詳細画⾯を push 21
初回ロードの流れ setupUI() viewDidLoad() fetchNewsList() fetchNewsList() Variable<[News]> NewsListPresenter: UITableViewDataSource NewsListViewController: UIViewController,
UITableViewDelegate NewsUseCaseImpl: NewsUseCase NewsStoreImpl: NewsStore tableView. reloadData() Update Observable<[News]> Observable<[News]> onNext ⾮同期処理の戻り値は Observable<T> 新しい [News] が来たら reloadData を発⾏ 22
セルタップ時の流れ selectNews() didSelectRowAt(…) PublishSubject <Int> NewsListPresenter: UITableViewDataSource NewsListViewController: UIViewController, UITableViewDelegate
NewsUseCaseImpl: NewsUseCase NewsStoreImpl: NewsStore showDetail ViewController(id: Int) onNext onNext イベントが来たら 詳細画⾯を push 23
Presenter のテスト • モックした DataStore を使い、 Presenter – UseCase を⼀気にテストする例
NewsListPresenter NewsUseCaseImpl NewsStoreMock Input (メソッドコール) Output (戻り値やイベント列) 24
⾮同期処理のテスト (1) class NewsListPresenterSpec: QuickSpec { override func spec() {
var presenter: NewsListPresenter! var bag: DisposeBag! ... _ = try! presenter.setupUI().toBlocking().first() let newsList = try! presenter.newsList.asObservable().toBlocking().first() // setupUI の完了後は News が 3 個表⽰されており、 // 先頭の News は id=1 expect(newsList).to(haveCount(3)) expect(newsList?[0].id) == 1 setupUI の完了を待機 更新後の Variable<T> を取得し Assertion 25
⾮同期処理のテスト (2) class NewsListPresenterSpec: QuickSpec { override func spec() {
var presenter: NewsListPresenter! var bag: DisposeBag! ... let scheduler = TestScheduler(initialClock: 0) let observer = scheduler.createObserver(Int.self) presenter.showDetailViewControllerMessage .subscribe(observer).disposed(by: bag) _ = try! presenter.setupUI().toBlocking().first() presenter.selectNews(indexPath: IndexPath(row: 0, section: 0)) // selectNews に先頭の IndexPath を渡すと、id=1 の News を詳細表⽰ expect(observer.events).to(haveCount(1)) expect(observer.events[0].value) == Event.next(1) テスト⽤の Observer を⽤意 得られたイベント列を Assertion 26
欲しいもの再考 • テスト可能な実装 • Presenter だけのテストもできるし、 UseCase や Store を含む横着テストもできる
• 依存コンポーネントはイニシャライザで注⼊ • ⼊れ替え可能なインフラレイヤー • UseCase と Store は疎結合なので、 必要に応じてデータレイヤーをモックしたり、 実装を差し替えたりできる • インフラレイヤーの内部実装を変更しても、 ドメインレイヤーには影響しない 27
悩みどころ • UseCase の責務が Presenter と DataStore 間の 仲介のみで、あんまり仕事をしていない気が •
API クライアント的なアプリだと特にこうなりがち • ViewController 周りの調整を⼀⼿に引き受ける Presenter が肥⼤化しがち • ViewController 同⼠のイベント送受信どうやる? • 現状は ViewController に⽣やした PublishSubject<T> を 遷移元の ViewController で subscribe している • Redux のようにアプリ全体で ひとつのイベントストリームを共有すれば解決? 28
まとめ • レイヤードアーキテクチャを⼀部導⼊した結果、 実装⼯数を抑えながら欲しいものを得られた • とはいえ、やはり銀の弾丸は存在しない • 各プロダクトで必要としているものを検討し、 アーキテクチャも取捨選択するのが⼤事 •
ソースコードは下記リポジトリから読めます! • https://github.com/tondol/LayeredArchSampl e 29
参考資料 • RxSwift? いやClean Swiftっしょ • https://qiita.com/takahia/items/67b9e122968 2127d924e • まだMVC,MVP,MVVMで消耗してるの?
iOS Clean Architectureについて • https://qiita.com/koutalou/items/07a4f9cf51a 2d13e4cdc • iOSDesignPatternSamples • https://github.com/marty- suzuki/iOSDesignPatternSamples 30 15分⽬安