Haskellで解くのが楽な問題があってHaskellで書いたけど、その後iOSアプリから使いたくなったので調べてみた。
GHC iOS の導入
まず、GHCやcabal-installを最新にしておく。
brew install ghc cabal-install cabal update && cabal install cabal-install
~/.cabal/config に $ncups
という定義があったら消す必要があるらしいが、うちにはなかった。
git clone git@github.com:ghc-ios/ghc-ios-scripts.git ~/bin/ghc-ios-scripts echo -e "\nPATH=~/bin/ghc-ios-scripts:"'$PATH' >> ~/.profile PATH=~/bin/ghc-ios-scripts:$PATH
installGHCiOS.shを実行。クロスコンパイル用のGHCとかを入れてくれる。
installGHCiOS.sh
が、コケたので調べてみたら、curlがリダイレクト処理してなかったので修正。再度実行したらいけた。
diff --git a/installGHCiOS.sh b/installGHCiOS.sh index e13087e..780c326 100755 --- a/installGHCiOS.sh +++ b/installGHCiOS.sh @@ -13,7 +13,7 @@ fi echo "Downloading GHC for iOS devices..." -curl -O https://www.haskell.org/ghc/dist/7.8.3/ghc-7.8.3-arm-apple-ios.tar.xz +curl -OL https://www.haskell.org/ghc/dist/7.8.3/ghc-7.8.3-arm-apple-ios.tar.xz tar xvf ghc-7.8.3-arm-apple-ios.tar.xz && mv ghc-7.8.3 ghc-7.8.3-arm rm ghc-7.8.3-arm-apple-ios.tar.xz cd ghc-7.8.3-arm @@ -35,7 +35,7 @@ rm -r ghc-7.8.3-arm echo "Downloading GHC for the iOS simulator..." cd /tmp -curl -O https://www.haskell.org/ghc/dist/7.8.3/ghc-7.8.3-i386-apple-ios.tar.xz +curl -OL https://www.haskell.org/ghc/dist/7.8.3/ghc-7.8.3-i386-apple-ios.tar.xz tar xvf ghc-7.8.3-i386-apple-ios.tar.xz && mv ghc-7.8.3 ghc-7.8.3-i386 rm ghc-7.8.3-i386-apple-ios.tar.xz cd ghc-7.8.3-i386
iOSアプリプロジェクト作成
Xcodeで新規プロジェクトを作る。
CocoaPodsと協調利用できるか確認したかったからとりあえず適当に1つ入れておく。
platform :ios, '8.0' pod 'SDWebImage' use_frameworks!
HaskelliOS.xcconfigをダウンロードしてプロジェクトに追加。これとCocoaPodsのxcconfigをマージするxcconfigを作成して、向け先もCocoaPodsのものからこちらに向ける。
#include "Pods/Target Support Files/Pods/Pods.debug.xcconfig" #include "HaskelliOS.xcconfig"
#include "Pods/Target Support Files/Pods/Pods.release.xcconfig" #include "HaskelliOS.xcconfig"
HaskelliOS.xcconfigのARCHS = "$(ARCHS_STANDARD_32_BIT)"
のコメントアウトを外す(まだ64bitで動かない)。また、HEADER_SEARCH_PATHSも自分の環境に合わせて書き直す。うちの環境ではバージョンだけなおした。
ソースコードの配置
アプリのコードの中にHaskellというフォルダを作って、haskell.hsを追加。試しに以下のようなコードを書く。戻り値はIOでくるむ必要がある様子。
{-# LANGUAGE ForeignFunctionInterface #-} module HTestApp where import Foreign foreign export ccall x3Int :: Int -> IO Int x3Int :: Int -> IO Int x3Int x = return $ x * 3!
この状態で、ghc-ios haskell.hs
をビルドすると.aとか.hとか出てくるので、Haskellフォルダごとまるっとプロジェクトに追加。
Haskell.hsはターゲットに含めないようにTarget Membershipのチェックを外しておく。
HaskellのコードをiOSアプリビルド時にビルドされるようにする
次に、ビルドしたときにHaskellのコードがビルドされるようにする。ターゲットのBuild PhasesにRun Scriptsを追加して、Compile Sourcesの前に、以下のように追加する
cd $SRCROOT/App/Haskell PATH=$PATH:/usr/local/bin:$HOME/bin/ghc-ios-scripts/ ghc-ios haskell.hs
Haskell部分の初期化と解放
Haskell部分を呼ぶときはhs_initで初期化して、hs_exitで解放する必要がある。ブリッジオブジェクトを作り、こいつの初期化・解放とHaskellの状態が一致するようにした。
#import <Foundation/Foundation.h> @interface HaskellBridge : NSObject - (int)x3Int:(int)x; @end
#import "HaskellBridge.h" #import "HTestApp_stub.h" @implementation HaskellBridge - (instancetype)init { self = [super init]; if (self) { hs_init(NULL, NULL); } return self; } - (void)dealloc { hs_exit(); } - (int)x3Int:(int)x { return (int)x3Int(x); } @end
サンプルアプリの作成
あとはこういう画面を作って
こんな感じで使う。
#import "ViewController.h" #import "Haskell/HaskellBridge.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UITextField *textField; @property (nonatomic) HaskellBridge *bridge; @end @implementation ViewController -(void)viewDidLoad { [super viewDidLoad]; self.bridge = [[HaskellBridge alloc] init]; } - (IBAction)x3Pushed:(id)sender { self.textField.text = [NSString stringWithFormat:@"%d", [self.bridge x3Int:[self.textField.text integerValue]]]; } @end
文字列を渡す
文字列を渡すには、Haskell側でCStringを使って定義しておく。
foreign export ccall repeatString :: Int -> CString -> IO CString repeatString :: Int -> CString -> IO CString repeatString x cstr = do str <- peekCString cstr newCString . foldl (++) "" $ replicate x str
NSStringをcStringUsingEncodingでCの文字列ポインタをとってきてそれを渡せばOK。
- (NSString *)repeatString:(NSString *)str count:(int)count { const char *inCstr = [str cStringUsingEncoding:NSUTF8StringEncoding]; const char *outCstr = (const char *) repeatString(count, (HsPtr)inCstr); return [[NSString alloc] initWithCString:outCstr encoding:NSUTF8StringEncoding]; }
問題点
最初の方にも書いたけど、64bit未対応なので、現時点ではストアにリリースできなくなってしまっていること。