Objective-Cのメソッド名は冗長的で読みづらいと言われていますが、メソッド呼び出し式を関数でラップすることで、より簡潔な記述を実現することができます。
// Before
[@"a" length];
[@"b" isEqualToString:@"b"]
[@(4) isEqualToNumber:@(4)]
// After
len(@"a");
eq(@"b", @"b");
eq(@(4), @(4));
目次
ヘルパー関数の作成方法
C++では関数の多重定義が可能となっています。そのため以下のような形で手軽に関数を作成することができます。
BOOL eq(NSString* a, NSString* b) { return [a isEqualToString:b]; }
BOOL eq(NSNumber* a, NSNumber* b) { return [a isEqualToNumber:b]; }
NSUInteger len(NSString* s) { return s.length; }
size_t len(const char* s) { return strlen(s); }
BOOL y = eq(@"a", @"a"); // YES
BOOL y = eq(@(9), @(9)); // YES
NSUInteger n = len(@"ab"); // 2
size_t n = len("ab"); // 2
同名の関数が複数定義されていても問題ありません。C++では型名による厳格なオーバーロード解決が行われるためです。方の種類に応じた関数が適切に呼び出されます。
NSString向け関数
以下はNSString用の便利機能です。独自のユーティリティ関数を自作する際の参考にして下さい。
NSString* join(NSString* a) { return a; }
template<class... T> NSString* join(NSString *a, T... b) { return [NSString stringWithFormat:@"%@%@", a, join(b...)]; }
NSString* replace(NSString* s, NSString* a, NSString* b) { return [s stringByReplacingOccurrencesOfString:a withString:b]; }
// Before
auto s = [NSString stringWithFormat:@"%@%@%@", @"a-b-c", @"-", @"d-e-f"];
puts([s stringByReplacingOccurrencesOfString:@"-" withString:@"_"].UTF8String);
// After
auto s = join(@"a-b-c", @"-", @"d-e-f");
puts(replace(s, @"-", @"_").UTF8String); // "a_b_c_d_e_f"
BOOL eq(NSString* a, NSString* b) { return [a isEqualToString:b]; }
template<class T> BOOL isin(T a) { return NO; }
template<class T, class U, class... A> BOOL isin(T a, U b, A... c) { return eq(a, b) ?: isin(a, c...); }
// Before
assert( [@"a" isEqualToString:@"b"] || [@"a" isEqualToString:@"a"] );
// After
assert( isin(@"a", @"b", @"a") );
名前空間の活用
Objective-C向けのヘルパー関数は、既存の関数との混在を考慮して、独自の名前空間で定義すると安全です。
namespace ns {
NSUInteger len(id a) { return [a length]; }
}
void a() {
ns::len(@"ab");
}
void b() {
using namespace ns;
len(@"ab");
}
void c() {
NSUInteger len = len(@"ab"); // ERROR: Called object type 'NSUInteger' (aka 'unsigned long') is not a function or function pointer
NSUInteger len = ns::len(@"ab"); // ok
}
超万能自由関数
NSObjectの派生クラスをid型の引数として受け取ることで、任意のメソッドを呼び出すことが可能となります。
NSUInteger len(id a) { return [a length]; }
BOOL eq(id a, id b) { return [a isEqual:b]; }
上記の関数にはそれぞれlength
, isEqual:
メソッドを実装したObjective-C製のオブジェクトを渡すことができます。
eq(@"a", @"a"); // ok
eq(@(9), @(9)); // ok
eq([NSObject new], [NSObject new]); // ok
ただし、id
型を利用した場合、戻り値型の型推論は有効に働きません。その場合はテンプレート関数を用いて対処します。
template<class T> auto len(T* a) -> decltype(a.length) { return [a length]; }
template<class T> auto len(T* a) -> decltype(strlen(a)) { return strlen(a); }
decltype( len(@"abc") ); // NSUInteger
decltype( len("abc") ); // size_t
なお、戻り値型に対するdecltypeの利用(decltype([a length])
)はエラーとなる点に注意が必要です(上記の方法のように、プロパティー呼び出し(a.length
)を用いればエラーにはなりません。)。
// Cannot yet mangle expression type ObjCMessageExpr
template<class T> auto len(T* a) -> decltype([a length]) { return [a length]; }