Edited at

Objective-C++でSwiftに挑む

More than 5 years have passed since last update.

Swiftの新しい言語仕様も良いですが、Objective-CとC++が入り混じったカオスさも自分は結構好きだったりします。

そこでSwiftのおいしい機能にあえてObjective-C++で立ち向かってみましょう。

※ここではC++11を使っていくのを前提にしています


ターゲット1 型推論

SwiftにはObjective-Cには無い型推論があります。

let a = 10.0

let b = hoge

等のように型を明示的に指定せずとも使えますね。

しかしObjective-Cにはid型が、というのはありますが、id型自体たいして使い勝手の良いものではありません。

ですがC++と連携すればその弱点等は軽く克服してしまいます。

auto a = 0.0;

auto b = @"";
auto c = [NSDate date];

Objective-C特有の型だってもちろん使えます。


ターゲット2 コレクション型

Swiftでは基本となるコンテナクラスは今のところDictionaryとArrayのみという極めて貧弱極まりない環境です。

しかしその点Objective-C++なら、Objective-Cのコレクションクラスはもちろん、

状況に応じてC++のよく熟成された標準ライブラリに簡単に統合することが可能ですね。

例えば以下のように

#import <Foundation/Foundation.h>

#include <map>

int main(int argc, const char * argv[])
{
@autoreleasepool {
struct CompareNSString {
bool operator()(NSString * lhs, NSString *rhs)const
{
return [lhs compare:rhs] == NSOrderedAscending;
}
};

std::map<NSString *, int, CompareNSString> calendar;
calendar[@"A"] = 1;
calendar[@"C"] = 3;
calendar[@"B"] = 2;
calendar[@"D"] = 4;

for(auto keyvalue : calendar)
{
NSLog(@"key = %@, value = %d", keyvalue.first, keyvalue.second);
}
}
return 0;
}

NSStringとstd::mapを組み合わせた実装も簡単です。

基本C++のコンテナにObjective-Cの変数を突っ込んでもしっかりstrong参照で管理されるので、

この辺りも安心です。


ターゲット3 配列

NSMutableArrayのaddObject:や、std::vector<>のpush_backが面倒だって?

boostを使ってください。Header Search Pathだけ設定すれば使えるものも多いです。

#import <Foundation/Foundation.h>

#include <vector>
#include <boost/assign/std/vector.hpp>

int main(int argc, const char * argv[])
{
@autoreleasepool {
using namespace boost::assign;
std::vector<NSString *> strings;
strings += @"one";
strings += @"two";
strings += @"three", @"four";

for(auto s : strings)
{
NSLog(@"%@", s);
}
}
return 0;
}


ターゲット4 クロージャ

Swiftになっても、結局循環参照等問題は解決せず、基本構文がよくなったと見てるひとも多いようですね。

C++のクロージャは少し癖がありますが、じゃあそれならblocksでいいじゃないか。

また、autoと組み合わせれば、糞だったblocksの構文も結構簡単になりますね。

当然blocksだってSTLに渡せます。

int main(int argc, const char * argv[])

{
@autoreleasepool {
auto power = ^(int x){
return x * x;
};
std::vector<int> values = {1, 2, 3, 4, 5};
std::transform(values.begin(), values.end(), values.begin(), power);
std::sort(values.begin(), values.end(), ^(int lhs, int rhs){ return lhs > rhs; });
std::for_each(values.begin(), values.end(), ^(int value){
NSLog(@"%d", value);
});
}
return 0;
}


ターゲット5 タプル

これはSwift有利に見えますね。

でも、std::tuppleなら華麗に解決してくれます

@egtraさんのコメントにより、boost::tupleからstd::tupleに修正しました。ありがとうございました!

#import <Foundation/Foundation.h>

#include <tuple>

namespace
{
std::tuple<int, NSString *> httpError() {
return {404, @"Not Found"};
}
}

int main(int argc, const char * argv[])
{
@autoreleasepool {
int statusCode;
NSString *description;
std::tie(statusCode, description) = httpError();

NSLog(@"%d, %@", statusCode, description);
}
return 0;
}


ターゲット6 オプショナル型

これもSwift有利に見えますね。

でもboostを使ってください

#import <Foundation/Foundation.h>

#include <boost/optional.hpp>

namespace{
boost::optional<int> someFunction() {
auto value = rand() % 10;
if(value < 5) {
returnboost::none;
}
returnboost::make_optional(value);
}
}
int main(int argc, constchar * argv[]){
@autoreleasepool {
for(int i = 0 ; i < 10 ; ++i)
{
auto value = someFunction();
if(value)
{
NSLog(@"%d", *value);
}
else
{
NSLog(@"no value");
}
}
}
return0;
}


ターゲット7 Any

Objective-Cでは、何でも入る型はありませんでしたが、Swiftではなんでも入る型Anyが用意されました。

ですが、C++にだってあります。そう、boost::anyならね。

#import <Foundation/Foundation.h>

#include <iostream>
#include <vector>
#include <string>

#include <boost/any.hpp>
#include <boost/assign/std/vector.hpp>

int main(int argc, const char * argv[])
{
@autoreleasepool {
using namespace boost::assign;
std::vector<boost::any> anyObjects;
anyObjects += 1;
anyObjects += "C String";
anyObjects += @"Objective-C String";
anyObjects += std::string("std::string");
anyObjects += [NSDate date];

for(auto any : anyObjects)
{
if(any.type() == typeid(int))
{
NSLog(@"%d", boost::any_cast<int>(any));
}
else if(any.type() == typeid(const char *))
{
NSLog(@"%s", boost::any_cast<const char *>(any));
}
else if(any.type() == typeid(NSString *))
{
NSLog(@"%@", boost::any_cast<NSString *>(any));
}
else if(any.type() == typeid(std::string))
{
std::cout << boost::any_cast<std::string>(any) << std::endl;
}
else if(any.type() == typeid(NSDate *))
{
NSLog(@"%@", boost::any_cast<NSDate *>(any));
}
}
}
return 0;
}


ターゲット8 乱数

Objective-CやSwiftは乱数のいいモジュールがありませんが、C++にはあります。 (だんだんObjective-C関係なくなって来ましたが)

超絶雑なかんじで円周率を求めてみましょう

#import <Foundation/Foundation.h>

#include <iostream>
#include <random>

int main(int argc, const char * argv[])
{
@autoreleasepool {
std::mt19937 mt;
std::uniform_real_distribution<> gen(-1.0, 1.0);

int N = 100000000;
int inside = 0;
for(int i = 0 ; i < N ; ++i)
{
double x = gen(mt);
double y = gen(mt);
if(x * x + y * y < 1.0)
{
++inside;
}
}

double pi = 4.0 * (static_cast<double>(inside) /static_cast<double>(N));

NSLog(@"%f", pi);
}
return 0;
}


ターゲット9 CFオブジェクトとメモリ管理

Swiftではどうやら自動管理の方法があるようですが、C++にだってよく成熟したメモリ管理クラスがあります。

これでもうSwiftじゃなくたってCFRelease()に悩まされる事など無いでしょう!

#import <Foundation/Foundation.h>

#include <memory>

namespace {
template <typename T>
using cf_shared_ptr = std::shared_ptr<typename std::remove_pointer<T>::type>;
}

int main(int argc, const char * argv[])
{
cf_shared_ptr<CFMutableDataRef> data(CFDataCreateMutable(NULL, 0), CFRelease);
for(int i = 0 ; i < 26 ; ++i)
{
UInt8 c = 'A' + i;
CFDataAppendBytes(data.get(), &c, 1);
}

cf_shared_ptr<CFStringRef> text(CFStringCreateWithBytes(NULL,
CFDataGetBytePtr(data.get()),
CFDataGetLength(data.get()),
kCFStringEncodingUTF8,
false),
CFRelease);
NSLog(@"%@", (__bridge NSString *)text.get());
return 0;
}

とまあObjective-C++最強だよ、とでも言いたげに書いてみましたが、

あんまりカオスなコーディングしてると、

社会的に怒られる場合や、誰もメンテナンスできないコードが生産される場合も多々あるかと思うので、ほどほどにしましょう。

過ぎたるは及ばざるが如しですね。

でもたまには”はしか”にかかるのも良いかとおもいます。


じゃ、Swiftやるか。皆さんよいSwiftライフを!