Edited at

汎用性が高い処理はクラス拡張してViewControllerをスマートに -UIAlertController編-

More than 3 years have passed since last update.


Abstract and Motivation

 こんにちは、Objective-Cを勉強し始めて約3か月が経ちました。最近はコーディングにも慣れてきたので、本格的にアプリを開発しているところです。しかし、それに伴っていろいろな課題に直面するようになりました。

 例えば、 ViewControllerが肥大化する ということ。ViewControllerといえば、ライフサイクルやIBActionの処理が集中します。割と削れる部分が限られる上、新たな機能を追加するうちにコード量も増えてしまう...。センター試験の英文を読むように、長い長いソースコードでも軽々と読めるようになるといいのでしょうが...、到底及びません。

それを避けるため、通常は利用頻度が高い処理や汎用性が高い処理に関しては、別クラスやメソッドにまとめようとします。特に新しいメソッドを定義する際に、ふと...


- 性質が似ている...

- メソッドをコピペした...


このようなことを感じることがあれば、私はそこで立ち止まり、一旦自分のコードを見返すことにしています。同じようなことを繰り返し書いている場合があるからです。おそらくコード量が増えたとしても、喜ぶような人はきっといないはずです。同じような性質の処理をスマートにまとめることができれば、コードの可読性が上がり、いざという時にコードの変更したり修正するのが楽ですね。

今回のお題は、アラート表示機能です。


Let's classify or organise into a particular structure!

アラートを表示する機能を実装するには、まずはViewController上でUIAlertControllerのインスタンスを生成し、表示するタイトルや内容、あるいはアラート表示後の処理内容をクロージャーなどで定義するという手続きをとります。この時、アラート上に表示されるタイトル、メッセージ、あるいはボタンを押した後の処理内容の違いを除けば、おそらく基本的な処理を行う手続きが非常に似ていることに気づきます。そこで、アラート機能に関連する箇所をまとめて別の場所で記述するようにする。そして、ViewController内のコード量を削減するぞ!というわけです。

そこで、私は次のような可能性を考えてみました。



  1. サブクラス 内でアラート機能を定義する。


  2. 親クラス UIViewControllerにアラート機能を組み込む。(厳密には拡張とは異なる)

(1)については,

nishinopさんの記事で説明されています。是非そちらをご覧になって下さい。

大雑把なイメージは、アラートを表示させるタイミングでサブクラスのインスタンス生成を宣言し、処理内容に応じてメソッドを呼び出して使うといったところでしょうか。簡単に定義できるので、一番シンプルな方法だと思います。

今回、私が紹介したい(2)は、UIViewControllerのカテゴリーを定義するという方法です。それぞれのViewControllerは、UIViewControllerを継承して利用しています。UIViewControllerで定義された内容は、基本的に子クラスに受け継がれるという仕組み(継承)が備わっていいます。今回アラート機能を実装するにあたって、まずはUIViewControllerのカテゴリークラスを作成しました。そして、その子クラスであるViewController自身のメソッド [self メソッド名] として呼び出せるようにしました。こうすれば、サブクラスを宣言してインスタンスを生成する必要がありません。ViewController自身から直接呼び出せるため、アラート表示機能の呼び出す箇所が多ければ多いほど、カテゴリーの強みが生きてくるかと思います。

では、早速カテゴリーを定義してみることにします。


How to define?

カテゴリ(Category)とは、あるクラスの一部分のメソッドを実現するモジュールを指します。そして、クラスと同様にインターフェイス部で宣言を書いて、実装部にそのメソッドの定義を記述します。注意すべき点としては、カテゴリではメソッドのみ(インスタンス変数は宣言できない)を含みます。


$宣言部$

@interface Class (Category)

Method;

@end


インターフェイス部では、次のように定義します。


$実装部$

@interface Class (Category)

Method;

@end


カテゴリの実装部にもインスタンス変数を宣言することはできません。あくまで、メソッド定義から、そのクラスの別のメソッドを呼び出したり、インスタンス変数にアクセスすることは自由にできます。

次に、カテゴリを利用してUIViewControllerにメソッドを付け加える方法を考えます。


How to add new method into UIViewController?

カテゴリのメリットは、既に現在用いられているクラスそのものにメソッドを追加できるという点にあります。

方法としては、

1.新しいクラスを作成する

スクリーンショット 2016-07-24 17.15.52.png

慣例として、既存のクラス名に追加する機能や性質が分かるように名前を付加するようです。もちろん、既存のクラス名と同じでなければ特に問題はありません。今回のカテゴリは、アラート機能を付加されるため +Alerts という名を付加しました。

宣言部と実装部のクラス名の隣には、必ずカテゴリ名を記述する。

以下は、実装部の記述例です。

スクリーンショット 2016-07-24 17.18.57.png

ここでは(Alerts) がカテゴリー名です。

2.新しいメソッドを宣言部で宣言する。


UIViewController+Alerts.h

@interface UIViewController (Alerts)

- (void)displayAlertWithTitle:(NSString *)title
message:(NSString *)text
actionWithTitle:(NSString *)actionTitle
clickedBlock:(void(^)(UIAlertAction *action))completion;

@end


3.新しいメソッドを実装部で定義する。


UIViewController+Alerts.m


#import "UIViewController+Alerts.h"

@implementation UIViewController (Alerts)

- (void)displayAlertWithTitle:(NSString *)title
message:(NSString *)text
actionWithTitle:(NSString *)actionTitle
clickedBlock:(void(^)(UIAlertAction *action))completion {

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
message:text
preferredStyle:UIAlertControllerStyleAlert];

[alertController addAction:[UIAlertAction actionWithTitle:actionTitle
style:UIAlertActionStyleDefault
handler:completion]];

[self presentViewController:alertController animated:YES completion:nil];

}

@end


4.サブクラス内にインポートする

カテゴリーを利用する場合には、必ず以下のような#importが必要です。

スクリーンショット 2016-07-24 17.17.09.png

5.UIViewController自身のメソッドとして呼び出す。

   if (self.textField.text.length > 30) {

[self (void)displayAlertWithTitle:@"確認"
message:@"最大文字数を超えています。"
actionWithTitle:@"OK"
clickedBlock:nil];
}


Summary

今回、私がカテゴリによる追加する方法を選んだのは、まさに簡単だったからです。もちろん、新しいサブクラスで置き換えて機能を追加するという方法もありました。しかし、特に注目したい点は、既存のメソッドにカテゴリをメソッドを追加すると、そのクラスのサブクラスからも新しいメソッドが利用できるという性質です。先にも述べましたが、今回のケースでは、アラート機能を各ViewControllerの標準的な仕様として実現したいというモチベーションがありました。親クラスであるUIViewControllerに機能を追加できれば、サブクラス(子クラス)内では自身のメソッドとして宣言できる強みを生かせるのです。そして、結果的に数行のコードで実装できるようにもなります。

  ただし、既存のメソッドと同名のメソッドを定義する際には注意が必要です。なぜなら、新たに定義されたメソッドが有効になることで、既存のメソッドは無効になるからです。


Finally

まだまだ、大いに議論の余地があるかと考えております。実は投稿するあたっては、以前何度かカテゴリーか? サブクラスか?という議論がありました。最終的に私が一番感じたことは、どちらが良いという結論よりも、両者の長所と短所を挙げた上で、プロダクトの仕様にフィットする方法を導き出していく過程こそが大いに意義深いと思いました。

もしも独自の意見がございましたら、ご教示を頂ければと思います。


謝辞

多大な助言を事前にいただきました。

この場をおかりいたしまして、

心から感謝の意をお伝えします。


参考文献

詳解 Objective-C 2.0 第3版

カテゴリーによるクラス拡張ではとても参考になりました。