Skip to content

Instantly share code, notes, and snippets.

@hiromitsu-murakami
Last active August 18, 2017 03:34
Show Gist options
  • Save hiromitsu-murakami/5581930 to your computer and use it in GitHub Desktop.
Save hiromitsu-murakami/5581930 to your computer and use it in GitHub Desktop.
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 <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
#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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment