2013年10月3日木曜日

Objective-C に Modules がやってきた!

Modulesの他、iOSの新しいトピックについても多数解説(NSProgressなど)。


注目は Modules。
Modulesは今までのinclude(Objcだとimport)を置き換える新しい仕組み。メリットとしては
・#includeの順序依存による問題を解消(定義順序によって挙動が変わる・名前衝突でエラーなど)
・ビルドシステム向けのメタ情報の埋め込み(公開APIの定義や、リンクリンクライブラリの指定など)

Xcode5ではデフォルトでモジュールが使えるようになっている。従来のプロジェクトでは明示的に有効にする必要あり。
設定:Apple LLVM 5.0 - Language - Modules


モジュールを使うには #import の代わりに @import を使う。
こんな感じ。
@import UIKit;
@import MapKit;
@import iAd;
iOS/OSXの場合、フレームワーク名を指定する感じになる。

モジュールにはサブモジュールを定義することができて、特定のサブモジュールだけ利用することもできる。
@import UIKit.UIView;

モジュールの特徴は本記事の最後の方にまとめてある。メリットとしてはコンパイル速度の向上などがあるが、元々Xcodeはプリコンパイルヘッダの仕組みがあるのでそこまで変わらないかもしれない。

最もメリットがありそうなのはビルド時にフレームワークの選択が不要になること。

モジュールはリンクすべきライブラリ情報を持っているので、Xcodeがそれを参考にして自動的にリンクしてくれる(Automatic Link)とのこと。

実際そうなのか試してみた。

Xcode5で新規にプロジェクトを立ち上げて MKMapViewをビューに配置してみた。
#import 
@import MapKit;

@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet MKMapView *mapView;

@end
コンパイルは通った。おお。

しかし実行するとエラーで落ちた。

XibにあるMKMapViewをインスタンス化(アンアーカイブ)できないとのこと。
MapKit.framworkを追加すると当然だが問題なく実行できる。

むむ。もしかしてXibにある(アーカイブされている)から問題なのか?
そこでXibにMKMapViewを置くのはやめ、コードでインスタンス生成してビューに貼り付けるようにしてみた。
- (void)viewDidLoad
{
    [super viewDidLoad];
    MKMapView* mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0, 0, 320, 400)];
    [self.view addSubview:mapView];
    self.mapView = mapView;
    
}
すると

できた。MapKit.frameworkは指定していない。

現時点ではXib上に配置したオブジェクトにはモジュールのAutomatic link は適用されないようだ。
この辺り今後Xcodeのバージョンアップで改善されるかもしれない。

なお紹介記事によれば内部的には#importはモジュールに変換されているようだ(なのでわざわざ#importを@importに書き換える必要は無い)。


------
以下は Modulesのとりとめないまとめ。

Modulesについては InfoQの日本語記事が詳しい(2012年提案時のニュース)。


上記の元ネタのスライド(AppleのDong Gregor氏)



スライドの中で現在のインクルードヘッダの問題点が2つ取り上げられている。
・Fragility ... 定義の順番で結果が変わる
・Performance ... 巨大で大量のヘッダとそのプリプロセッシングは遅い

これを解決する為に import キーワードを導入する(#のつかない import)。

Before
#include <stdio.h>

int main() {
 printf("Hello, world!\n");
}

After
import std;

int main() {
 printf("Hello, world!\n");
}
書き方が変わっただけでなくビルド時の解釈の仕方が従来のインクルードと大きく異り、ビルドシステムがimport情報を積極的に利用することで様々なメリットが生まれる。

Import Resilience
例えば従来のincludeだと名前空間がファイル毎に分離されていないのでシンボルが重複するとビルドが失敗する。
#define FILE "myfile.txt"
#include <stdio.h>

int main() {
 printf("Hello, world!\n");
}

一方、importの場合、他のモジュールとは独立しているのでこの問題は起きない。

#define FILE "myfile.txt"
import std;

int main() {
 printf("Hello, world!\n");
}

Selective Import
サブモジュールを定義しておけば、一部のモジュールだけ取り込むことができる。
import std.stdio;

int main() {
 printf("Hello, world!\n");
}

モジュールの書き方
こんな感じ。
// stdio.c
export std.stdio:

public:
typdef struct {
 ...
} FILE;
int printf(const char*, ...) {
 ...
}
:
・ソースファイル中にモジュール名を定義する(export ...)
・public:を使うことで外部への公開範囲を制御することができる(いままでのincludeはもちろんできない)。
・ヘッダファイル不要(!)

互換性
従来のヘッダをモジュールへ対応させる方法も提供されている。
module std {
    module stdlib { header "stdlib.h" }
    module math { header "math.h" }


Umbrella Headers
同じディレクトリ内のヘッダ全体をモジュールとして扱う方法。
module ClangAST {
    umbrella header "AST/AST.h"
    module * {}
}
ネーミングがCocoaのUmbrella Frameworksそれと同じ(Appleっぽい)。

パフォーマンス
・モジュールヘッダのパースは1回だけ(キャッシュされる) ※Objcのプリコンパイルヘッダ相当か
M x N が M + N のオーダーに。

Automatic Linking
モジュール内にリンクすべきライブラリ情報を記述できる。
module ClangAST {
    umbrella header "AST/AST.h"
    module * {}
    link "-lclangAST"
}
この情報をビルドシステムが使えば、開発者がいちいちリンクライブラリを明示的に指定する必要がなくなる。

0 件のコメント:

コメントを投稿