Objective-C クラスにプロトコルを実装する : Objective-C プログラミング
PROGRAM
Objective-C クラスにプロトコルを実装する
Objective-C では、クラスが実装すべき機能をプロトコルとして定義することができます。
あるプロトコルに準拠したクラスであれば、そのプロトコルに定義されている機能を実装しているものとして扱うことができるので、単純に実装を統一させるためだけでなく、場面によっては同種のクラスとして一括して扱うことも可能になります。
プロトコルの定義は、簡単に整理すると、次のようになります。
// プロトコルを定義します。既存のプロトコルの継承も出来ます。
@protocol EzCustomProtocol <NSObject>
// 必ず実装しないといけない機能は @require 以下に記載します。
@required
- (NSString*)toString;
// 必要に応じて実装すればいい機能は @optional 以下に記載します。
@optional
@property (atomic,readonly) BOOL isEmpty;
- (void)setString:(NSString*)string;
@end
プロトコルの定義では、最初にプロトコル名を指定します。
必要に応じて継承するプロトコルを < > の中に指定することができます。複数のプロトコルを継承したい場合にはカンマで区切って指定します。
そして、このプロトコルが提供するメソッドやプロパティの定義を記載して行きます。プロトコルではインスタンス変数は定義できません。
@required が登場したところよりも下に記載された機能は、このプロトコルに準拠したクラスに必ず実装する機能になります。そして @optional が登場したところよりも下に記載された機能は、このプロトコルに準拠していても実装されているとは限らない機能です。
プロトコルに定義されている機能の実装はそれに準拠したクラスに任されるので、プロトコルの定義ではこのように、あくまでもその機能が必須かどうかを指定するまでの役目になります。
なお、プロトコルは継承しなくてもエラーにはなりませんが、最低でも NSObject プロトコルを継承しておくと後が便利です。
特に @optional で機能を定義している場合、その機能が実際に実装されているかどうかを -respondToSelector: メソッドで判定することになるので、このメソッドが定義されている NSObject プロトコルを継承しておいた方が、プログラミングがしやすくなります。
クラスをプロトコルに準拠させる
クラスをプロトコルに準拠させたい場合は、Objective-C クラスの @interface 定義のところで、準拠させたいプロトコルを指定します。
プロトコルはリスト指定できるので、複数のプロトコルに準拠させたい場合には < > の中にプロトコルをカンマ区切りで並べます。
@interface の定義にはいくつか種類があるので、それらについてプロトコルを実装する方法について見てみます。
通常のプロトコル実装
まず、通常の Objective-C クラス実装です。
// ヘッダーファイルで、基本通りのクラス実装を行っています。
@interface EzSampleClass : NSObject <NSCoding, NSCopying>
この場合は、基底クラスを記述した後に、準拠させたいプロトコルを < > で指定します。
クラス拡張でのプロトコル実装
Objective-C クラス拡張による実装でも、プロトコルを指定することができます。
クラス拡張というのは、通常の @interface 実装とは別に、@interface クラス名 () とすることで、そのクラスの定義を追加できる機能です。実装内部だけで使用するメソッドを定義するようなときに便利です。
// クラス拡張に対してプロトコルを実装しています。
@interface EzSampleClass () <NSCoding, NSCopying>
この場合は、クラス拡張を示す () に続けて、準拠させたいプロトコルを < > で指定します。
プロトコルの実装を、クラス拡張を使って実装ファイル内で定義することで、ヘッダーファイル内からプロトコルの実装の記述を隠すことができます。
ヘッダーファイルからプロトコルの実装を隠すことで、プログラミングでこのクラスを外から利用する時に Xcode がそのプロトコルによって実装されているメソッドを補完候補として表示させなくなるので、コーディングがしやすくなります。
デリゲートプロトコルのように内部だけでの実装で完結するような場面で、このようなプロトコル実装が向いていると思います。
カテゴリでのプロトコル実装
Objective-C カテゴリでの実装でも、プロトコルを指定することができます。
カテゴリというのは、通常の @interface 実装とは別に、@interface クラス名 (カテゴリ名) とすることで、そのクラスの定義を追加できる機能です。別ファイルでも実装できるので、定義済みのクラスの機能を拡張したいときに便利です。
// カテゴリに対してプロトコルを実装しています。
@interface EzSampleClass (Category) <NSCoding, NSCopying>
この場合は、カテゴリ定義に続けて、準拠させたいプロトコルを < > で指定します。
こちらもクラス拡張のときと同様で、実装ファイル内で @interface を定義することで、プロトコルに定義されている内容を Xcode のコード補完に登場させないようにできます。
プロトコルを実装したインスタンスを受ける変数
例えば NSString は NSCopying プロトコルを実装しています。
この時、NSString 型のインスタンスを NSString* 型の変数で受けられるのは当然ですが、次のように id<NSCopying> という型で受けることも可能です。
// NSCopying プロトコルを実装したインスタンスは id<NSCopying> でも受けられます。
id<NSCopying> instance = string;
他にも NSNumber も NSCopying プロトコルを実装しているので、同じ id<NSCopying> で受けられます。
// NSNumber も NSCopying プロトコルを実装しているので id<NSCopying> で受けられます。
id<NSCopying> instance = number;
変数に受けることについては、どの型でも無理やりキャストすることで実現することはできますけど、このように id<プロトコル名> の変数で受けることで、理屈通りに扱えることが重要です。
そして、そもそもの id 型や NSObject 型で受けるのとも違い、そのプロトコルに定義されている機能をプログラムで簡単に呼び出せることもメリットです。
例えば NSArray 型の変数 instances に UITextInput プロトコルを実装しているインスタンスが格納されている場合、次のようにして簡単に、それらの機能をループで順次呼び出すことが簡単な記述で可能になります。
for (id<UITextInput> object in instances)
{
object.selectedTextRange = range;
}
このように、共通の目的で作られたオブジェクトをひとくくりにして処理できるのは、応用力の高い便利な機能です。
あるプロトコルをインスタンスが実装しているか判定する
Objective-C では、変数に格納されているインスタンスが、あるプロトコルを実装しているかどうかを調べることもできます。
たとえば、id<NSObject> 型の変数 object に格納されているインスタンスが NSCoding プロトコルを実装しているかどうかを知りたい場合は、次のようにすることで判定することができます。
if ([object conformsToProtocol:@protocol(NSCoding)])
{
}
このように、NSObject に実装されている -conformsToProtocol: メソッドを使用することで、呼び出し元のインスタンスが、引数で指定したプロトコルを実装しているかどうかを判定できます。
通常のクラス定義でも、クラス拡張でも、カテゴリでも、どれかでプロトコルが実装されていれば -conformsToProtocol: は YES を返します。
ただし、カテゴリ実装の場合、@interface 宣言だけして @implementation を忘れていると、指定されているプロトコルであっても -conformsToProtocol: は NO を返すようでした。
また、基底クラスでプロトコルが実装されている場合、その情報が派生クラスへ継承されます。
そのため、派生先ではプロトコルを特に指定しなくても、クラス拡張やカテゴリなどの実装方式にかかわらず、派生先から呼び出した -conformsToProtocol: は YES を返します。
[ もどる ]