LoginSignup
35

More than 5 years have passed since last update.

[iOS] Extension を使用する時のアーキテクチャに Layered Architecture を採用する

Last updated at Posted at 2016-11-07

肥大化するiOSアプリケーション

iOS 10 がリリースされ使用できる Extension が更に増えました。
特に Notification Service Extension を使った Notification のリッチ化などはアプリの継続率にも大きく影響しそうで導入するアプリも多いのではないでしょうか。

iOS 10 で画像つきの Notification を配信する

他にも Share Extension や Today Extension、Notification Content Extension など、1つの iOS アプリの中に多くの機能を持たせることができるようになってきています。

iOS8のApp Extensionsをつくってみる(Share 実装編)
iOS 8から導入されたウィジェット機能を使ってみる

Extension を実装する時の課題 - コードの共有

Extension は通常の iOS アプリ(Extension を抱えるという意味で Containing App と呼ばれます)とは別プロセスとして呼び出されます。

app_extensions_container_restrictions_2x.png
An app extension’s container is distinct from its containing app’s containes

つまり Extension を実装する、というのはiOSアプリ(Containing App)本体と似たような機能(またはContaining App の一部の機能)を持ったものを複数作ることになります。
そうすると同じコードを複数のプロセスで共有したくなります。この場合、Xcode ではどのように管理をすればよいでしょう?

Target membership を使う

コードを複数の Target で共有する方法として Target membership というものがあります(Extension は Target として作成します)。ソースコードの右ペインにあるTarget membershipで共有したいTargetにチェックをすることで共有することができます。

スクリーンショット 2016-11-06 21.26.11.png

しかし共有するソースコードが増えてくると、「あれ?どのコードを共有してるっけ?」という管理上の問題がでてきます。

Embeded framework を使う

他の共有方法としてEmbeded framework を使うという方法があります。

Embeded framework については下記記事に解説がありますがコード共有する際に利点があります。

Embedded Framework使いこなし術

Apple のドキュメントでもコードの共有にEmbeded framework を勧めています。
Using an Embedded Framework to Share Code

Layered Architecture

共通するコードを Embeded framework にして管理したいなと思ったところでどうやって分割すればよいかを考えます。

これまで iOS アプリや Android アプリは比較的シンプルがアプリが多かったので分割の必要性をあまり感じることはなかったと思います。
ですが Windows のデスクトップアプリなどは比較的規模の大きなアプリを作ることが多く(主観ですが)、知見があるのではないかと思いました。

というわけで Microsoft の技術サイト MSDN を覗いてみます。

Layered Application Guidelines

ここでは Layered Architecture という構成を説明しています。その名の通りアプリケーションを層(Layer)に分割して設計しようというものです。

下図は基本的な Layer 構成のアプリケーションです。

IC351011.png

この例で言うと、
- Presentation layer: ユーザーインタラクションに責任を持つ機能、UI。MVC でいうVの役割のコード。
- Bussiness layer: ビジネスロジックに関するもの。MVC でいうCの役割のコード。
- Data layer: データの管理(取得、永続化)に責任を持つ。MVC でいうMの役割のコード。

このあたりの分割の考え方は「Separation Of Concerns(関心事の分離)」に沿っています。他にもアプリケーション全体に横断して使用されるコードを置く Cross cutting layer と呼ばれるものもあります。Cross cutting layer にはロギング用のコードや定数などアプリ全体で使用されるコードを置きます。

Layerd Architecture を Framework を用いて実現する

とはいえ分割しすぎるのも大変になるので、ここでは Presentation layer と Bussiness layer は分割せず、Data layer だけ分割します。

  • Presentation layer & Bussiness layer : ViewController など。これはContaining App(iOSアプリ本体)
  • Data layer : Http Client や Model class など。これを Framework で作成

スクリーンショット 2016-11-06 22.15.39.png

この矢印は依存関係を表します。下位の layer は上位の layer にはアクセスできません。

さらにロギング機能など横断的に使用する layer を追加します。

  • Cross Cutting layer: ロギング用のクラス、定数など。Framework で作成。

スクリーンショット 2016-11-06 22.15.06.png

ここまでは MSDN に従った Layered Architecture ですが、ここに iOS 特有の Extension を追加してみます。例として Today Extension を追加します。Today Extension は役割としては Containing app とほぼ同じなので、Presentation layer, Business layer の横に置いて、Data layer と Cross cutting layer に依存させます。

スクリーンショット 2016-11-06 22.19.25.png

ここまでのプロジェクトの構成は下記のようになります。

スクリーンショット 2016-11-06 22.25.55.png

Factory クラスと Protocol を使った実装

ここまでで役割によってlayer分割できましたが、上位 layer から 下位 layer にあるコードになんでもアクセスできるように実装するとコードの変更時の影響範囲が大きくなってしまいます。
そこで下位 layer には Factory クラスと Protocol だけ公開することで影響範囲を絞る構成にしてみます。

Data layer で HTTP Client を実装する

例として Http Client を実装してみます。まず Protocol で HttpClient を定義して、サーバーからのレスポンスを String で取得できる関数 get を持つとします。この protocol を public で定義します。

// Protocol だけ public
public protocol HttpClient {
    func get() -> String
}

次に HttpClient の実装として DefaultHttpClient を定義します。関数get の中身は適当ですが、ここでのポイントはこのクラスを public にしないことです。

// 実装の実体は public にしない
class DefaultHttpClient: HttpClient {
    func get() -> String {
        return "Data from API Server"
    }
}

Factory クラスから protocol の実体を渡す。

HttpClient を作ったら Factory クラスを作って、その関数やプロパティから上位の layer に渡せるようにします。Data layer で public のクラスはこの Factory クラスだけにします。

public class DataLayerFactory: NSObject {
    public static var httpClient: HttpClient {
        return DefaultHttpClient()
    }
}

こうすることで DefaultHttpClient という実装の実体は隠蔽しつつ機能(protocol)のみ公開できるようになります。いわゆるインターフェイスベースのプログラミングですね。

Cross Cutting layer の実装

Cross Cutting layer は Data layer と異なり、アプリ横断的に使用するコードを置くので public でクラスを作ります。いわゆる Utility クラス群ですね。ここでは例としてロギング用のクラスを作ります。

import UIKit

public class Logger {
    public class func log(text: String) {
        print("log = \(text)")
    }
}

上位 layer から 下位 layerを呼び出す

ここまでやったらあとは上位 layer から呼び出すだけです。やり方は簡単で下位 layer を import して使うだけ。

import UIKit
import LayeredData
import LayeredCrossCuttingg

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let client = DataLayerFactory.httpClient
        Logger.log(text: client.get())
    }
}

Today Extension でも使い方は同じです。

import UIKit
import NotificationCenter
import LayeredData
import LayeredCrossCuttingg

class TodayViewController: UIViewController, NCWidgetProviding {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view from its nib.
        let client = DataLayerFactory.httpClient
        Logger.log(text: client.get())
    }
}    

ここまでのサンプルコードはこちら。
https://github.com/takecian/iOS-LayeredAppSample

上位 layer はアクセスしている実体を意識することなく使用できる(依存するのはProtocolだけ)ので layer 間の結合を疎に保つことができそうです。
テストする際も protocol を実装したテスト用クラスを作ることでテストがしやすくなりそうです。

まとめ

  • Containing app, Extension で共有するコードは Framework に分割
  • 分割の基準は Layered Application Guidelines を参考に
  • 下位 Layer が公開するものは Factory クラスと protocol のみ
    • 公開するものはできるだけ少なくする(Layer 間の結合が疎であればあるほど変化に強いです)
  • 横断的にしようする機能は Cross Cutting layer に置く

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
35