Swift時代のDelegate通知
Objective-C時代のDelegate
今は昔、Objective-Cの時代ではOwnerへの通知にDelegateを使用していました。 言語仕様としてDelegate機構が用意されているわけでも、Delegateは通知のための機構であるというわけでもなく、CocoaフレームワークでDelegateパターンが通知に多用されていた、ということです。
Delegateの実装としては、概ね下記のようになっています。
通知を受け取るReceiverと、通知を送るSenderがいます。
Senderはプロパティとしてdelegate
をもっており、delegate
プロパティにはReceiverがセットされます。
また、Senderのヘッダ内で、「Delegateがどのような通知に対して処理が出来るのか」 = 「Delegateが受け取ることができるメソッド」 が定義されます。
定義には、NSObject
クラスのカテゴリか、プロトコルを使用します。
プロトコルで定義するメソッドには、「Required: Delegateに実装されていないといけないメソッド」と「Optional: 通知は送るが、実装されていなくてもよいメソッド」の二種類があります。
Receiverでは、定義されたメソッドを実装し、通知を受け取ったときの処理を記述します。
通知を送る処理はSenderで実装します。単純にdelegate
プロパティにメッセージを送るだけなので、こんなかんじです。
[self.delegate someMethod];
ただし、delegate
プロパティ = Receiver がsomeMethod
を実装していない場合、存在しないメソッドを呼び出すこととなり、クラッシュを引き起こします。
当然クラッシュは好ましくないため、respondsToSelector:
を使い、メソッドを呼ぶ前にメソッドの存在確認を行なうのが通例です。
if ([self.delegate respondsToSelector:@selector(someMethod)]) { [self.delegate someMethod]; }
これが典型的なObjective-CのDelegateの使用例となります。
Swift時代のDelegate
まず、SwiftでもObjective-CスタイルのDelegateの実装は可能です。
コードで示します。
// Protocolの定義 @objc protocol SampleDelegate { func requiredMethod() -> () optional func optionalMethod() -> () } // Receiver: 通知を受け取る方 class Receiver :SampleDelegate { //SampleDelegateプロトコルに準拠していることを示す let sender :Sender = Sender() init() { // Senderのdelegateに自分を指定 sender.delegate = self // senderに通知を送ってもらう sender.notify1() sender.notify2() } // SampleDelegateプロトコルに準拠しているので、`requiredMethod`を実装しないとコンパイルできない func requiredMethod() { println("required method was called") } // `optionalMethod`はoptionalなので実装しなくてもよい } // Sender: 通知を送る方 class Sender { // delegate にはSampleDelegateプロトコルに準拠したオブジェクトかnilが入る weak var delegate :SampleDelegate? = nil // requiredなメソッドを呼び出す func notify1() { self.delegate?.requiredMethod() } // optionalなメソッドを呼び出す func notify2() { self.delegate?.optionalMethod?() // メソッド呼び出しに?が付いているのがキモ } }
プロトコル定義に@objc
属性が付いているのが嫌なかんじです。
これは、SwiftのプロトコルではOptionalを指定できないために付けています。*1
Swiftのみで完結させようとすると、プロトコルに定義したメソッドがすべてRequiredになってしまうということですね。
カジュアルにDelegateメソッドをバンバン追加していると、「これ全部実装しないといけないのかよ」となり、使い勝手が非常に悪いです。
UIScrollViewDelegate
の定義なんてこんなんですよ。これでもマシなほうですけど。
@protocol UIScrollViewDelegate<NSObject> @optional - (void)scrollViewDidScroll:(UIScrollView *)scrollView; // any offset changes - (void)scrollViewDidZoom:(UIScrollView *)scrollView NS_AVAILABLE_IOS(3_2); // any zoom scale changes // called on start of dragging (may require some time and or distance to move) - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView; // called on finger up if the user dragged. velocity is in points/millisecond. targetContentOffset may be changed to adjust where the scroll view comes to rest - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset NS_AVAILABLE_IOS(5_0); // called on finger up if the user dragged. decelerate is true if it will continue moving afterwards - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView; // called on finger up as we are moving - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; // called when scroll view grinds to a halt - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView; // called when setContentOffset/scrollRectVisible:animated: finishes. not called if not animating - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView; // return a view that will be scaled. if delegate returns nil, nothing happens - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view NS_AVAILABLE_IOS(3_2); // called before the scroll view begins zooming its content - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale; // scale between minimum and maximum. called after any 'bounce' animations - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView; // return a yes if you want to scroll to the top. if not defined, assumes YES - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView; // called when scrolling animation finished. may be called immediately if already at top @end
これが全てRequiredだったら..と考えるとゾッとします。 メソッドの中身は空でもいいけど、とにかく全部実装してあげないとコンパイルが通らないんですからね。
今後
当面は@objc
を使っていればよいのですが、今後どうなっていくんでしょうか。
ニュービーなので実際のところはよくわかりませんが、Objective-CでDelegateが多用されているのは、Cocoaフレームワークで多用されていて慣れ親しんでいるからだろうと思います。
今後、Objective-Cの領域が徐々に狭くなっていくだろうことが想像されます。その中で、こういった通知の仕組みもSwiftフレンドリーな書き方に置き換わっていくんでしょうか。