Cライブラリを使ってiPhoneからTwitter APIを叩いてみる

libcurlをiPhoneアプリからつかう方法 (ついでにopensslも) | tech.kayac.com - KAYAC engineers' blog
を読んで、面白そうだなーと思い Cの世界でTwitter OAuthを叩くところまでやってみようと思った。調べたらOAuth用のCライブラリもあるようで。
liboauth download | SourceForge.net

とりあえずシミュレータで動かせるかどうかやってみる

結局openssl+libcurlは必要なようなのでまずはそこから。

$ curl -O "http://www.openssl.org/source/openssl-1.0.0a.tar.gz"
$ tar zxvf openssl-1.0.0a.tar.gz
$ cd openssl-1.0.0a
$ CC=/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/gcc ./config --prefix=${PWD}/../libraries
$ perl -i~ -pe 's!^CFLAG=!$&-isysroot /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.0.sdk!' Makefile
$ make
$ make install
$ cd ..
$ ls libraries/lib
engines     libcrypto.a libssl.a    pkgconfig

opensslできた。次libcurl。

$ curl -O "http://curl.haxx.se/download/curl-7.21.1.tar.gz"
$ tar zxvf curl-7.21.1.tar.gz
$ cd curl-7.21.1
$ ./configure --prefix=${PWD}/../libraries --disable-shared --with-ssl CFLAGS="-arch i386 -isysroot /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.0.sdk -I${PWD}/../libraries/include -L${PWD}/../libraries/lib" CC=/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/gcc
$ grep "SSL support" config.log
  SSL support:     enabled (OpenSSL)
$ make
$ make install
$ cd ..
$ ls libraries/lib
engines     libcrypto.a libcurl.a   libcurl.la  libssl.a    pkgconfig

できた。そして最後にliboauth。

$ curl -O "http://liboauth.sourceforge.net/pool/liboauth-0.8.8.tar.gz"
$ tar zxvf liboauth-0.8.8.tar.gz
$ cd liboauth-0.8.8
$ PKG_CONFIG_PATH=${PWD}/../libraries/lib/pkgconfig ./configure --prefix=${PWD}/../libraries --disable-shared CFLAGS="-arch i386 -isysroot /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.0.sdk" CC=/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/gcc
$ make
$ make install
$ cd ..
$ ls libraries/lib
engines     libcrypto.a libcurl.a   libcurl.la  liboauth.a  liboauth.la libssl.a    pkgconfig

全部入った…ぽい。
Xcodeで新しいプロジェクトを作成、上記のインストールディレクトリの libraries/lib 以下の"libcrypto.a", "libcurl.a", "liboauth.a", "libssl.a"をそれぞれプロジェクトに追加。プロジェクトの設定でヘッダ検索パスに libraries/include を指定、 あとzlibへの依存もあるということなので"Other Linker Flags"に"-lz"を追加。
とりあえず****AppDelegate.mに以下のように書いてみた。手抜きにつきconsumer_keyとかは全部直書き。
liboauthの使い方に関しては下記の記事を参考にさせていただきました。
C/C++でTwitterのXAuth認証を行う - 休日奮闘記

#import "HogeAppDelegate.h"
#import <oauth.h>

@implementation HogeAppDelegate

@synthesize window;


#pragma mark -
#pragma mark Application lifecycle

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    
    // Override point for customization after application launch.
    char *consumer_key        = "**********************";
    char *consumer_secret     = "****************************************";
    char *access_token        = "**************************************************";
    char *access_token_secret = "******************************************";
    char *req_url = oauth_sign_url2(
        "http://api.twitter.com/statuses/mentions.json",
        NULL, OA_HMAC, "GET",
        consumer_key, consumer_secret, access_token, access_token_secret
    );
    NSLog(@"result: %s", oauth_http_get(req_url, NULL));
	
    [window makeKeyAndVisible];
	
	return YES;
}

...

実行してみるとDebug Consoleにmentionsのjson文字列が出力された! ちゃんと認証されたらしい。
xAuthを使ってaccess_token, access_token_secretを取得するのも試してみた。

    char *consumer_key    = "**********************";
    char *consumer_secret = "****************************************";
    int argc = 4;
    char **argv = (char **)malloc(sizeof(char *) * argc);
    argv[0] = strdup("https://api.twitter.com/oauth/access_token");
    argv[1] = strdup("x_auth_username=sugyan");
    argv[2] = strdup("x_auth_password=********");
    argv[3] = strdup("x_auth_mode=client_auth");
    char *req_url = oauth_sign_array2(
        &argc, &argv, NULL, OA_HMAC, NULL,
        consumer_key, consumer_secret, NULL, NULL
    );
    for (int i = 0; i < argc; i++) {
        free(argv[i]);
    }
    free(argv);
    NSLog(@"result: %s", oauth_http_post(req_url, NULL));

…が、

result: (null)

となってしまい、どうもうまく取れず。oauth_http_**** はエラーハンドリングもできないようで微妙である。libcurlをつかって

#import <curl/curl.h>

...

    char *consumer_key    = "**********************";
    char *consumer_secret = "****************************************";
    int argc = 4;
    char **argv = (char **)malloc(sizeof(char *) * argc);
    argv[0] = strdup("https://api.twitter.com/oauth/access_token");
    argv[1] = strdup("x_auth_username=sugyan");
    argv[2] = strdup("x_auth_password=********");
    argv[3] = strdup("x_auth_mode=client_auth");
    char *req_url = oauth_sign_array2(
        &argc, &argv, NULL, OA_HMAC, NULL,
        consumer_key, consumer_secret, NULL, NULL
    );
    for (int i = 0; i < argc; i++) {
        free(argv[i]);
    }
    free(argv);

    CURL *curl = curl_easy_init();
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, req_url);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        CURLcode result = curl_easy_perform(curl);
        if (result == CURLE_OK) {
            NSLog(@"success!");
        } else {
            NSLog(@"error: %s", curl_easy_strerror(result));
        }
        curl_easy_cleanup(curl);
    }

とやってみたら無事にaccess_token, access_token_secretが取れた。liboauthのoauth_http_****でsslがつかえてないっぽい。。ビルドの設定が何か足りてないのかな…?

iPhone実機で

opensslは最新1.0.0aは上記のやり方だとごっそりエラーが出てきてしまって手に負えなそうだったので0.9.8oでごまかすことにした。

$ curl -O "http://www.openssl.org/source/openssl-0.9.8o.tar.gz"
$ tar zxvf openssl-0.9.8o.tar.gz
$ cd openssl-0.9.8o
$ CC=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc ./config --prefix=${PWD}/../libraries
$ perl -i~ -pe 's!^CFLAG=!$&-isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.0.sdk!' Makefile
$ perl -i~ -pe 's!-arch i386!-arch armv6!' Makefile
$ perl -i~ -pe 's!sig_atomic_t intr_signal!int intr_signal!' crypto/ui/ui_openssl.c
$ make
$ make install

libcurl, liboauthはconfigureオプションに少々追加する程度で大丈夫そう。

$ cd curl-7.21.1
$ ./configure --prefix=${PWD}/../libraries --host=arm-apple-darwin --with-random=/dev/urandom --disable-shared --with-ssl CFLAGS="-arch armv6 -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.0.sdk -I${PWD}/../libraries/include -L${PWD}/../libraries/lib" CC=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc
$ make
$ make install
$ cd liboauth-0.8.8
$ PKG_CONFIG_PATH=${PWD}/../libraries/lib/pkgconfig ./configure --prefix=${PWD}/../libraries --host=arm-apple-darwin --disable-shared CFLAGS="-arch armv6 -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.0.sdk" CC=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc
$ make
$ make install

これでちゃんと実機でも動いた。

感想

まぁCだけでもライブラリ使って動かすことができるらしい、というのは分かった。けどそれほどメリットは感じなかったので、Twitter APIをちょっと叩く程度の用途なら素直にObjective-C用のライブラリを使うのがいいのではないかなー、と。明らかにパフォーマンスに差がある、とかなら考えるけど…
libcurlでのhttpアクセスはNSURLConnectionのdelegate methodたちを実装する手間を考えるとラクかも知れないけど、4.0以降ならGrand Central Dispatch (GCD)で非同期処理させてNSURLConnectionでは sendSynchronousRequest:returningResponse:error: を使って一気にデータを取得するようにするとそれほど面倒でもない気がする。
とりあえずアプリケーション作成時には、後でどうにでも出来るようにOAuthのリクエストURLを作る部分とHTTPアクセスする部分のインタフェースをちゃんと用意しておいて裏で差し替え可能なように設計しておくといいのかな。