Core Data から NSDictionary を取得・保存する

By | 2012年5月11日 Check

Core Data で Relationship をはって1:多の関係の際に NSSet でインスタンスをもたせてるだけなので、多の方に独自のデータを追加したい時があります。
記事テーブルとタグテーブルがあって、そのリレーションテーブルに priority をもたせるみたいな感じですね。





記事テーブルに Binary Data の Type をもった Attribute を作成し、そこに NSDictionary 等を保存しようといたったわけです。
つまりは NSCoding プロトコルに準拠しているインスタンスは全て保存できるわけです。

で、わざわざそれをやるため NSManagedObject に別のメソッドのはやすのも嫌だなと思ったんで NSManagedObject で定義されたプロパティを override して透過的に扱えるようにしました。

実装

Tag.h, Tag.m はそこまで必要ではなかったので省略。
ARC 下でのコードなので、非 ARC の場合はちゃんと retain と release しないとリークします。

Article.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
 
@interface Article : NSManagedObject
 
// Binary Data で作ったので本来なら NSData のところ NSDictionary に変更している
@property (nonatomic, retain) NSDictionary * priority;
 
@end

Article.m

#import "Article.h"
 
@implementation Article
 
@dynamic priority;
 
-(void)setPriority:(NSDictionary *)priority {
    [self willChangeValueForKey:@"setPriority"];
 
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:appsIndexDictionary];
    [self setPrimitiveValue:data forKey:@"priority"];
 
    [self didChangeValueForKey:@"setPriority"];
}
 
-(NSDictionary *) priority {
    NSData *data = [self primitiveValueForKey:@"priority"];
    if (!data) return nil;
 
    return [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
 
@end

実際に使うところ

NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:persistentStoreCoordinator];
 
// 透過的に NSDictionary を扱ってるようにみせるためなのでそれ以外のデータは割愛
 
Article *article = (Entity *)[NSEntityDescription insertNewObjectForEntityForName:@"Article"
                                                       inManagedObjectContext:context];
NSDictionary *priority = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:1], @"hoge", nil];
article.priority = priority;
[context save:nil];
 
 
// 取得して表示
 
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Article" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:1];
 
NSArray *array = [context executeFetchRequest:fetchRequest error:nil];
Article *fetchArticle = [array objectAtIndex:0];
 
NSLog(@"%@", fetchArticle.priority);
 
/*
{
    fuga = hoge;
}
*/

これの欠点は fetchRequest で指定する際は binary data なので NSDictionary のデータを使ってソートとかしようとするとプログラマブルソートをする必要があります。
まぁメソッドの中でやっちゃえばいいけど、priority でフィルターかけてページャーとかだとめんどくさいことになるのでケースバイケースで使い分ければ良いかなと。
これを共通化して使えるようにして、継承してつかえば簡単につかえて結構便利なのかなと。

他にもこうやってるとか、良い方法等あればつっこみよろしくです。
にしても Core Data って DBIC に似てる気がする

参考資料

Saving and Retrieving NSDictionary from Core Data

homebrew で install した AUTOJUMP を使えるようにするまで

By | 2012年3月2日 Check

zsh使いなら効率改善のため知っておきたいAUTOJUMP」をみて便利そうだったのでさっそく AUTOJUMP を使ってみることに。
使い方はリンク先をみてください

補完関数を自分で配置とあったのですが配置せずにいけました。

# brew install autojump

(※)brew –prefix が /usr/local 前提ではなします。

これでインストールはおしまい。

/usr/local/etc/autojump をよみこめばはれて autojump が使えます。
このへんはログにでてきますね。

.zshrc

BREW_PREFIX=`brew --prefix`
if [ -e $BREW_PREFIX/etc/autojump ]; then
    source $BREW_PREFIX/etc/autojump
fi

ただこのままだと fpath に/usr/local/share/zsh/functions が入っていない場合だと補完関数がきかないため不便です。
/usr/local/share/zsh/functions/_j に自動でシンボリックリンクがはられるため。

というわけで fpath を追加するか自分の好きなところにシンボリックリンクをはりましょう。

今後 homebrew でいれた zsh の拡張コマンドを使えるようにしたかったので今回は fpath を追加します。
fpath の変更は compinit を実行する前にしておかないと読み込まれませんので注意が必要になります。
地味にはまりました。

.zshrc

if [ -x /usr/local/bin/brew ]; then
    BREW_PREFIX=`brew --prefix`
    fpath=($BREW_PREFIX/share/zsh/functions(N) $BREW_PREFIX/share/zsh/site-functions(N) $fpath)
fi
autoload -U compinit; compinit -u

functions はバージョン依存がなく site-functions は active の zsh の site-fuctions をさしている感じです。

autojump 便利ですね。かなりいい
おしまい

追記

ログの出力先が XDG_DATA_HOME に依存しています。
昔 ports で shard-mime-info をいれていたので XDG_DATA_HOME を設定していたために permission denied がでて地味に悩みましたとさ

iOS, Android, perl間で AES暗号化を行った通信をする

By | Check

Andorid, iOS, サーバー(perl)間で暗号化して通信する必要があったのでまとめてみました。

処理の流れはこんな感じ

クライアント(iOS, Android) からの通信
         [Request]                            [Response]
        plain text                       plain text
           ↓                                   ↑
        cipher text                        cipher text
           ↓                                   ↑
        base64 text                      base64 text
           ↓                                   ↑
          WAN(POST)                       WAN
           ↓                                   ↑
        base64 text                      base64 text
           ↓                                   ↑
        cipher text                        cipher text
           ↓                                   ↑
        plain text                      plain text

鍵は事前に交換してるものとして話をすすめます。

結論からいうと iv さえあわせとけば特にハマリませんでした。
そういいつつここで大分はまったんですけどねw

とういわけで iv をそろえないといけないので今回は iOS で NULL(0×00 x 16 Byte) にあわせました。

それは「CCCrypt(Objective-C) で暗号化したデータを Crypt::OpenSSL::AES(perl) で復号化する」を参考にしただけで特に理由はありません。

本来なら C で Android, iOS 用のライブラリをつくっちゃったらよかったのだろうけどもと思いつつそこまでやってません。

あと GET で暗号化したものを通信する際は Base64 を URLSafe にして encode / decode してください。

perl

#!/usr/bin/env perl
 
use strict;
use warnings;
 
use Digest::MD5 (); 
use MIME::Base64 (); 
use Crypt::CBC (); 
 
sub main {
    my $key = 'e8ffc7e56311679f12b6fc91aa77a5eb';           #Digest::MD5::md5_hex(time); とか。
    my $cipher = Crypt::CBC->new(
        -key         => $key,
        -literal_key => 1,
        -cipher      => 'Crypt::OpenSSL::AES',
        -header      => 'none',
        -iv          => pack('C*', map {0x00} 1..16),
    );  
 
    ############## Request(decrypt) ##############
    my $base64_text = "BhSJd4mRRJo+fGzpxIOUNg==";
    my $cipher_text = MIME::Base64::decode_base64($base64_text);
    my $plain_text  = $cipher->decrypt($cipher_text);
    print "plain text for request : $plain_text\n";         # plain text for request : crypt text!!
 
    ############## Response(crypt) ##############
    $plain_text  = "I'm hungry!";
    $cipher_text = $cipher->encrypt($plain_text);
    $base64_text = MIME::Base64::encode_base64($cipher_text);
    print "base64 text for response : $base64_text\n";      # base64 text for response : 72XrlydqnUzVrDfDE7ncnQ==
 
    return 0;            
}                        
 
exit main() unless caller;

iOS

AES crypt category」と「Base64 category」をもってきて
import すれば下記の方法でいけます。

NSString *key = @"e8ffc7e56311679f12b6fc91aa77a5eb";
 
NSData *cipherData;
NSString *base64Text, *plainText;
 
//############## Request(crypt) ##############
plainText  = @"crypt text!!";
cipherData = [[plainText dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
base64Text = [cipherData base64Encoding];
NSLog(@"%@", base64Text);            //BhSJd4mRRJo+fGzpxIOUNg==
 
//############## Response(decrypt) ##############
base64Text = @"72XrlydqnUzVrDfDE7ncnQ==";
cipherData = [base64Text base64DecodedData];
plainText  = [[NSString alloc] initWithData:[cipherData AES256DecryptWithKey:key] encoding:NSUTF8StringEncoding];
NSLog(@"%@", plainText);            //I'm hungry

Android

AndroidでAESの暗号化する」を参考にしてつくりました。
違うのは鍵を外部からうけとれるようにしたのと、 iv を 0×00 の 16 Byte にそろえたくらいです。
Base64 で encode / decode しないといけないので Android SDK 2.2 以上だと動きます。
2.1 以下で使用する際は別途 Base64 のライブラリを用意してください。

エラーのハンドリングとかちゃんとしていないので参考程度にしてください。

AES256 encryption on Android

String key = "e8ffc7e56311679f12b6fc91aa77a5eb";
byte[] keyBytes = key.getBytes("UTF-8");
byte[] ivBytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
 
String plainText;
byte[] cipherData;
String base64Text;
 
//############## Request(crypt) ##############
plainText  = "crypt text!!";
cipherData = AES256Cipher.encrypt(ivBytes, keyBytes, plainText.getBytes("UTF-8"));
base64Text = Base64.encodeToString(cipherData, Base64.DEFAULT);
Log.d("encrypt", base64Text);   //BhSJd4mRRJo+fGzpxIOUNg==
 
//############## Response(decrypt) ##############
base64Text = "72XrlydqnUzVrDfDE7ncnQ==";
cipherData = AES256Cipher.decrypt(ivBytes, keyBytes, Base64.decode(base64Text.getBytes("UTF-8"), Base64.DEFAULT));
plainText = new String(cipherData, "UTF-8");
Log.d("dcrypt", plainText);         //I'm hungry

いじょう。

Jenkins を iOS に導入してみたメモ -Kiwi編-

By | 2012年1月29日 Check

どうも最低限1週間に1回更新しようと思いつつなかなかできていない僕です。
最近個人的に CI が便利で勝手にやってたりします。Jenkins はよくできていて個人的に気に入っています。
個人的に気になってるのが KIF, Kiwi, cedar あたりですね。
Kiwi cedar は BDD のテスティングフレームワークです。
情報もあって Jenkins で 簡単に導入できるのは GHUnit だと思います。ここみれば簡単に導入できます。
個人的には logic 周りのテストを Kiwi をつかって UI が絡んだテストに KIF を使おうかなと思ったりしてる今日この頃です。

前置きが長くなりましたが今回は Kiwi を使用して Jenkins で CI ができる状態までのお話です。
といってもリンク先をみれば解決するんですけどねw
そういうまとめがなかったので試したまとめって感じです。

project への導入はここをみればいけます。
Jenkins の設定はここの通りで大丈夫です。

上記の設定では SCM ポーリングで監視したりしてるんですけど git リポジトリも管理下にあるなら hooks/post-update で http リクエストを送ってジョブ登録するのがいいかと思います。

はまったのが “Buid Settings” => “Test Host” を消さないといけないのを忘れてテスト結果がちゃんと反映されず悩んでました。
そこで書かれている通り “UIWindow”“UIApplication” を使用している場合は動作しなくなるので気をつけてください。

あとほかに code coverage を出したりしようとするとここをみればいけると思います。
ここまでやると楽しいテストサイクルを送れるはずです。
こうやって視覚的にみれるのもそうだし自動ビルドできるのは便利ですね。

僕がやった環境のせいかわからないですけど gcov で code coverage をとろうとするとエラーでこけちゃってとれませんでした。。。。

fopen$UNIX2003 called from function llvm_gcda_start_file in image [project名].

/Develper/usr/lib/libprofile_rt.dylib を追加すれば大丈夫という記事をみかけたりしたのですがうまくいかず。。。
このあたりはもう少し調べてみようと思います。

参考資料

もう一つのBDDスタイルのiOSテスティングフレームワーク「Kiwi」の導入方法

Node.js で POST したコードを Closure Compiler で圧縮する簡易サーバーを書いた

By | 2011年10月13日 Check

webAPI で Node.js を使ったのは勉強がてらです。はい。完全に趣味ですね。
jar おとしてきて conpile すればいいやんといえばそれでおしまいですねw
コードは Github にあげてあります。

そもそも複数ファイルをまとめて圧縮したり、Web で毎回コピペしてやるのもめんどくなってきたのでちょりっと書いてみました。
コマンドラインから post して圧縮できたらいいなと思ってたのでそんな感じにした。

$node server.js
Server running at http://127.0.0.1:8000/

デフォルトだと localhost の 8000 でサーバーが起動します。
あとはそこにむかって POST するだけです。
そうすると圧縮された js が標準出力に表示されます。

$echo "(function() { alert('hoge'); })();" |  curl -d @- http://localhost:8000
alert("hoge");
$alias -g GCC='| curl -d @- http://localhost:8000 '

みたいなエイリアスを定義しておくと下記みたいに使えるので便利ですね

$echo "(function() { alert('hoge'); })();" GCC -o compress.js
$cat hoge.js fuga.js GCC -o compress.js

実装に少しふれておくと server のコードは 40 行くらい。
Google Closure Compiler API のライブラリは ここのを使いました。
npm でもライブラリが3つくらいあったので、とりあえず一番最初に見つけたのを使ったかんじ。
あと Node.js をそこまで理解して書いたわけでもないので、突っ込みどころあればよろしくです

var host    = '127.0.0.1',
    port    = 8000,
    http    = require('http'),
    closure = require('./lib/closure');
 
http.createServer(function (request, response) {
    var code;
    request.setEncoding('utf-8');
 
    request.on('data', function(chunk) {
        if (chunk) code = chunk;
    });
 
    request.on('end', function() {
        try {
            console.log('[CODE]: ' + code);
            if (!code) throw new Error('not found post data');
            closure.compile(code, function(err, _code) {
                if (err) throw err;
 
                var rate = Math.round((1 - (_code.length / code.length)) * 100);
                console.log("[COMPILED] compiled (%d% smaller)\n" + code, rate);
                response.writeHead(200, { 'Content-Type' : 'text/plain' });
                response.end(_code);
            });
        }
        catch (err) {
            console.error('Faild to Compile: ' + err.toString());
            response.writeHead(500, { 'Content-Type' : 'text/plain' });
            response.end('Faild to Compile: ' + err.toString());
        }
    });
}).listen(port, host);
 
console.log("Server running at http://" + host + ':' + port + "/");

WordPress Themes