Objective-C++でヘルパー関数を自作する【メソッド名を短くする】

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]; }
広告