5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

protocolを使うと、非同期通信後のUI更新処理がとてもわかりやすかった話

Last updated at Posted at 2020-09-30

はじめに

こんにちは、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】ハマりがちな循環参照について

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?