はじめに
こんにちは、iOSエンジニアの dayossi です。
家族が幸せになれるサービスを提供したいと思って、
HaloHaloという家族日記アプリをリリースしています。
今回は、非同期通信後の処理について
protocolがとても便利だったという内容を書いています。
(今回の記事の成果物は、最下部に載せています)
そもそも非同期でUI更新したいと思ったきっかけは?
「非同期通信でデータを取得したあと、CollectionViewに値を反映しようと思ったけど
なんか表示の仕方がうまくいかないなー」
と思ったのがきっかけです。
原因は?
今回は非同期通信の処理に絞って、2つの問題に分けて考えてみました。
・1:参照しているデータがおかしい
・2:UI更新が遅い、滑らかにできない
###1:参照しているデータがおかしい
・クラス型で構築したデータモデルに、APIで取得した値を格納すると
格納した値は参照型となります。
変数に置き換えたあとでも、変数内のデータを書き換えると
もとのデータも書き換えられます。
・なので、例えば複数のユーザーデータを同時に取得して
ユーザー固有の値を表示したい場合だと
UI更新が行われる前にデータが書き換えられてしまう可能性があり、
期待どおりの表示にならない場合があります。
【 参照元 】
Value Semantics を持たない型の問題と対処法
###2:UI更新が遅い、滑らかにできない
MVCモデルのように、UI表示を行うViewとUI描画に必要なデータを持つModelとが密接になっていたり
ViewControllerに全ての処理をベタ書きして、FatViewControllerの状態になると
API通信でサーバーからデータを取得する処理と
UIを構築する処理が同一スレッドで行われるため
メインスレッドでの処理が止められ、わずかな時間ではあるものの
アプリの挙動が止まります。
そのため、サクサクとキレイにUIを表示してほしいのに
画面がカクついてしまう問題があります。
【 参照元 】
【Swift】Grand Central Dispatch (GCD)とOperationQueue まとめ
いままで行っていた対策
これらの問題の対策として以下を、個人的によく取っていました。
###1つ目:参照しているデータがおかしい
###→ データの保持方法を工夫する
● データを保持するときは、structによる値型で管理する
→ APIから返ってくるJSONデータを、structによる値型で保持すると
複数の通信結果を受けたときに、変な上書き処理が行われるリスクをぐんと減らせるから
● 格納したデータを、別の配列や辞書型の変数に入れて管理する
→ SwiftのArreyは、参照型のデータを値型で保持してくれる機能があるから(。。。便利!!!)
【 参照元 】
SwiftのArrayが実はすばらしかった)
###2つ目:UI更新が遅い、滑らかにできない
###→ 非同期処理に切り替える
● DispatchQueueを活用した非同期処理を入れる
→ UI描画・更新を行うメインスレッドと、データ処理や通信を行うバックグラウンドスレッドを分割して
メインスレッドの処理を軽くしたかったから
【 参照元 】
Swift の非同期処理の例(複数パターン)
【Swift】Grand Central Dispatch (GCD)とOperationQueue まとめ
ただ、非同期に得られたデータをUIへ反映するための伝達手段として
コールバックで値を返すやり方をゴリゴリ書いていたので
ネストがどんどん深くなり、カオス状態になっていました。。。
もう読み返したくなくなるくらい。。。泣
class hoge{
/*
例えば、ユーザーデータをサーバーから取得したあと
HugaClassにある getImage() というメソッドで
画像データを取りに行きたいのに...
*/
// ムダにクラス全体をインスタンス化...
let huga = HugaClass()
// ネストが深いうえに、クロージャ内で強参照...
func getUserData(){ (userData) in
let userId = userData.userId
huga.willFetchImage(userId){ (imageData) in
self.imageData.image = imageData.image
}
}
}
一番ネックになったのはこの
「取得したデータをもとに、どうやってUIを更新するか?」
という点でした。
protocolの可能性
前置きが長くなりましたが、ここからがようやく本題です。
クラス内に実装した処理を呼び出すために、
毎回クラスをインスタンス化して、そのクラス内の処理を呼び出すと
どこに何の処理を書いたのかがわかりにくくなります。
少ないコード数で書き出せる反面、
活用できる場面が非常に限定的になるもろさもあります。
そんな中、iOSアプリ設計パターンを読んでいて
Model
Model自身とRxSwiftらしいModel実装について、次の観点で解説します
・ protocol化して疎結合に、テスタブルにする
・ Observableを返却して、VIewModelとの建て付けを良くする
(引用元:iOSアプリ設計パターン入門 p124 より)
の中の 「protocol化して疎結合に、テスタブルにする」 という内容が印象的でした。
要するに、protocolを介して
必要な情報だけを抽出して伝達しましょう、ということと認識しています。
// MARK:- どんな処理を引き渡すのかが明確!
protocol HugaProtocol{
func willFetchImage(_ userId:String)
}
class hoge: hugaProtocol{
// クラスではなく、protocolで引き渡せる
private weak var repository:HugaProtocol?
init(repository:HugaProtocol){
self.repository = repository
}
func getUserData(){ (userData) in
let userId = userData.userId
input.willFetchImage(userId)
}
}
この書き方にすると、特定のクラスに依存しなくて済むようになるので
ある特定のクラス内の処理をわざわざ呼び出さなくても
protocolに準拠したクラスなら誰でも、
protocol内の処理を利用可能な状態にできます。
つまり、柔軟に設計を替えられるようになります。
さらに、これまでのclass同士をコールバック処理でくっつけるようなやり方よりも
各クラスの責務も明確に確認できるので、
どこで何の処理を行っているかがわかりやすくなります。
プロトコル指向とオブジェクト指向の違いと、プロトコル指向の恩恵に
ここでようやく気がつきました。。。
【 参照元 】
プロトコル指向プログラミングについて調べてみた
WWDC 2015「Swiftでプロトコル指向プログラミング」
非同期処理への応用
protocolの特徴は
特定のクラスに依存しなくても、特定の処理を呼び出せる点です。
なので、CollectionViewやTableViewのライフサイクル内で
データ取得→UI描出→UI更新を無理やり同一スレッドで完結させなくても、
データ取得後、データを保存してからreload処理を呼んで
改めてUI更新→UI再描出を行う方法をとることができます。
アプリ設計とprotocolによって、非同期処理に柔軟に対応でき
かつ滑らかなUIアニメーションも損わずに済みそうです。
もう、protocolサイコーだなって…感動しました!!
もっとprotocolを活用しよう!!
protocolを活用できると、わかりやすい設計になるだけでなく
変化に強いiOSアプリにできそうな予感しかしないので
本当にワクワクしました!!
特に非同期通信をこまめに行うアプリでは
protocolは大いに活用できそうです。
これからもっとprotocolと仲良くなろうと思います!!
今回の成果物
(あくまでprotocolの活用事例を示しています。
このままでは到底、実践には活用できない点はご容赦ください)
https://github.com/Kohei312/CollectioView_loose-coupling
参考書籍・記事
###書籍
もっと早く読んでおきたかった...わかりやすくてホントにオススメです!
関 義隆, 史 翔新, 田中 賢治 他. iOSアプリ設計パターン入門(2018) PEAKS出版
###値型について
SwiftのArrayが実はすばらしかった
純粋値型Swift
Value Semantics を持たない型の問題と対処法
###非同期処理について
Swift の非同期処理の例(複数パターン)
【Swift】Grand Central Dispatch (GCD)とOperationQueue まとめ
###プロトコルについて
Protocol-oriented Programming とは
Swiftのプロトコル
###delegateなどの情報通知について
【Swift】DDDを取り入れたiOS開発 その1 ~UseCaseとdelegate~
Swiftとオブジェクト間の通知のパターン
[iOS] iOSのDelegateをしっかりと理解する
SwiftにおけるDelegateとは何か、なぜ使うのか
【Swift】delegate実装の流れ
###Dependency Injection(DI)
猿でも分かる! Dependency Injection: 依存性の注入
swiftで 依存関係逆転の原則 を使ってテストしやすい設計にする
###(+α)
【Swift】ハマりがちな循環参照について