CentOS6 に Objective-C 環境を整える


ウェブにあるいろいろな情報を参考に、とりあえずコードを実行できる環境を整えたのでメモ。随時加筆訂正します。

本記事は主に gcc を用いた環境設定についてのメモだが、clang に関するメモも現時点でわかるだけ載せている。

## 実行環境について

コンパイラ単独ではなくランタイムパッケージ（フレームワーク）が合わせて必要になる。次のようなものがあるとのこと。

* GNUStep
* Cocotron
* Cocoa
* ObjFW

Linux 上で動かす際には GNUStep がおもに使われているようなので、本記事でもひとまずそれに倣う。

## コンパイラによる違い

現在 Objective-C をサポートするフリーのコンパイラとしては伝統的な gcc と新進の clang がある。Apple が ObjC の言語仕様を拡張するにあたって gcc をフォークするのではなく新しいコンパイラを開発することを決断した結果、新しい機能は clang 上に実装されることとなった。そのため @property などの記法やプリミティブなオブジェクトの省略記法（ @(42) や @[@"foo", @"bar"] 等）それに ARC 等は gcc には実装されていない（はず）。

しかし clang 自体は ARC をサポートしているとはいえ、バックエンドとなるランタイムも ARC をサポートしていないと有効化できない。どうやら GNUStep は ARC 未サポートのようで、`-fobjc-arc` オプションを付けてコンパイルを試みるとエラーとなる。

```
error: -fobjc-arc is not supported on platforms using the legacy runtime
```

そのうち他のランタイムでも試してみたい。

また Xcode 上とは違って @property を書くだけではダメなようで @synthesize を要求されるが書いてもなぜかエラーとなる。こちらもそのうち。

## 前提

*以下、ひとまず gcc 上で動かす場合の方法となる。*

コンパイラは gcc が昔から Objective-C をサポートしているので追加でライブラリを入れるだけでよい。

NSObject などを使うには GNUStep のライブラリを入れる必要がある。入れなくても gcc の機能だけで `Object` クラスを継承すればオブジェクト自体は作れるが、@"text" 等も使えないし現実的にはまったく役に立たないだろう。よってライブラリの導入は面倒だがほぼ必須となる。

そういえばスレッドセーフではないよ、みたいな configure メッセージ出ていた気がする。

## インストール

gnustep 関係も（一部？） yum にあるが、面倒でも手動で入れたほうが確実だ。

### 準備

ひとまず gcc-objc がないと始まらないので入れる。

	yum -y install gcc-objc

次の２つは configure のオプションで外せるようだが、面倒なので入れておくとよい。

	yum -y install gnutls gnutls-devel
	yum -y install libicu libicu-devel

### libffi

こちらも yum にあるにはあるが、バージョンミスマッチのエラーでまともに入らないので手動で入れる。

	wget ftp://sourceware.org/pub/libffi/libffi-3.0.9.tar.gz
	...
	./configure --prefix=/usr/local
	make
	make install

現時点での最新版 3.0.13 を入れると新しすぎるせいか後述の gnustep-base の configure でエラーになる（フィックスして報告すると喜ばれるかも）。

ヘッダファイルがヘンなところにインストールされるので注意。

### gnustep-make

さくっと。

	wget ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-make-2.6.4.tar.gz
	...
	./configure
	make install

### gnustep-base

ffi の場所を指定。いちおう `--disable-libffi` や `--disable-invocations` といったオプションもあるがおそらく libffi をきちんとリンクできないと使い物にならない気がする。

	wget ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-base-1.24.4.tar.gz
	...
	./configure \
		--with-ffi-include=/usr/local/lib/libffi-3.0.9/include \
		--with-ffi-library=/usr/local/lib64
	make
	sudo make install
	sudo ldconfig

gnustep-config がないよとか言われたら適当にごまかそう。

	sudo ln -s /usr/local/bin/gnustep-config /usr/bin/

### OSX

ちなみに OSX だと gnustep-base とか gnustep-make の port があるようだが、うちの環境ではエラーが出てインストールできない。

## サンプルファイル

完動するサンプルとして次の４ファイルを用意した。

### Makefile (gcc)

ここでは Sample.h と Sample.m を用意することにした。

```makefile:Makefile
CC        = gcc
CFLAGS    = -O2 -fconstant-string-class=NSConstantString
INCLUDES  = Sample.h
LIBS      = -lobjc -lgnustep-base
OBJS      = Sample.o
TARGET    = sample

$(TARGET): $(OBJS)
	$(CC) -o $@ main.m $(OBJS) $(LIBS)

%.o: %.m
	$(CC) $(CFLAGS) -c $*.m -o $*.o

clean:
	rm -f $(TARGET) $(OBJS)
```

### Makefile (clang)

参考までに。OBJCFLAGS の include path は Foundation.h が objc/objc.h を要求するので必要となる。

```makefile:Makefile
CC        = clang
CPP       = clang -E
CFLAGS    = -O2 -fconstant-string-class=NSConstantString -fobjc-arc
OBJCFLAGS = -I/usr/lib/gcc/x86_64-redhat-linux/4.4.7/include
INCLUDES  = Sample.h
LIBS      = -lobjc -lgnustep-base
OBJS      = Sample.o
TARGET    = sample

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) $(OBJCFLAGS) -o $@ main.m $(OBJS) $(LIBS)

%.o: %.m
	$(CC) $(CFLAGS) $(OBJCFLAGS) -c $*.m -o $*.o

clean:
	rm -f $(TARGET) $(OBJS)
```

### main.m

メインルーチンは Sample.m に押し込めなくもないが分けておくと見やすい。

```objectivec:main.m
#import <Foundation/Foundation.h>

#import "Sample.h"

int main(int argc, char *argv[]) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    Sample *sample = [[Sample alloc] init];
    [sample hello];
    [sample release];

    [pool drain];

    return 0;
}
```

### Sample.h

```objectivec:Sample.h
#import <Foundation/Foundation.h>

@interface Sample: NSObject {
    NSString *text;
}

- (void) hello;
- (void) callme: (NSNumber *) n;

@end
```

### Sample.m

```objectivec:Sample.m
#import <Foundation/Foundation.h>

#import "Sample.h"

@implementation Sample

- (id) init {
    self = [super init];
    if( self ){
        text = @"howdy";
    }
    return self;
}

- (void) hello {
    NSLog(@"hello world");

    NSMutableArray *array = [[NSMutableArray alloc] init];
    [array addObject: @"one"];
    [array addObject: @"two"];
    [array addObject: @"three"];

    NSLog(@"array: %@", array);

    [array release];

    NSNumber *n = [NSNumber numberWithInt: 42];
    [self performSelector: @selector(callme:) withObject: n afterDelay: 3.0f];

    [[NSRunLoop currentRunLoop] run];
}

- (void) callme: (NSNumber *) n {
    NSLog(@"%@ %@ !", text, n);
}

- (void) dealloc {
    [super dealloc];

    NSLog(@"destroyed");
}

@end
```

### 実行

	make
	./sample

### 結果

``` stdout
2013-07-24 19:29:42.038 sample[23942] hello world
2013-07-24 19:29:42.040 sample[23942] array: (one, two, three)
2013-07-24 19:29:45.043 sample[23942] howdy 42 !
2013-07-24 19:29:45.043 sample[23942] destroyed
```

## エラー対策

実行時にログに出るエラーについて。

### "No local time zone specified" 

環境変数 TZ を指定すればよい。

	export TZ=Asia/Tokyo

### "Unknown time zone name `JST'"

JST というファイルが存在しないから出る。たとえば次のようにする。

	cd /usr/local/lib/GNUstep/Libraries/gnustep-base/Versions/1.24/Resources/NSTimeZones/zones/
	sudo ln -s ./Japan ./JST

## まとめ

Apple 版ほどの機能はないが、Linux 上で Objective-C はわりと簡単に動かせる。