月別アーカイブ: 2012年5月

[iOS]Json形式のテキストを読み込んでNSDictionaryにする

このメモはSBJson frameworksに関するもの。
記述時点での正式リリースバージョン3.0.4
参考:http://stig.github.com/json-framework/api/3.0/
ARC適用する場合は3.1α以降でなければ動作しない。
http://ip7.biz/wordpress/?p=1008

導入手順

  1. ターミナルを立ち上げる
  2. gitから入手すべく以下を入力してENTER
    • git clone git://github.com/stig/json-framework.git
  3. DLしたファイルを解凍し、解凍したファイル下のClassフォルダ内のファイルを自分のプロジェクトフォルダにD&Dしてコピーする。
    • もしビルドが通らないのであれば、、Build Phases のCompile SourcesにSBJon関連全ファイル(.m,.h)を追加することでBuildを通す事が出来る。
    • この時のエラーメッセージ
      _OBJC_CLASS_$_SBJsonParser", referenced from:
  4. Json形式のファイルを扱うクラス(.m)内で以下を記述
    • #import 
  5. JSON形式のファイルを扱うクラスヘッダ内で以下を記述
    • @class SBJsonStreamParser;
    • @class SBJsonStreamParserAdapter;

テキストファイルから読み込むんでデータ化

テキストファイルから読み込む

        //read file
        NSString* fileName  = [NSString stringWithFormat:@"dummyData"];
        NSString* path      = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Documents"];
        path = [path stringByAppendingPathComponent:fileName];
        NSData* data = [NSData dataWithContentsOfFile:path];

読めてなかったら強制終了

if(data == nil) abort();

データを文字列にエンコード

        //Data encode to NSString.
        NSString* dataStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"data = %@",dataStr);

SBJsonでパースする

        //SBJson parse to NSDictionary.
        SBJsonParser* sbjsonparser =[[SBJsonParser alloc]init];
        NSError* error;
        error = nil;
        NSDictionary* dic = [sbjsonparser objectWithString:dataStr error:&error];
        NSLog(@"JSON dictionary=%@", [dic description]);

テスト

読み込んだテキストファイルの中身

{
"buttonArray":[
    {
      "word":"キウイ",
      "imgPath":"greenButtons_0000s_0003.png"
    },
    {
      "word":"うみがめ",
      "imgPath":"redButtons_0000s_0003.png"
    }
    ]
}

動作確認した際に出た文字列

CoreData[39477:fb03] JSON dictionary={
    buttonArray =     (
                {
            imgPath = "babylogButtons_0000s_0003.png";
            word = "U30adU30a6U30a4";
        },
                {
            imgPath = "babylogButtons_0000s_0003.png";
            word = "U3044U3061U3054";
        }
    );
}

JavaScript本格入門 ~モダンスタイルによる基礎からAjax・jQueryまで

[ObjC]ファイルの読み書きとNSHomeDirectory()と[[NSBundle mainBundle]resourcePath]

テキストファイルの読み込みでどうしようもないところでハマったので戒めを込めてちゃんとまとめておく。

やろうとしたこと

テキストファイルを読み込む
テキストファイルはコンパイル時には存在している
起動と同時に読み込んでそれを保持する。

ハマったところ

App内がファインダから参照できずにAppと同じディレクトリに置いてあるDocumentsフォルダが自前で用意したものと混同。
参照しても容易したファイルが存在せず、起動時も落ちる。
原因は[[NSBundle mainBundle]resourcePaht]で取得するべきパスをNSHomeDirectory()で取得していた事。
だって読み込み用のサンプルにはNSHomeDirectory()ベースで記述されてるんだもの…。

参考にしたソース

ボタンを押すとボタンのタグに応じて書込み/読み込みが実行される。

//ボタンクリック時に呼ぶ
- (IBAction)clickButton:(UIButton*)sender {
    if (sender.tag==0 {
        NSData* data=[self str2data:_textField.text];
        [self data2file:data fileName:@"test.txt"];
        NSLog(@"data = %@",data);
    } else if (sender.tag==1 {
        NSData* data=[self file2data:@"test.txt"];
        [_textField setText:[self data2str:data]];
        NSLog(@"data = %@",data);
    }
}


//バイト配列の書き込み
- (BOOL)data2file:(NSData*)data fileName:(NSString*)fileName {
    NSString* path=[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
    path=[path stringByAppendingPathComponent:fileName];
    NSLog(@"path : %@",path);
    return [data writeToFile:path atomically:YES];
}

//バイト配列の読み込み
- (NSData*)file2data:(NSString*)fileName {
    NSString* path=[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
    path=[path stringByAppendingPathComponent:fileName];
    NSLog(@"path : %@",path);
    return [NSData dataWithContentsOfFile:path];
}

コードの解説

-(IBAction)clickButton:(UIButton*)sender

ボタンが押された際にボタンに付属したタグに応じて処理を分岐させている。

- (BOOL)data2file:(NSData*)data fileName:(NSString*)fileName

分岐した先での処理。保存するパスをNSHomeDirectory()でルートを取得。
その後Documentsディレクトリ下を保存場所にして文字列化。
さらにファイル名を追加。ファイル名は引数で渡されている文字列を使用。
最後に保存時の結果を返す。

- (NSData*)file2data:(NSString*)fileName

書込み同様に読み込み先をDocuments下のディレクトリに指定。
ファイル名も指定。
最後に読み込んだデータを返す。

改変

データを書込みしてから読み込み、という流れであればNSHomeDirectory()ベースで良い。
なぜなら保存用ディレクトリであるDocumentsフォルダ内にファイルを置いているし、読み書きすべきところはそこだから。

やりたかったのは書き込みをする前からファイルが存在していて、それを読み込んでほしかった。
よって以下のように内容を修正

//バイト配列の読み込み
- (NSData*)file2data:(NSString*)fileName {
    NSString* path=[[[NSBundle mainBundle]resourcePath] stringByAppendingPathComponent:@"Documents"];
    path=[path stringByAppendingPathComponent:fileName];
    NSLog(@"path : %@",path);
    return [NSData dataWithContentsOfFile:path];
}

[[NSBundle mainBundle]resourcePath]をベースとしたディレクトリ内のファイルを読むように記述を変更。
App/Documents/data.txtというファイルが合った場合は

        NSData *data;
        NSString* fileName = [NSString stringWithFormat:@"data.txt"];
        NSString* path;
        path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Documents"];
        path = [path stringByAppendingPathComponent:fileName];
        data = [NSData dataWithContentsOfFile:path];

という記述で(NSData)data内にデータが格納される。

以降の処理は

        if(data == nil) abort();

これを挟んでから処理を記述した。
ファイルの読み込みに失敗するとその場でアプリが落ちるのでバグに気づく。

NSHomeDirectory()と[[NSBundle mainBundle] resourcePath]

前者のNSHomeDirectoryは.appが置いてあるディレクトリを指す関数で、アプリが自分で保存したファイル等を読み書きする際に使用する。
一方後者のディレクトリは.app内をRootとしたディレクトリで一つ階層が下になる。

プロジェクトファイルのTARGETSから対象を選択し、BuildPhaseから確認できるCopy Bundle Resourcesの項目が後者で取得したパスから見えるファイル群。
なのでここにあるファイルは前述のソースで言うところのNSHomeDirectory()を後者のメソッドに置き換える事で参照が可能になる。
NSHomeDirectory()
写真はiPhoneシミュレータの中のディレクトリ。
NSBundle (ry で指定された場所はAppより下になる。
NSHomeDirectory()で取得できるパスはAppやDocuments等がある一つ上の階層で直接ファインダーで確認できる。

参考

この本にファイル読み書きのサンプルが載っていたおかげで助かった。
iPhone/iPad/iPod touchプログラミングバイブル―iOS 5/Xcode 4対応 (smart phone programming bible)