2012/04/24

DelegateをBlockを使って実装

iOS、というかCocoa Frameworkが利用しているdelegateは非常に便利なのですが、複数のDelegateに対応する場合コンポーネントに関する記述が分散して読みにくくなる面があると思います。

iOS4からはBlockが利用できるので、delegateで実現されている一部の機能はBlockで記述した方が簡潔に記述できます。ちょっと調べてみると、やはりUIActionSheet等でdelegateを使うことに不満を持ち、サブクラスやカテゴリを利用してBlockに対応させている人も多いようです。

調べている過程でBlockも@propertyに設定できることがわかったので、それを利用してDelegateをBlock対応にするクラスを書いてみました。このDelegateオブジェクトにプロトコルを中継させることで、呼び出し元からはBlockのみで記述できるようになります。Delegateは通常弱参照しかされないので、オブジェクトの参照管理には注意してください。

具体的な実装例があった方がわかりやすいと思うので、UIImagePickerController用のコードを引用します。@property + @synthesizeを利用することで、かなり簡潔に記述できることがわかると思います。ダイアログ表示、通常処理、キャンセル処理が一カ所で記述できるので、非常に読みやすいコードになりました。

2012/5/12 追記
Delegateを実装していない場合の挙動に影響を与えないように、Blockを設定しているかどうかをrespondsToSelectorで確認するようにしました。


#import <Foundation/Foundation.h>

@interface BTKUIImagePickerControllerDelegate : 
    NSObject<UIImagePickerControllerDelegate,UINavigationControllerDelegate>

//UIImagePickerControllerDelegate
@property(readwrite,copy) void (^didFinishPickingMediaWithInfo)(UIImagePickerController *,NSDictionary *);
@property(readwrite,copy) void (^imagePickerControllerDidCancel)(UIImagePickerController *);

//UINavigationControllerDelegate
@property(readwrite,copy) void (^willShowViewController)(UINavigationController *, UIViewController *,BOOL);
@property(readwrite,copy) void (^didShowViewController)(UINavigationController *, UIViewController *,BOOL) ;

@end


#import "BTKUIImagePickerControllerDelegate.h"

@implementation BTKUIImagePickerControllerDelegate

@synthesize didFinishPickingMediaWithInfo=_didFinishPickingMediaWithInfo;
@synthesize imagePickerControllerDidCancel=_imagePickerControllerDidCancel;

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if(aSelector == @selector(imagePickerController:didFinishPickingMediaWithInfo:)){
        return self.didFinishPickingMediaWithInfo!=nil;
    }
    if(aSelector == @selector(imagePickerControllerDidCancel:)){
        return self.imagePickerControllerDidCancel!=nil;
    }
    if(aSelector == @selector(navigationController:willShowViewController:animated:)){
        return self.willShowViewController!=nil;
    }
    if(aSelector == @selector(navigationController:didShowViewController:animated:)){
        return self.didShowViewController!=nil;
    }
    return [super respondsToSelector:aSelector];
}

- (void)        imagePickerController:(UIImagePickerController *)picker 
        didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    self.didFinishPickingMediaWithInfo(picker,info);
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker;
{
    self.imagePickerControllerDidCancel(picker);
}

@synthesize willShowViewController=_willShowViewController;
@synthesize didShowViewController=_didShowViewController;

- (void)navigationController:(UINavigationController *)navigationController
      willShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animated
{
    self.willShowViewController(navigationController,viewController,animated);
}

- (void)navigationController:(UINavigationController *)navigationController
       didShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animated
{
    self.didShowViewController(navigationController,viewController,animated);
}

@end

呼び出し元からは、下記のようにBlockを設定できます。(ARC使用)

self.imagePickerDelegate = [[BTKUIImagePickerControllerDelegate alloc]init];
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self.imagePickerDelegate;

__weak BTKViewController* bself = self;       
imagePickerDelegate.imagePickerControllerDidCancel = 
        ^(UIImagePickerController *imagePicker){
            //Close Dialog
            [imagePicker dismissModalViewControllerAnimated:YES];
            
            //Cleanup
            bself.imagePickerDelegate = nil;
        };

0 件のコメント:

コメントを投稿