Objective-C/Swiftでは各オブジェクトの応答できるメソッドの約束事(他のプログラミング言語ではInterfaceと呼ばれるもの)としてProtocolを宣言する。
Protocolの中でもUIKitに含まれるようなビュークラスでは、Cocoa Delegation Patternと形容されるやり方が存在しており、自前でカスタムビューを作る時も、このパターンに合わせておくとユーザーコードも分かり易いものになる。
Swiftでこのやり方をする場合のコードを整理しておく。
Objective-Cの場合は、定義したカスタムビューのヘッダファイル前方にDelegationの仕様を明示する事が一般的だと思う。
@class
でカスタムビューの型を宣言する事で、コンパイルエラーとならずにカスタムビュー定義の前方にProtocolを書ける。
メソッドシグネチャの第1引数でカスタムビュー自身のオブジェクトを渡すように設計しておくと、画面内で複数配置しているようなケースで、どのビューから応答が来たか判別できる。UIKitのクラスでは大抵こうなっている。
カスタムビュー側では weak
参照した状態で MCVMyCustomViewDelegate
に適合するオブジェクトを保持する。
#import <UIKit/UIKit.h>
@class MCVMyCustomView;
@protocol MCVMyCustomViewDelegate <NSObject>
@optional
- (void)myCustomViewDidLoad:(MCVMyCustomView *)myCustomView;
- (void)myCustomView:(MCVMyCustomView *)myCustomView didSelectAtViewNumber:(NSUInteger)viewNumber;
@end
@interface MCVMyCustomView : UIView
@property (weak, nonatomic) id <MCVMyCustomViewDelegate> delegate;
@end
MCVMyCustomView.m
から応答する時は respondsToSelector
で実装されているか確認した上でメッセージを送信する(クラッシュ対策)。
if ([self.delegate respondsToSelector:@selector(myCustomViewDidLoad:)]) {
[self.delegate myCustomViewDidLoad:self];
}
if ([self.delegate respondsToSelector:@selector(myCustomView:didSelectAtViewNumber:)]) {
[self.delegate myCustomView:self
didSelectAtViewNumber:1];
}
Swiftの場合もObjective-C同様のところに気を遣って設計すると、以下のようなコードになる。
Protocolで宣言したメソッドへの適合(実装)を任意としたい時は @objc
属性か NSObject
の継承が必要になる。
カスタムビューでは、このProtocolをOptionalなweak参照のオブジェクトとして保持するのが妥当だと考えられる。
import UIKit
@objc protocol MyCustomViewDelegate: NSObject {
optional func myCustomViewDidLoad(myCustomView: MyCustomView)
optional func myCustomView(myCustomView: MyCustomView, didSelectAtViewNumber viewNumber: UInt)
}
class MyCustomView: UIView {
weak var delegate: MyCustomViewDelegate?
}
応答コードはSwiftでは非常に短く書ける。
ただし、型としてのOptionalや実装任意のメソッド呼び出しを安全に取り出すため各所に ?
が登場し、やや読みにくい印象を受ける。
delegate?.myCustomViewDidLoad?(self)
delegate?.myCustomView?(self, didSelectAtViewNumber: 1)