A Day In The Life

とあるプログラマの備忘録

iOS アプリの構造がどのようになっているか紐解いてみる

iOS アプリの構造がどのようになっているのか理解しなくても簡単なアプリを開発することは可能です。実際自分も iOS アプリの開発をはじめたことろはそうでした。しかしアプリの構造を理解していないと複雑なアプリ、例えばタブとナビゲーションを組み合わせたアプリやマルチタッチやジェスチャーを使ったアプリなどを作ろうとしたときにハマることが多いです。
本記事では iOS アプリの構造について説明します。

一番単純なアプリの構造

それでは iOS アプリの中でも一番単純なアプリの構造がどうなっているのか見てみましょう。
iOS で一番単純なアプリは画面を一つ表示するアプリです。画面を一つ表示するアプリはシングルビューアプリケーション(Single View Application)といいます。
ラベルもボタンもなく、ただ真っ白な画面を表示するだけのアプリがどのような構造になっているのかみてみましょう。
以下はシングルビューアプリケーションで使用されるクラスの関係を図にしたものです。
アプリに使われるクラス
図の中の青色のクラスは開発者が自分で作成するクラスです。その他のクラスは iOS から提供されているクラスです。図中の UIWindow クラスと UIStoryboard クラスは iOS が内部的に使用するクラスで開発者が直接これらクラスのオブジェクトを使うことはあまりありません。上記クラスの中でユーザが直接目に触れる部分の機能を提供しているのは UIView クラスです。それ以外のクラスは内部的な処理をするための機能を提供しています。

iOS から提供されているクラスの説明

それでは上記の図の中で iOS から提供されているクラスについて詳しく見ていきましょう。

UIApplication クラス

ウインドウの管理やイベント送信を行うクラス。UIApplication オブジェクトはステータスバーを表示するウインドウ*1とアプリ本体を表示するウインドウを管理しています。ステータスバーを表示するウインドウは開発者が直接参照することはできません。アプリを表示するウインドウのみ参照できるようになっています。
また UIApplication クラスのオブジェクトはイベントキューにたまったイベントを UIApplicationDelegate オブジェクトや UIWindow オブジェクトに送信します。
iOS では1アプリにつき必ず1つの UIApplication が存在します。このクラスのオブジェクトはシングルトンになっていて以下のように sharedApplication クラスメソッドを呼び出すことでインスタンスを取得することができます。

[UIApplication sharedApplication]

このクラスは開発者が独自に継承してイベント処理をカスタマイズすることが可能です。

UIApplicationDelegate プロトコル

アプリから通知されるイベントメソッドが定義されているプロトコルプロトコルとは Java や C♯でいうところのインタフェースのこと。開発者はこのプロトコルを実装したクラスを作成する必要があります。このプロトコルに定義されているイベントメソッドの詳細は以下の記事を参照してください。

UIView クラス

画面に矩形領域を表示するための機能を提供するクラス。iOS アプリ開発で使用するあらゆる UI 部品のクラス(UIButton,UILabelなど)は UIView クラスを継承しています*2
このクラスに関する詳細は以下の記事を参照してください。

UIWindow クラス

ウインドウを表示する機能を提供するクラス。UIWindow クラスは UIView クラスを継承していてビューとして機能します。しかしウインドウはユーザからは見えず直接操作することもできません。UIWindow オブジェクトはユーザが画面やアプリに対して行ったアクション(画面をタッチしたり端末をシェイクしたり)に対するイベントを UIApplication オブジェクトから受け取り、イベントを処理できるオブジェクトを探して送信します。

UIViewController クラス

画面に表示しているビューを監視する機能と他のビューコントローラに遷移させる機能を提供するクラス。このクラスに関する詳細は以下の記事を参照してください。

UIStoryboard クラス

Interface Builder のストーリーボードファイルに格納されているビューコントローラオブジェクトを格納する機能を持ったクラス。この記事の後半で詳しく説明します。

開発者が作成するクラス

シングルビューアプリケーションで使用するクラスの中で AppDelegate と ViewController クラスはアプリ開発者が作成するクラスです。2つのクラスは Xcode でプロジェクトを作成したときに自動生成されます。2つのクラスの名前は開発者が変更することもできます。今回使用したクラス名はプロジェクト作成時に自動生成される名前をそのまま使用しています。
以下は開発者が作成する2つのクラスの説明です。

AppDelegate クラス

UIApplicationDelegate プロトコルを実装したクラス。アプリが起動したり終了したりバックグラウンド状態になったりバックグラウンド状態から復帰したりしたときに UIApplication オブジェクトが AppDelegate オブジェクトのイベントメソッドを呼び出します。

ViewController クラス

UIViewController クラスを継承したクラス。開発者は1画面ごとに UIViewController クラスを継承したクラスを作成します。UI 部品のアクション(例えばボタンを押したときの動きなど)を定義したり、複数の UI 部品が連携した時の動きを定義したりします。

実際に動いているアプリのオブジェクトはどうなっているのか

シングルビューアプリケーションで使用されているクラスについて理解できたと思うので、実際にアプリが動いているときにオブジェクトがどのようになっているか見てみたいと思います。
まずはアプリ起動から画面表示までに各オブジェクトがどんな順番で生成されているか流れをまとめてみます。
以下はアプリが起動するまでの流れを図にしたものです。
アプリ起動の流れ

  1. main 関数から UIApplicationMain 関数が呼ばれる
  2. UIApplication クラスのインスタンスが生成される
  3. AppDelegate クラスのインスタンスが生成される
  4. Info.plist ファイルが読み込まれる
  5. UIApplication オブジェクトがイベントループ(Run Loop または実行ループとも呼ばれる)を実行する
    イベントループについての詳細は下記の記事を参照してください。
    -iOS のイベント駆動をライフサイクルイベントとユーザアクションイベントにわけて理解する
  6. Storyboard ファイルが読み込まれる
  7. UIApplication オブジェクトが ViewController クラスのインスタンスを生成する
  8. AppDelegate オブジェクトが UIWindow クラスのインスタンスを生成する
  9. AppDelegate オブジェクトの application: didFinishLaunchingWithOptions: メソッドが呼び出される

アプリの画面が表示された時点のオブジェクトとその関係を図にすると以下のようになります。
アプリを構成しているオブジェクト
ストーリーボードファイルの内容は UIStoryboard オブジェクトが保持します。
UIWindow オブジェクトはユーザからは見えませんがすべてのビューの親になるオブジェクトです。UIWindow オブジェクトには必ず1つの UIView オブジェクトがサブビューとして配置されています。
シングルビューアプリケーションのビュー階層を図にすると以下のようになります*3
ビューの階層
UIApplication オブジェクトの上に UIWindow オブジェクト、 UIView オブジェクトの順に重なっていてユーザからは UIView オブジェクトだけが見えるようになっています。

シングルビューアプリケーションにラベルとボタンを追加してみる

真っ白な画面を表示するシングルビューアプリケーションにラベルとボタンを追加するとアプリの構成がどうなるか見てみましょう。
以下の画面のアプリを例に説明していきます。
ラベルとボタンを表示するアプリ
上記のアプリの構成がどのようになっているかまずはクラス図を見てみます。以下はシングルビューアプリケーションにラベルとボタンを追加する場合に使用するクラスのクラス図です。
ラベルとボタン追加クラス図
真っ白な画面を表示するシングルビューアプリケーションのクラス図と比べると、画面にラベルを表示する UILabel クラスと画面にボタンを表示する UIButton クラスが追加されました。UILbael クラスは UIView クラスを直接継承していますが UIButton クラスは UIView クラスを直接継承せずにその子クラス UIControl クラスを継承しています。
それでは上記クラスがどのようにインスタンス化されるか見てみましょう。
以下はラベルとボタンを追加した画面を表示した時のオブジェクトを図にしたものです。
ラベルとボタン追加オブジェクト図
UILabel オブジェクトと UIButton オブジェクトが UIView オブジェクトのサブビューとして追加されたのがわかると思います。
シングルビューアプリケーションにラベルとボタンを追加した時のビュー階層を図にすると以下のようになります。
ラベルボタンを追加したビューの階層

シングルビューアプリケーションに画面を一つ追加してみる

ボタンを押すと新たに画面を表示するアプリの場合、その構造がどのようになるか見てみましょう。
以下の画面のアプリを例に説明していきます。
画面遷移するアプリ
ラベルとボタンを追加したアプリと比べてアプリの構成がどのようになっているかまずはクラス図で確認してみます。
以下はシングルビューアプリケーションに画面を追加する場合に使用するクラスのクラス図です。
画面を追加したときのクラス図
シングルビューアプリケーションに画面を追加するには UIViewController クラスを拡張したクラスを新たに作成します。図では新しい画面管理用の ModalViewController という名前のクラスを追加しています。
それでは追加した画面をモーダル(Model)で表示した時にオブジェクトの状態がどうなるのか見てみましょう。
以下は新規画面を表示したときのオブジェクトの状態を図にしたものです。
画面を追加したときのオブジェクト図
上記の図では、はじめに表示されていた画面のビューを管理している ViewController オブジェクトに rootViewController、新たに表示された画面のビューを管理する ModalViewController オブジェクトに destinationViewController という名前をつけています。
ユーザから画面が遷移したように見える動きは、画面を管理しているビューコントローラの制御が別のビューコントローラに切り替わったということになります。
ビューコントローラが遷移すると UIWindow オブジェクトの subview は ModalViewController オブジェクトの管理する UIView オブジェクトに変わります。
また遷移後は rootViewController オブジェクトの presentedViewController プロパティで遷移先の UIViewController オブジェクト(destinationViewController オブジェクト)を参照、destinationViewController オブジェクトの presentingViewController プロパティで遷移元の UIViewController オブジェクト(rootViewController オブジェクト)を参照することが出来ます。
なお「present」は表示や提示するという意味の動詞です。presentedViewController は遷移元からみて新たに表示されたビューコントローラー、presentingViewController は現在表示中のビューコントローラーを表示しているビューコントローラーという意味合いです*4

ストーリーボードとアプリの関係

iOS アプリのユーザインターフェースの設計は Interface Builder というツールを使って行います。アプリ開発者は Interface Builder を使ってストーリーボードと呼ばれるファイル(拡張子は .storyboard)にアプリのユーザインターフェースを作成していきます。
ストリーボードには画面のデザイン(例えば UI 部品の配置や背景色の設定など)に加えビューコントローラの遷移を定義することが出来ます。iOS ではビューコントローラの遷移のことをセグエ(Segue)と呼びます。
それではストリーボードで設計された内容をアプリがどのように読み込んで情報を管理しているか見ていきましょう。

クラス構成

まずはストーリーボードの情報を管理するクラスにどのようなものがあるか見ていきます。
ストーリーボード関連のクラスを図にまとめると以下のようになります。
ストーリーボード関連のクラス
画面デザインの情報は UIStoryboard オブジェクトが管理し、ビューコントローラの遷移情報は UIStoryboardSegueTemplate オブジェクトが管理します。画面デザインの情報は複数の UIViewController が共有するのに対し、ビューコントローラの遷移情報は UIViewController ごとに個別で保持します。また1つのビューコントローラに対して複数の遷移が定義できるため UIViewController クラスと UIStoryboardSegueTemplate クラスの関係は1対多になっています。
それではストリーボード関連のクラスの詳細を見ていきましょう。

UIStoryboard クラス

ストーリーボードファイルに格納されているビューコントローラオブジェクトを格納する機能を持ったクラス。アプリ起動時にストーリーボードファイルの情報からこのクラスのオブジェクトが生成されます。画面ごとに UINib オブジェクトに分割して管理します。

UINib クラス

ストーリーボードの画面デザインに関する情報を保持する機能を持ったクラス。ストーリーボードの情報を元にこのクラスのオブジェクトが UIViewController オブジェクトを生成します。

UIStoryboardSegueTemplate クラス

ビューコントローラの遷移情報を管理する機能を持ったクラス。このクラスは非公開クラスであり開発者がこのクラスのオブジェクトを直接参照することは出来ません。

UIStoryboardSegue クラス

遷移元と遷移先の UIViewController オブジェクトの情報を管理する機能を持ったクラス。遷移時に UIStoryboardSegueTemplate オブジェクトによってインスタンス化され UIViewController の prepareForSegue: sender: メソッドの引数として渡ってきます。

ストリーボードファイルはどのように管理されているのか

我々開発者から見るとストーリーボードはビジュアルなファイルですが、実際の中身は単なる XML ファイルです。ストーリーボードファイルはアプリ起動時に XML の内容が読み込まれ画面デザインの情報は UIStoryboard オブジェクトが、ビューコントローラの遷移情報は UIStoryboardSegueTemplate という非公開クラスのオブジェクトが保持します。ストリーボードの内容は画面デザインからビューコントローラの遷移までアプリのデザインの情報をすべて持っています。一つのオブジェクトで管理するには情報が大きいので、UIStoryboard オブジェクトは画面ごとに UINib オブジェクトに分割してその情報を管理しています。
ストーリーボードファイルとオブジェクトの関係
UIStoryboard と UIStoryboardSegueTemplate オブジェクトは UIViewController オブジェクトが生成された時に UIViewController のプロパティとしてセットされます。
以下はアプリを立ち上げたあと、最初の画面が表示された時のオブジェクトの状態を表した図です。
ストーリーボードオブジェクト図

ビューコントローラ遷移時の動き

ボタンを押すと新たに画面を表示するアプリを例にビューコントローラ遷移時の動きを見ていきます。
画面をモーダルで表示させる場合、以下の図のようにストーリーボードを使ってビューコントローラの遷移を定義することが出来ます。
ビューコントローラの遷移を定義する
このように定義しておくと開発者はプログラムを一切書かずにビューコントローラを遷移させることが出来ます。
それではストーリーボードに定義された遷移がプログラム上でどのように動くのか見てみましょう。
以下はユーザがボタンを押してビューコントローラが遷移する時の動きを図にしたものです。
画面遷移シーケンス図
はじめにユーザがボタンを押すとイベントが通知され ViewController オブジェクトに紐づいた UIStoryboardSegueTemplate オブジェクトの perform: メソッドが呼ばれます。UIStoryboardSegueTemplate オブジェクトは UIStoryboard オブジェクトから遷移先 UIViewController オブジェクトのインスタンスを取得します。次に UIStoryboardSegue オブジェクトを生成して遷移元と遷移先の UIViewController オブジェクトの情報をセットします。最後に遷移元 UIViewController オブジェクトの prepareForSegue:sender: メソッドを呼び遷移が行われることを通知します。prepareForSegue:sender: メソッドには引数として UIStoryboardSegue オブジェクトが渡ってきます。
以下は遷移時に呼ばれる UIViewController オブジェクトの prepareForSegue: sender: メソッドの例です。

@implementation ViewController

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
  NSLog(@"遷移元ビューコントローラ:%@", [segue sourceViewController]);
  NSLog(@"遷移先ビューコントローラ:%@", [segue destinationViewController]);
}

@end

このメソッドが呼ばれた後、遷移先 UIViewController オブジェクトの loadView や viewDidLoad メソッドが呼ばれ画面の表示が行われます。
UIStoryboardSegue オブジェクトは遷移時に一時的に使用されるオブジェクトです。遷移後は UIViewController オブジェクトの presentedViewController と presentingViewController プロパティで遷移先と遷移元ビューコントローラを参照することができます。
prepareForSegue:sender: メソッドはビューコントローラ同士のデータ受け渡しなどに使います。
以下はビューコントローラ遷移後のオブジェクトの状態を表した図です。
画面遷移後のオブジェクト図
遷移先ビューコントローラは遷移の情報を持っていないので UIStoryboard オブジェクトのみと関連しています。

ブログの記事が本になりました!

A Day In The Life の iOS に関する記事が書籍になりました。この記事の内容は書籍で読むことができます(2015年1月17日にSwiftに対応した改訂版がでました)。

本の内容に関する詳しい記事はこちらです。

*1:UIStatusBarWindow という非公開クラスのオブジェクトです

*2:ナビゲーションバーやツールバー、タブバーで使用する UIBarItem クラスとそのサブクラスを除きます

*3:UIApplication オブジェクトはビューの1種ではありませんがウインドウを管理しているオブジェクトなのでビューっぽく表現しています

*4:ビューコントローラーを表示するというよりもビューコントローラーが管理しているビューを表示すると言った方が正しいです