matsuda
5/15/2013 - 6:06 AM

Objective-C Nil Blocks Pattern Macro

Objective-C Nil Blocks Pattern Macro

Blocks の nil チェックをスルーするマクロ

Blocksがnilの場合、実行時にクラッシュするため、nilチェックを行う必要がある。

マクロはBlocks の nil チェックを省略表記する。 Blocks が nil だった場合は実行せず、戻り値も0を返す。 NSObject が nil のものにメッセージを送ったときと似たような動きをする。

if (block) {
    block(a, b);
}
#define FPB(block)  !(block) ? 0 : (block)

FPB(block)(a, b);  // OK
FPB(block);          // NG

Blocksはコールバック目的で使うことが多いので、呼び出し元がnilを指定したならスルーするといった程度の緩さで良いと思われる。 値を返すような使い方をするときは、後述の既知の不具合もあるので、慎重に使うか、普通の分岐処理を書く方が良いと思われる。

既知の不具合

全体を()で囲んでおらず、三項条件演算子の条件部分がむき出しなので、式の途中で使うと予期せぬ不具合が起こるので注意する。

int a = 1 + (FPB(block)(10));   // OK、式の途中で使う場合は()で囲む必要がある
int a = 1 +  FPB(block)(10);    // NG、(1 + !block) ? 0 : block(10); という意味になり、不具合になる

次のようなマクロも考えてみたが、コンパイルエラーを避けるために戻り値の型で使い分ける必要がある。 エラーになる分、こちらが安全か?

int __attribute__((overloadable)) FPSwamp(void)         { return 0; }
int __attribute__((overloadable)) FPSwamp(int arg, ...) { return 0; }
int __attribute__((overloadable)) FPSwamp(id  arg, ...) { return 0; }

id __attribute__((overloadable)) FPSwamp4o(void)         { return 0; }
id __attribute__((overloadable)) FPSwamp4o(int arg, ...) { return 0; }
id __attribute__((overloadable)) FPSwamp4o(id  arg, ...) { return 0; }

#define FPB2(block, ...)    (block ? block(__VA_ARGS__) : FPSwamp(__VA_ARGS__))
#define FPB2O(block, ...)   (block ? block(__VA_ARGS__) : FPSwamp4o(__VA_ARGS__))

typedef int (^FPIntBlocks)(int a);
typedef NSString *(^FPStringBlocks)(NSString *a, NSString *b);

FPIntBlocks    intBlock  = ^int (int a) { return a + 1; };
FPStringBlocks strBlocks = ^NSString * (NSString *a, NSString *b) { return a; };

// FPB2とFPB2Oを使い分ける必要がある
int b = 1 + FPB2(intBlock, 2);
NSString *c = FPB2O(strBlocks, @"a", @"b");
#import "FPNilBlocksPattern.h"

// Sample
typedef void (^FPVoidBlocks)(void);
typedef void (^FPArgBlocks)(BOOL success);
typedef BOOL (^FPReturnBlocks)(void);
typedef BOOL (^FPBothBlocks)(BOOL success);
typedef id   (^FPObjectBlocks)(id obj);
typedef id   (^FPTwoBlocks)(id obj, BOOL success);


// Sample
@implementation FPNilBlocksPattern

+ (void)runBlocks
{
    // Void
    {
        FPVoidBlocks block = ^{
            NSLog(@"Void run");
        };
        
        FPB(block)();
        
        // nil
        block = nil;
        
        FPB(block)();
    }
    
    // Arg
    {
        FPArgBlocks block = ^(BOOL success) {
            NSLog(@"Arg run %d", success);
        };
        
        FPB(block)(YES);
        
        // nil
        block = nil;
        
        FPB(block)(YES);
    }
    
    // Return
    {
        FPReturnBlocks block = ^{
            NSLog(@"Return run");
            return YES;
        };
        
        BOOL rt1 = FPB(block)();
        
        NSLog(@"Return return1 %d", rt1);
        NSAssert(rt1 == YES, @"Return Error");
        
        // nil
        block = nil;
        
        BOOL rt2 = FPB(block)();
        
        NSLog(@"Return return2 %d", rt2);
        NSAssert(rt2 != YES, @"Return Error");
    }
    
    // Both
    {
        FPBothBlocks block = ^(BOOL success) {
            NSLog(@"Both run %d", success);
            return YES;
        };
        
        BOOL b1 = FPB(block)(YES);
        
        NSLog(@"Both return1 %d", b1);
        NSAssert(b1 == YES, @"Both Error");
        
        // nil
        block = nil;
        
        BOOL b2 = FPB(block)(YES);
        
        NSLog(@"Both return2 %d", b2);
        NSAssert(b2 != YES, @"Both Error");
    }
    
    // Object
    {
        FPObjectBlocks block = ^(id obj) {
            NSLog(@"Run Object run %@", obj);
            return @"ReturnObject";
        };
        
        id o1 = FPB(block)(@"arg1");
        
        NSLog(@"Object return1 %@", o1);
        NSAssert(o1 != nil, @"Object Error");
        
        // nil
        block = nil;
        
        id o2 = FPB(block)(@"arg2");
        
        NSLog(@"Object return2 %@", o2);
        NSAssert(o2 == nil, @"Object Error");
    }
    
    // Two
    {
        FPTwoBlocks block = ^(id obj, BOOL success) {
            NSLog(@"Two run %@ %d", obj, success);
            return @"ReturnObject";
        };
        
        id o1 = FPB(block)(@"arg1", YES);
        
        NSLog(@"Two return1 %@", o1);
        NSAssert(o1 != nil, @"Two Error");
        
        // nil
        block = nil;
        
        id o2 = FPB(block)(@"arg2", YES);
        
        NSLog(@"Two return2 %@", o2);
        NSAssert(o2 == nil, @"Two Error");
    }
    
    // Direct
    {
        FPB(^{
            NSLog(@"Direct run");
        })();
    }
    
    // Nil
    {
        FPB((FPVoidBlocks)nil)();
    }
    
    // OtherMethod
    {
        [[self class] otherMethod:^id(id obj, BOOL success) {
            NSLog(@"OtherMethod run %@ %d", obj, success);
            return @"ReturnObject";
        }];
        
        [[self class] otherMethod:nil];
    }
    
    // BUG
    {
        FPVoidBlocks block = ^{
            NSLog(@"Void run");
        };
        
        // forget "()".
        
        FPB(block)();               // OK
        
//        FPB(block);                 // warning: expression result unused
        
//        BOOL b = FPB(block);     // error: expression of incompatible type
        
        NSString *str = FPB(block); // no error. no warning. but this is BUG.
        NSLog(@"BUG %@", str);      // BUG <__NSGlobalBlock__: 0xABC>
        
        NSString *str2 = block;     // Why?
        NSLog(@"BUG %@", str2);
    }
}

+ (void)otherMethod:(FPTwoBlocks)block
{
    double delayInSeconds = 0.5;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        
        id o1 = FPB(block)(@"arg1", YES);
        
        NSLog(@"OtherMethod return %@", o1);
        
        if (block) {
            NSAssert(o1 != nil, @"OtherMethod Error");
        } else {
            NSAssert(o1 == nil, @"OtherMethod Error");
        }
    });
}

@end

#import <Foundation/Foundation.h>


// return 0 if nil Blocks. (like nil NSObject)
// ex. FPB(block)();                   // OK
//     FPB(block)(obj1, obj2);         // OK
//     NSString *str = FPB(block)();   // OK
//     FPB(block);                     // NG
//     NSString *str = FPB(block);     // NG, no warning
//     int a = 1 + (FPB(block)(10));   // OK, need ()
//     int a = 1 +  FPB(block)(10);    // NG, int a = (1 + !block) ? 0 : block(10); // bug

// Blocks の nil チェックを省略する
// ※ 全体を()で囲んでおらず条件部分がむき出しなので、式の途中で使うと予期せぬ不具合が起こるので注意する
// return 0 if nil Blocks. (like nil NSObject)
// ex. FPB(block)();                   // OK
//     FPB(block)(obj1, obj2);         // OK
//     NSString *str = FPB(block)();   // OK
//     FPB(block);                     // NG
//     NSString *str = FPB(block);     // NG、警告されないので注意
//     int a = 1 + (FPB(block)(10));   // OK、式の途中で使う場合は()で囲む必要がある
//     int a = 1 +  FPB(block)(10);    // NG、(1 + !block) ? 0 : block(10); という意味になり、不具合になる

#define FPBlocks(block)    !(block) ? 0 : (block)
#define FPB(block)         FPBlocks(block)


// Sample
@interface FPNilBlocksPattern : NSObject
+ (void)runBlocks;
@end