SwiftでCocoa Delegation Patternに合わせたカスタムビューのProtocolを設計する

By raimon, 2015-07-12(日), in category Ios

iOS, Objective-C, Swift

Delegateを通したカスタムビューとのやり取り

Objective-C/Swiftでは各オブジェクトの応答できるメソッドの約束事(他のプログラミング言語ではInterfaceと呼ばれるもの)としてProtocolを宣言する。

Protocolの中でもUIKitに含まれるようなビュークラスでは、Cocoa Delegation Patternと形容されるやり方が存在しており、自前でカスタムビューを作る時も、このパターンに合わせておくとユーザーコードも分かり易いものになる。

Swiftでこのやり方をする場合のコードを整理しておく。

Objective-Cの場合

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の場合

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)

参考情報