2012/05/02

ソケット通信で画像を送るときのエンコードまわりについて : Objective-C

Objective-CとRubyのソケットサーバーを使ってチャットアプリを作る際にエンコード周りでハマったのでメモ。

まずは大前提として

1.ソケット通信でテキストメッセージと画像をやりとりする

2.メッセージ送信の主体を表すキー、実行コマンドのキーを付与して、id:cmd:msg というString型の文字列データで通信する
という感じで実装しています。実際にはid/cmd/msgのそれぞれにString型のデータが入っていて、それをコロン「:」でつなげた文字列を使ってサーバーとやりとりする、ということです。

今回はサーバー側、クライアント側で受け取ったデータの内容によって処理を分岐させるために、データをsplitして簡単にキーを抽出できると嬉しいな、ということでString型を採用します。で、問題になったのが、画像の送信。今回は画像データをどうやってソケット通信で送るのか、というところについてです。

例えば

ユーザーAが画像を送信する場合は 「userA:sendimg:画像のString型データ」という文字列をソケットサーバーに送ります。

サーバー側では文字列を解析してUserAが所属しているグループのメンバーに対して受信したものと同じ文字列をブロードキャスト(配信)します。

また、コマンドがsendimgの場合はサーバーに画像を保存する、など含まれるコマンドによってサーバー側でいくつか実行される処理が変わる、という感じ。

テキストデータはそのまま文字列にキーとコロンを付与して送信すればOKだけど、画像については一旦バイナリに変換してからString型に変換する必要がある。なので、画像をNSString型まで持って行って、他のキーと統合した文字列を生成して通信することにする。

画像データにおいては下記のような感じで型変換をしています。
UIImage→NSData(バイナリ)→String→ ソケット通信 →String→NSData→UIImage
NSData→String/String→NSData の部分がMIMEエンコードです。
基本はEメールのエンコードに利用されているものですね。このあたりはもうちょっと勉強しないと、と思っています。


ここからは1つずつ追っていきます。

まずUIImageデータをNSData型に変換

これは簡単。メソッド一発。

NSData* nsData = [[NSData alloc] initWithData:UIImagePNGRepresentation( image )];

逆は下記。あとで受信したデータを戻すときはこっちを使います。
UIImage* image = [[UIImage alloc] initWithData:nsData];

NSDataをString型に変換

さて、メソッドを探してみよう、、ん?Objective-Cにはそんな便利なものは用意されていない!とのこと。。。なので、クラスを自分で定義する必要があります。
下記のサイトで紹介されているソースコードをそのままクラスとして定義すれば動きます。感謝!

[Objective-C] base64エンコード、デコードを実装する & 64進数

というわけでBase64クラスを定義します。


MIMEエンコード/デコード用のクラスを定義

ヘッダーファイル:Base64.h
ここのソースコードをそのままコピー

実装ファイル:Base64.m
ここのソースコードをそのままコピー
※ファイル冒頭のインポートするファイル名は適宜ヘッダーファイル名に変えてください。


使い方も丁寧に載せていただいてますのでリンク貼っておきます。
[Objective-C] base64エンコード、デコードを実装する & 64進数(使い方)

ちなみにObjective-Cはネットの情報が間違っていたり、Xcodeのバージョンによって動かなかったり、ARCを利用しているかどうかで扱いが変わったり、などなど、、色々苦労します。。

でもこのソースコードはそのまま動くので、そのまま書けばOK。
クラスを定義したら実際にメソッドを使ってみましょう。

エンコードしてみる

NSString *msg = [nsData base64String];

これだけでOK。無事にNSString型に変換されました。
そして逆向きの場合は下記です。
NSData *nsData = [NSData dataWithBase64String:msg];

というわけで画像データのUIImage→NSData→NSStringまでの変換が完了しました。

エンコードした文字列を使って通信に使う文字列を生成

あとはキーとなる文字列と統合してソケットに送るメッセージ文字列を生成します。

NSString* key = @"userA";
NSString* cmd = @"sendimg";
NSString* str = [NSString* stringWithFormat:@"%@:%@:%@", uid, cmd, msg];
生成したstr文字列をサーバーに送ると色々処理してくれる、という感じにします。具体的にはコロンでSplit(分割)してcmdを参照して場合分けして、該当処理のメソッドを実行して、、という感じですね。

ソケット通信クライアント側の実装

参考までに、ソケット通信のクライアント側は次のような感じで書いてます。(ちなみにサーバー側はRubyです。)
- (BOOL)socketConnector:(NSString*)uid cmd:(NSString*)type data:(NSData*)msg  {
    NSString *normalBase64String = [msg base64String]; //image | NSData -> NSString
    NSString* msgToSocket = [NSString stringWithFormat:@"%@:%@:%@\n", uid, type, normalBase64String];
    NSUInteger bufferCount = sizeof(int) * ([msgToSocket length] + 1);
    const char *cMsgToSocket = malloc(bufferCount);

    if ([msgToSocket getCString:cMsgToSocket
                      maxLength:bufferCount
                       encoding:NSUTF8StringEncoding]) {
    }
    
    CFSocketContext CTX;
    CTX.version = 0;
    CTX.info = (__bridge void*)self;
    CTX.retain = NULL;
    CTX.release = NULL;
    CTX.copyDescription = NULL;
    
    CFSocketRef client;
    client = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketDataCallBack, (CFSocketCallBack)DataCallBack, &CTX);
    
    CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, client, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes);
    CFRelease(sourceRef);
    
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_len = sizeof(addr);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(ポート);
    addr.sin_addr.s_addr = inet_addr("IPアドレス");
    
    NSData *address = [NSData dataWithBytes:&addr length:sizeof(addr)];
    CFSocketError error = CFSocketConnectToAddress(client, (__bridge CFDataRef)address, 2);
    
    if (!error) {
        NSData *sendData = [NSData dataWithBytes:cMsgToSocket length:strlen(cMsgToSocket)];
        CFSocketSendData(client, (__bridge CFDataRef)address, (__bridge CFDataRef)sendData, 10);
        free(cMsgToSocket);
        return YES;
    } else {
        [self showAlert:@"Error002" text:@"Socket Connection2 error"];
        return NO;
    }
}

コールバック関数の実装

また、上記メソッド内でコールバック関数のDataCallBackを起動してます。この部分はC言語で記述します。

static void DataCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void* data, void* info) {
    NSString* socketRcvMessage = [NSString stringWithCString:CFDataGetBytePtr((CFDataRef)data) encoding:NSUTF8StringEncoding];
    [(__bridge chatwindowViewController*)info rcvJudge:socketRcvMessage];
    return;
}
データを受信するとコールバック関数 DataCallBack が呼ばれて受信した文字列をObjective-Cのメソッドに渡します。ちなみにこのようにC言語とObjective-Cとの間で値の受け渡しをする際にはbridgeをしてあげて渡す必要があります。このあたりもハマったのですが、長くなりそうなので、今回はデータの変換に話を留めます。

あとは受信した文字列を解析して、画像だったら画像の表示メソッドを呼ぶ、テキストだったら、、といった感じで処理をすればOKです。

参考サイト

余談

日々いろんなサイトを参考にさせていただいていますが、難しいサイトが多いんですよね、、内容が難しいのもそうだけど、端折られてる部分が初心者にはわからなくて、理解ができない、ということが多かったです。。分かってる人が見たら脳内補完が働くのでそれでもわかるのだと思いますが、、。今みたら「あぁそういうことだったのか、、そう言ってくれれば、、」と感じることも多いです。とはいえ、全部書くわけに行かないので、レベルを想定して書くしかないのですが、、

とりあえず僕個人としては初心者の辛い気持ちが痛いほどわかったので、このブログでは「んなこたぁわかってるよ!」と突っ込まれるくらい丁寧にブログを書いていこう、なんてことを思いましたとさ。。

2012/04/02

インターフェースビルダーを使わずに画面を生成:Objective-C

画面が表示されるまでの内部的な動きを理解するために、インターフェースビルダーを使わずにベタ書きで画面を生成してみる。

画面を描画するためには大きく分けて次の処理が必要になる。
1.ウィンドウの生成と初期化 2.ビューの生成と初期化 3.ウィンドウにビューを追加して表示
全体像としては、ウィンドウは基本1つだけ存在し、その上に複数のビューとコンポーネントが乗る、というイメージ。 ウィンドウの生成やビューの呼び出しはAppDelegateファイル内に記述する。実際のビューの中身についてはビューを描画するクラスを作って別途定義する。 AppDelegateファイルの全体像は下記。
#import "StudyAppDelegate.h"
#import "HelloWorld.h"

@implementation StudyAppDelegate

@synthesize window = _window;

- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];  //ウィンドウの生成、初期化
    _window.backgroundColor = [UIColor whiteColor];  //背景色の指定
    [_window makeKeyAndVisible];  //ウィンドウの表示

    CGRect bounds = [[UIScreen mainScreen] bounds];  //スクリーンサイズの取得
    bounds.origin.y += 20;  //ビューの配置場所を指定
    bounds.size.height -= 20;  //ビューのサイズを指定
    UIView* view = [[[HelloWorld alloc] initWithFrame:bounds] autorelease];  //ビューを生成
    [_window addSubview:view];  //ビューの表示
    
    return YES;
}

- (void)dealloc {
    [_window release];
    [super dealloc];
}

@end
1行ずつの処理の内容を羅列すると、
・起動直後に呼ばれるメソッドで下記を処理 - ウィンドウの生成、初期化 - 背景色の指定 - ウィンドウの表示 - スクリーンサイズの取得 - ビューの配置場所を指定 - ビューのサイズを指定 - ビューを生成 - ビューの表示 - メソッドの返り値としてYESを返す。 ・メモリ開放のメソッドを書く。
1行ずつ見ていく。 まずは土台となるウィンドウを作るところから。

ウィンドウの生成と初期化

_window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
まずはUIWindowクラスのallocメソッドでメモリを確保して、initWithFrameメソッドで初期化。初期化時に画面サイズを指定したいので、UIScreenクラスのmainScreenメソッドでメインスクリーンのオブジェクトを取得し、boundsプロパティで画面サイズを取得する。今回はウィンドウサイズ=スクリーンサイズとする。ここまででウィンドウの生成と初期化が完了。

ウィンドウ背景色を指定する

_window.backgroundColor = [UIColor whiteColor];
インスタンス変数_windowのbackgroundColorプロパティに[UIColor whiteColor]を代入すると背景色が指定できる。 この時点でウィンドウの準備が完了したので、

ウィンドウを表示する

[_window makeKeyAndVisible];
つぎはウィンドウの上に乗せるビューの生成と初期化。 ビューの配置する場所や大きさを事前に指定するところから。

改めてウィンドウのサイズを取得しておく

CGRect bounds = [[UIScreen mainScreen] bounds];
CGRect型の変数boundsを用意しつつ、再度[[UIScreen mainScreen] bounds]でメインスクリーンのサイズを改めて取得。(ウィンドウ生成する前の時点で変数を用意して格納しておけば2回記述する必要ないな、、) ※サイズ指定(CGRect CGPoint CGSize) - iPhoneアプリ開発の虎の巻 : http://goo.gl/rJR6H

ビューの配置場所を指定する

bounds.oringin.y += 20;
左上の頂点を始点としてピクセル単位で指定する。メニューバーの下から表示するように値に20を足す。

ビューの縦の大きさを指定する

bounds.size.height -= 20;
ビューを配置する位置を20ピクセル下に移動したので、ビューの全体の縦の大きさは20ピクセル小さくする。(そうしないと画面からはみ出してしまうので。)

ビューの生成と初期化

UIView* view = [[[HelloWorld alloc] initWithFrame:bounds] autorelease];
HelloWorldクラスのインスタンスを生成して、配置場所と大きさを格納したbounds変数を引数として初期化する。 HelloWorldクラスはビューの詳細について書かれているのかな?という感じがする。実際に見てみると、、
#import "HelloWorld.h"

@implementation HelloWorld

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor purpleColor];
    }
    return self;
}

- (void)drawRect:(CGRect)rect {
    UIFont* font = [UIFont systemFontOfSize:24];
    [@"Hello, World!" drawAtPoint:CGPointMake(0,0) withFont:font];
}
@end
こんな感じ。詳しく見ていく。

initWithFrame:メソッドをオーバーライド

- (id)initWithFrame:(CGRect)frame {
}
HelloWorldクラスはUIViewクラスを継承しているので、initWithFrame:メソッドをオーバーライドすることによって初期化処理ができるようになる。 次に、初期化処理の具体的な内容。

親クラスの同名メソッドを読んでオブジェクトを生成してプロパティを指定

self = [super initWithFrame:frame];
if (self) {
    self.backgroundColor = [UIColor purpleColor];
}
return self;
ビューを作ったら何か表示しましょう、ということで描画メソッドを定義する。 HelloWorldクラスはUIViewクラスを継承しているので、drawRect:メソッドをオーバーライドして描画処理を実行できる。

drawRect:メソッドをオーバーライド

- (void)drawRect:(CGRect)rect {
}
このdrawRect:メソッドはビューの再描画が必要なタイミングで自動的に呼び出される。

文字列を描画してみる

UIFont* font = [UIFont systemFontOfSize:24];
[@"Hello, World!" drawAtPoint:CGPointMake(0,0) withFont:font];
1行目でフォントサイズの指定用にUIFont型のfont変数にフォントサイズを格納し、2行目でNSStringクラスのdrawAtPoint:withFontメソッドで文字列を描画する。 ちなみにsystemFontOfSizeメソッドはUIFontのクラスメソッド。また、drawAtPointの引数はCGpoint型で引き渡す必要があるため、CGpointMake関数を使ってCGPointを生成している。 HElloWorld.mが確認できたのでAppDelegate.mファイルの続きに戻る。

ビューを表示する

[_window addSubview:view];
addSubviewメソッドでウィンドウにビューを追加する。 ここまでが画面描画までの一連のプロセス。

2012/03/27

いろいろ用語集

tmux

サーバー側でターミナルを起動して、そこに対してリモートから接続するイメージ。
もし本番環境で作業中にネットワークが切れても大丈夫。
スクリーンの縦分割がデフォルトでできるというのが使いやすいところ。

※tmux : http://goo.gl/DlKn

クロスサイトリクエストフォージェリ対策

昔mixiで話題になったぼくはまちちゃん、がそう。

1. 攻撃者が、攻撃用の Web ページを作成して WWW 上に公開する。(WWW上ででなくとも、未対策のHTMLメーラーにウェブページをHTMLメールとして送信するだけでもよい。)
2. 第三者が、攻撃用の Web ページにアクセスする。
3. 第三者は、攻撃者が用意した任意の HTTP リクエストを送信させられる。

4. 送信させられた HTTP リクエストによって、攻撃者の意図した操作が行われる。 (ここで「第三者」とは、被攻撃サイトに意図せずアクセスさせられると言う意味で用いている。)

防ぐ方法としては一連の流れを紐付けるようにセッション情報を紐付ける、という方法がある。

Railsではライブラリとして提供されているものがあるので、それを組み込む方法で対応したほうが自前で作成するよりも信頼性が高い。

クロスサイトリクエストフォージェリ - Wikipedia : http://goo.gl/bFhn

カピストラーノ

クライアントからSSHでサーバーにリモートアクセスしてシェルを実行するどいう動作をRubyをつかって自動化できるツール。
元々はRailsのデプロイの緩和のために作られたが、他にも色々と使える。

カピストラーノタスクはRubyで記述する。また、カピストラーノはcapfileから指示を読む。capfileはただのテキストファイル。

capコマンドで実行できる。また、役割を与えることでサーバーごとに処理を変えることもできる。

※カピストラーノ(Capistrano)を使ってみよう!(Basic編) : LR Labs : 株式会社ライブレボリューション アリエスユニット : http://goo.gl/SHjeF

chef-solo

chefもあるが、chefサーバーを立てないといけないので、chef-soloが比較的お手軽な感じ。
Rubyでレシピを書ける、というのがいいらしい。

※chef-soloで作業環境構築の自動化 | ひげろぐ : http://goo.gl/MfE3g

IRC【Internet Relay Chat】

基本的にはチャットシステム。Skypeなどとは違い、ID認証を必要とせずサーバー情報をIRCクライアントに指定するだけで接続することができる。また、基本は多人数でのテキストチャットがメイン機能。
IRCのサーバー側はLinuxベースでの構築が必要。
会社内や学校内のサーバーにIRCを構築してクローズドのチャットシステムを作ることができる。

※IRCとSkypeの違いってなんですか? - Yahoo!知恵袋 : http://goo.gl/IJ94V

継続的インテグレーション (Continuous Integration, CI)

継続的インテグレーションとは、ソフトウェアの品質改善・納期短縮のためのソフトウェア・エンジニアリングの習慣の集合である。その原則は、開発の連続的な全行程が終わってから品質管理を行うという古い慣行をやめ、成果物の諸小部分に対して頻繁に品質管理を行うことである。

※継続的インテグレーションとは - はてなキーワード : http://goo.gl/sY1yw

Jenkins

CIツール。
ソースコードの統合、テストの自動化、メトリクスの自動取得、ビルド完了時の画面を変えたりなどの愉快な機能もw
詳細は下記。

※「Hudson」改め「Jenkins」で始めるCI(継続的インテグレーション)入門 (1/4) - @IT : http://goo.gl/6F4d7

リファレンス

「変数を参照するもの」の意味。参照したい変数のアドレスのことで、C言語ではポインタと呼ばれたりする。
ただしC言語のポインタと同じに考えてはいけない。微妙に別のものである。
このリファレンスを通して、参照している変数を呼び出すことをデリファレンスという事も合わせて覚えておきたい。

※リファレンスとは - はてなキーワード : http://goo.gl/Dr8vb

ガングリア

下記のdstatと同様、サーバーリソースをグラフ化してくれるツール

dstat

サーバーリソースをグラフ化してくれるツール。
他にもいくつかツールが有るようだが、色々と統合して表示してくれる、ということで便利らしい。

※dstatの万能感がハンパない - (ひ)メモ : http://goo.gl/ETRui

iostat -dkxではIOPSやバイト単位の値など、I/Oに関する詳細は確認できますが、CPU使用率など他の統計値は同時に見ることができません。
vmstatはメモリの状況やブロック数単位のI/O状況は見られますが、バイト単位のI/O状況やネットワークの送信、受信バイト数を見ることはできません。
ターミナルを複数上げてvmstatとiostatをそれぞれ実行してもいいのですが、できれば一つのターミナルでいろいろな値を見たい。というわけで、dstatの出番です。
□MTGでの会話メモ IOスタットとVMスタットを合わせた感じ
1秒ごとにモニタリングしてくれる。
最初はガングリアでもいいが、ガングリアは15秒とか30秒ごとに見る感じ。
脳内マージしなくても良くなるのは嬉しい。
方針としてはディスクIOをなくすようにしていくことが目標。

インスタンス変数の定義はプロパティ宣言が自動生成してくれる:Objective-C

いろんなサンプルプログラムを見ているとクラスを定義する際のインスタンス変数の定義部分で色々な定義のされ方があって混乱したので、調べてみた。

そもそもは

インスタンス変数はクラス定義の.hファイル内の@interfaceから始まるコンパイラディレクティブ内の{}の中に記述する、とういうのが基本でした。が、Objective-C2.0からコンパイラがclang(詳細についてはエントリ末尾の参考サイト参照)になって賢くなった、ということらしく、プロパティで変数が宣言されていれば自動生成させるだけでOK、ということになったそうです。

なので、これまではインスタンス変数を宣言しつつ、プロパティでももう一回インスタンス変数についてのプロパティ宣言をしていたのですが、{}内の記述は消しちゃってもいい、といよりもむしろ{}ごと消しちゃいなさい、ということみたいです。具体的には下記みたいな感じです。

これまでは

@interface hogehoge : NSObject
{
    NSString *str1 ;  //インスタンス変数str1を定義
    NSString *str2;   //インスタンス変数str2を定義
}

@property (nonatomic) NSString *str1;  //str1のプロパティ宣言
@property (nonatomic) NSString *str2;  //str2のプロパティ宣言
@end

Objective-C2.0以降は

@interface hogehoge : NSObject
@property (nonatomic) NSString *str1;  //str1のプロパティ宣言
@property (nonatomic) NSString *str2;  //str2のプロパティ宣言
@end
すっきり!

また、上にも書きましたが、基本的にプロパティ宣言で済んでしまった場合は{}も消しましょう、ということみたいです。 iOS Developer Libraryの「Objective-C プログラミング言語」ファイルのP.35にも下記のように書いてあります。
歴史的には、インターフェイスにはクラスのインスタンス変数宣言が必要でした。(中略)インスタンス変数は実装詳細であり、通常、クラス自身の外からアクセスされることはありません。さらに、実装ブロック内に宣言すること、あるいは宣言済みプロパティから自動生成させることも可能です。したがって通常は、インスタンス変数宣言をパブリックインターフェイスで行うべきではないので、波括弧も省略してください。
プロパティ宣言が必要ないインスタンス変数を定義したい場合にはヘッダファイル(.hファイル)に書くのではなくて、実装ファイル(.mファイル)内の@implementation内に{}を追加してその中に記述するのが今風(?)みたいですw


参考サイト

イマドキっ子の Objective-C | cockscomb.info
Objective-C入門その7:アクセサメソッドをマスターする|サルにもできるiPhoneアプリの作り方
Clang - Wikipedia

2012/03/26

ラッパークラスについて:Objective-C

ラッパークラスってなに?

まず前提として、int型等の基本的な変数はオブジェクト化されていません。ただし、Objective-Cで用意されている基本的なクラス群の中には引数を必ずオブジェクト型で渡さないといけないメソッドがたくさん定義されています。

そこで、int型などの数値をオブジェクトとして扱う必要があるため、その時のためにラッパークラスが用意されています。
つまり、ラッパークラスは数値などをオブジェクト化するためのクラス、ということになります。

ラッパークラスNSNumbrerでラップしてみる

まずは
NSNumber *wrappedInt = [NSNumber numberWithInt:123]; //インスタンス生成メソッドを利用してオブジェクト生成
もしくは
NSNumber *wrappedInt = [[NSNumber alloc] initWithInt]];  //イニシャライザを利用してオブジェクト生成
でInt型のラッパークラスのインスタンスを生成する。

※上記の2つにはメモリ管理の視点から見ると違いがあるらしいので、後々勉強が必要。

ラップされた値を取り出す

NSNumberオブジェクトからラップされている値を取り出すときにはいろんな型で取り出すことができる。 たとえば、 [wrappedInt intValue] とか [wrappedInt doubleValue] とか [wrappedInt stringValue] とかで取り出せる。

文字列の生成について:Objective-C

文字列の生成方法について、文字コードの指定や、複数の文字列を統合したい場合などパターン別にまとめ。

1.文字列を直接指定して生成する方法

NSString *str = @"hogehoge"; これは単純でわかりやすいですね。

2.文字列と文字コードを指定して文字列を生成する方法

オブジェクトを生成してからイニシャライザで指定する方法と、クラスメソッドを利用する方法があります。
結論から言うと、クラスメソッドを利用したほうがシンプル、かつ色々と便利です。

オブジェクトを生成してからイニシャライザで指定すると、
NSString *str = [[NSString alloc] initWithCString: "hogehoge" encoding:NSUTF8StringEncoding];
こんな感じ。

クラスメソッドstringWithCString を利用した場合、下記のようになります。

NSString *str = [NSString stringWithCString:"hogehoge" encoding:NSUTF8StringEncoding];

stringWithCString:encoding: メソッドはallocメソッドによるメモリ割り当てからインスタンスの初期化まで全部やってくれるみたいです。
また、ラッパークラスのところでも書きましたが、イニシャライズを利用する場合とクラスメソッドを利用する場合で、メモリ管理の手続きが若干変わるようです。そのあたりについてはまた別に機会にエントリしたいと思います。

3.複数の文字列オブジェクトを組み合わせて文字列を生成する場合

実際にはこの形もよく使いますね。
NSString *str = [NSString stringWithFormat: @"%@ と%@", str1, str2];
1.の方法で下記のように書くとエラーになります。サボってついついやりがち。(私だけ?)
NSString *str = @"%@ と%@", str1, str2;  //これはダメ
ちゃんとstringWithFormatメソッドを使って生成しましょう。

メソッドの定義と実行:Objective-C

Objective-Cの特徴としてメソッドの定義方法と呼び出し方法がある。 メソッドの持つ引数の数によっても形が変わってくるので慣れが必要。戻り値の有無、引数の数によっていくつか例を挙げながら確認してみる。

※下記コード内の test は各クラスのインスタンスを表す変数

1.戻り値も引数もないメソッドの場合

- (void)hogeMethod;  //メソッドを定義
{
  //処理
}

[test hogeMethod];  //メソッド呼び出し

2.戻り値があって引数がないメソッドの場合

- (NSString *)hogeMethod;  //戻り値がNSString型のメソッドを定義
{
  //処理
}

NSString *result = [test hogeMethod];

3.引数が1つあって戻り値がないメソッドの場合

- (void)hogeMethod:(NSString *)argString;  //引数がNSString型のメソッドを定義
{
  //処理
}

[test hogeMethod:@"hogehoge"];  //hogehoged文字列を引数としてメソッド呼び出し

4.引数が1つと戻り値があるメソッドの場合

- (NSString *)myMethod:(NSString *)argString;  //戻り値がNSString型、引数がNSString型のメソッドを定義
{
   //処理
}

NSString *result = [test myMethod:@"hogehoge"];

5.引数が2つと戻り値があるメソッドの場合

 - (NSString *)myMethod:(NSString *)argString   //戻り値がNSString型、引数がNSString型とint型の2つあるメソッドを定義
                           myInt:(int)argInt;
{
  //処理
}

NSString *result = [test myMethod:@"hogehoge" myInt:123]; //メソッド呼び出し
myMethod、myIntはメッセージキーワードと呼ばれ、メソッド名はメッセージキーワードをつないだものになる。 つまり、このメソッドのメソッド名は「myMethod:myInt:」となる。

もっとわかりやすくすると、、

たとえば、
- (void)setWidth:(int)argWidth
              height:(int)argHeight
               depth:(int)argDepth;
{
  //処理
}
というメソッドの場合、メソッド呼び出しは下記のようになる。
[test setWidth:5 height:6 depth:7]
この記述方法だと各引数の意味がメッセージキーワードを見ればわかるので便利。

※一般的には下記のように書くがちょっとわかりにくい。
public void setSize(int w, int h, int d)  //メソッド定義
{
  //処理
}

test.setSize(5, 6, 7);  //メソッド呼び出し

というわけで

Objective-Cでメソッドを定義するときにはメソッドを呼び出した時の記述のされ方をイメージしておくとコードが非常にわかりやすくなる、というメリットがある。

おまけ

メソッド定義の最初の「-」はインスタンスメソッドの定義の場合に付加する。 先頭の記号が「+」の場合はクラスメソッドの定義となる。 例えば、クラスの初期化手続きで利用するallocメソッドやinitメソッドなどが代表的なクラスメソッド。

2012/03/08

sort_byメソッドの定義をひとつひとつ理解してみる

sortよりもsort_byの方が処理が軽いらしい。なんで?と思ったので、yield、ブロックの勉強も兼ねて調べてみた。

sort_byメソッドの定義を確認

class Array
  def sort_by
    self.collect { |i| [yield(i), i] }.
      sort {|a,b| a[0] <=> b[0] }.
      collect! {|i| i[1]}
  end
end
ほっほう。何やら複雑そうだ。yieldも入っていてなんだかわかりにくいので、処理の流れに沿って1つ1つ見ていく。

まずはsort_byメソッドが呼ばれるところから

sort_byメソッドは下記のような感じで呼ばれる。
ary = [1,2,3]
ary.sort_by do |num|
  num
end
もちろん下記でも同じ。
ary = [1,2,3]
ary.sort_by { |num| num }
この時にプログラムの気持ちになると、、
「実行しようとしたらsort_byメソッドが呼ばれたから呼び出してみよう!」
と思ってsort_byメソッドの定義を参照。
class Array
  def sort_by
    self.collect { |i| [yield(i), i] }.
      sort {|a,b| a[0] <=> b[0] }.
      collect! {|i| i[1]}
  end
end
「あれ?sort_byメソッドの中にyieldって書いてある!何したらいいかわからんからもう一回呼び出し元のブロックの中身を見に行かなきゃ!」

で、もう一回sort_byの呼び出し元を見てブロック要素を発見
do |num|
  num
end
yieldを実行すると、呼び出し時に与えられたこのブロックが実行されることになる。
イメージとしては下記のような感じに呼び出されたブロック内が実行される。
def yield(num)
  num
end
この場合は単純に引数をそのまま返すだけの処理。
これでようやくsort_byメソッド内でやるべきことがわかった。

ここからはメソッド内部の話。

最初から順番に見ていく。
self.collect { |i| [yield(i), i] }
まず、self.collectとあるので、メソッドのレシーバであるary配列に対してcollectすることがわかる。
ary配列の各要素を取り出し、[yield(i), i]の左側の部分で先程定義されたyield関数が実行され、右側のiにはそのまま配列から取り出した要素が渡される。
それをary配列分繰り返すと下記のような配列が生成される。
[[3, 3],[2, 2][1, 1]]
そしてこの配列に対して次の処理がそのまま実行される。つまり、
[[3, 3],[2, 2][1, 1]].sort{ |a, b| a[0] <=> b[0] }
となる。
sortメソッドがする処理を日本語で書くと、
「配列の中の各要素の内、左側の数字を2つ(たとえば、[3, 3]の左側の3と[2, 2]の左側の2)を持ってきて <=>で比較して並び替える。この処理をすべての要素の並び順が整うまで実行する」となる。

結果は
[[1, 1],[2, 2],[3, 3]]
となる。
これをさらに
[[1, 1],[2, 2],[3, 3]].collect! { |i| i[1] }
今度は各要素の右側の要素を抜き出して並べる処理。結果は
[1, 2, 3]
こうなる。完成!

で、結局なんでsortよりもsort_byのほうが処理が軽いのか

並び替えの操作自体はそれほど重い処理ではないので、大きな差は生まれない。ただ、並び替えをする前に処理の重い文字列操作などが入ると大きな差になる、ということ。

たとえば、暗号化をしてから並び替えを実施する場合などには、sortの場合には比較する度に暗号化の操作が必要になるため、最大でn^2(nは比較の要素数)の処理が発生することになる。
これに比べて、sort_byの場合は最大で要素数n回の処理で済む。

サンプルで示したような単純な並び替えであればそれほど処理速度に差は生まれないが、後から操作を追加したりすることも想定されるので、できるだけsort_byを使うようにしておくほうがよい、ということになる。

sort_by、よく考えられていますね!

参考URL

collect と map の違いってなんだ?

結論:調べてみたけどちょっとわからない、、

派閥抗争??

Enumerable#collect を主に使う派閥と Enumerable#map を主に使う派閥があり (これらのメソッドは効能および用法用量が同じ)、互いに血で血を洗う派閥抗争に明け暮れている
ん?結局同じもんだって理解でいいのかな。

Perlにはmapしかないらしい。ちなみにDan Kogaiさんはmap派。
404 Blog Not Found:mapとはおれのことかとcollectいい

だったらcollectはなんかタイプミスしそうだからmapつかおっと。

おまけ

ついでに見つけたmapの動きについて解説してるページ
map/collect - もち

キューとスタックについて

配列に対する処理について

配列の先頭・末尾要素を操作するメソッドまとめ

先頭要素に対する操作末尾要素に対する操作
要素を追加するunshiftpush
要素を取り出すshiftpop
要素を参照するfirstlast

キュー処理

キューはFIFO(First-in First-out:列の最後に要素を追加し、列の先頭(最初に列に追加したもの)を取り出す処理)なので、下記で実現できる。
ary = %w(a b c d e f)
p ary.push("g")
p ary.shift
p ary
実行すると
>> $ ruby que.rb
["a", "b", "c", "d", "e", "f", "g"]
"a"
["b", "c", "d", "e", "f", "g"]

スタック処理

スタックはFILO(First-in Last-out:列の最後に要素を追加し、列の最後を取り出す処理)なので、下記で実現できる。
ary = %w(a b c d e f)
p ary.push("g")
p ary.pop
p ary
実行すると
>> $ ruby stack.rb
["a", "b", "c", "d", "e", "f", "g"]
"g"
["a", "b", "c", "d", "e", "f"]

2012/03/06

print / puts / p の出力と返り値について

こんがらがるのでメモ。

それぞれで出力結果と返り値が異なる

それぞれ単体で動かしてみる。

print
>> print "test"
test=> nil    # testが出力され、返り値はnil
puts
>> puts "test"
test    # testが出力されて改行される
=> nil    # 返り値はnul
p
>> p "test"
"test"    # テキストオブジェクトが出力される
=> "test"    # 返り値にもテキストオブジェクトが入る
ここからわかるのは
・printとputsは改行がある程度で大きく変わらない。(putsは文字列の最後で改行)
・pは返り値を持ちつつ、オブジェクトが属するクラス情報も保持している。


出力と返り値の関係を理解するためにいろんなパターンで動きを見てみる。

print puts "test"

puts が test を出力して改行。putsの返り値 nil が print に引き渡され、print の出力は何もなし、返り値はnil

実行結果
>> print puts "test"
test
=> nil

print p "test"

pが"test"を出力して改行。pの返り値"test"がprintに引き渡され、printがtestを出力し、返り値はnil

実行結果
>> print p "test"
"test"
test=> nil

puts p "test"

pが"test"を出力して改行。pの返り値"test"がputsに引き渡され、putsがtestを出力して改行。返り値はnil

実行結果
>> puts p "test"
"test"
test
=> nil

puts print "test"

printがtestを出力。printの返り値nilがputsに引き渡され、putsの出力は何もなしで改行。返り値はnil

実行結果
>> puts print "test"
test
=> nil

p print "test"

printがtestを出力。printの返り値nilがpに引き渡され、pはnilを出力して改行。返り値はnil

実行結果
>> p print "test"
testnil
=> nil

p puts "test"

putsがtestを出力して改行。putsの返り値nilがpに引き渡され、pはnilを出力して改行。返り値はnil

実行結果
>> p puts "test"
test
nil
=> nil

2012/03/05

timesメソッドをeachメソッドで書き換えてみる

timesメソッドにもブロックローカル変数がある、ということを知って へー と思ったのでメモ。 自分の頭の整理のためにtimesメソッドをeachメソッドで書き換えてみる。

timesメソッドは簡潔

簡単なコードにしておく。
flower = "Rose"

5.times do
  puts flower
end
実行すると
$ ruby times_test.rb
0Rose
1Rose
2Rose
3Rose
4Rose
こうなる。問題なし。

timesメソッドでもブロックローカル変数を定義できる

flower = "Rose"

5.times do |i|
  print flower,i,"\n"
end
実行すると
$ ruby times_test.rb
Rose0
Rose1
Rose2
Rose3
Rose4
ブロックローカル変数iに数字が順番に代入されていることがわかる。

あえてeachメソッドで書き換えてみる

flower = "Rose"
ary = [0, 1, 2, 3, 4]

ary.each do |i|
  print flower,i,"\n"
end
こんな感じになる。

こうやって見てみるとeachメソッドもtimeメソッドも結局根本は同じで、timesメソッドはeachメソッドの実行を数字配列に限定したもの、と理解できる。
また、数字配列に限定することによって、終了の数字をtimesの前に書くだけで実行してくれるようになっている、ということ。

case文を勘違いしていたのでメモ

case文ってifとかwhileとかと同じようなもんでしょ!と思ってたけど、ちゃんと調べたらなんか色々勘違いしていたのでメモ。

case文をif文で書き換えてみる

下記は普通のcase文。
ary配列の各要素の所属クラスによって条件分岐させている。

case version
ary = [1, "hoge", nil]

ary.each do |item|
  case item
  when String
    print item," is string\n"
  when Numeric
    print item," is numeric\n"
  else
    print item," is something\n"
  end
end

実行結果
$ ruby case_test.rb
1 is numeric
hoge is string
 is something
※3つ目の変数はnilなので、何も表示されない。

まずは素直にif文にしてみる

if version1
ary = [1, "hoge", nil]

ary.each do |item|
  if item == String
    print item," is string\n"
  elsif item == Numeric
    print item," is numeric\n"
  else
    print item," is something\n"
  end
end
ん?これでクラスの情報にアクセスしてくれるのかな?とりあえず実行してみる。
$ ruby case_test.rb
1 is something
hoge is something
 is something
あぁ。やっぱりだめ。「==」は値の等号の演算子だしね、、(「==」は類似のメソッドも判断できるらしい)全部somethingになってしまった。

こういう時は「===」という演算子を使うらしい。

調べてみると、、
「===」は左辺が数値や文字列の場合は「==」と同じ意味を持つが、左辺が正規表現の場合は「=~」と同じようにマッチ判定をする。また、左辺がクラスの場合は右辺がそのクラスのインスタンスかどうかを判定する。
なるほど。ということは下記もダメ。一応試してみる。

if version2
ary = [1, "hoge", nil]

ary.each do |item|
  if item === String                  # クラス名が右辺になっている
    print item," is string\n"
  elsif item === Numeric          # クラス名が右辺になっている
    print item," is numeric\n"
  else
    print item," is something\n"
  end
end
実行してみる。
$ ruby case_test.rb
1 is something
hoge is something
 is something
やっぱりだめだった。

左辺にクラス名を移動

ary = [1, "hoge", nil]

ary.each do |item|
  if String === item                    # クラス名を左辺にした
    print item," is string\n"
  elsif Numeric === item            # クラス名を左辺にした
    print item," is numeric\n"
  else
    print item," is something\n"
  end
end
実行してみる。
$ ruby case_test.rb
1 is numeric
hoge is string
 is something
やっといけた。
case文の挙動をちゃんと理解できていなかった。。一つ一つ根本を理解していく癖をつけたい。


2012/03/04

Ruby1.9におけるローカル変数とブロック変数の関係

Ruby1.9からブロック内の変数のスコープが変わったようなのでメモ。


1.9ではブロック変数はブロックローカル変数として扱われる

つまり、ブロック内のパラメータとして定義された変数のスコープはブロック内に限定される、ということ。これをブロックローカル変数という。
試しに下記のコードを1.8.7、1.9.3それぞれのバージョンで実行してみる。

block_test.rb
x = 1
ary = [1,2,3]

ary.each do |x|
  puts x
end

p x

1.8.7での実行結果
$ ruby block_test.rb
1
2
3
3
1.9.3での実行結果
$ ruby block_test.rb
1
2
3
1
上記のように、1.8.7では最初に定義したx=1がイテレータ実行による影響を受けて x=3 になっているが、1.9.3では x=1 が保持されている。


ブロックパラメータで指定していない変数はブロックローカルにはならない

ブロック内でパラメータとして定義されずに利用された変数はブロックローカルな変数ではないため、ローカル変数として扱われる。

例えば、
x = 1
ary = [1,2,3]

ary.each do |y|
  x = y
end

p x
とすると結果は x=3 になる。念のため。
また、上記のようなケースで x もブロックローカルにするためには下記のように記述する。
x = 1
ary = [1,2,3]

ary.each do |y; x|
  x = y
end

p x
上記の実行結果は x=1 になる。


<以下、2012/03/05追記>

もうちょっと複雑にしてみると、
x = y = z =0         # 変数初期化
ary = [1, 2, 3]

ary.each do |x; y|   # 配列の要素はxに順番に代入される。yはブロック内のみをスコープとしたブロックローカル変数
  y = x              # ブロック変数xに配列の要素が順番に追加され、ブロックローカル変数yに代入される
  z = x              # ブロック変数xに配列の要素が順番に追加され、ローカル変数zに代入される
  p [x, y, z]
end

p [x, y, z]

実行結果は下記のようになる。
[1, 1, 1]
[2, 2, 2]
[3, 3, 3]
[0, 0, 3]

ブロックローカル変数として定義をしなかったzのみ影響を受けていることがわかる。



参考サイト

Ruby 1.9に移行する際に注意すべき10のポイント - なんとなく日記


2012/03/01

githubを使い始めるために最低限わかっておくべきこと

チームでgithubを使う事になったので、勉強中。
githubを理解する上で、なぜGitが生まれたのか、なぜSVNじゃだめなのか、を簡単に調べつつ、基本的な操作手順メモを書いておく。

なぜGitが生まれたのか

SVNとGitの比較から考えてみる。
調べてみたらこんな説明があった。

SVN は中央のサーバー上で実行され、変更を SVN のデータ・リポジトリーに追加し、また変更ごとにスナップショットを提供することができます。このスナップショットにはリビジョン番号が付けられています。リビジョン番号は SVN にとって、そして SVN を使用する人にとって、非常に重要です。私が変更した後で他の人が変更した場合には、その人のリビジョン番号の方が必ず大きいことが保証されています。
Git の目標も SVN に似ており、変更を追跡することですが、Git には中央のサーバーはありません。この違いは重要です。SVN は集中型ですが、Git は分散型です。そのため、Git では変更ごとに増加するリビジョン番号というものはありません。「最新のリビジョン」というものはないからです。Git にも一意のリビジョン ID がありますが、これらのリビジョン ID 自体は SVN のリビジョン番号ほど有用なものではありません。
Git の場合、重要なアクションはもはやコミットではありません。マージです。誰でもリポジトリーを複製することができ、そのクローンにコミットすることができます。リポジトリーの所有者は、変更をマージして元の 1 つのリポジトリーに戻すことができます。あるいは、開発者が変更をプッシュしてリポジトリーに戻すこともできます。
※Subversion ユーザーのための Git: 第 1 回 Git 入門 : http://goo.gl/U5I0Dより

つまり、SVNはリビジョン管理をメインとした集中管理型で、Gitはリポジトリーをみんなで複製(クローン)して変更をマージして一つのリポジトリに戻すための分散型システム。Gitのすごいところは分散して開発しても変更がちゃんとマージされるようになっている、という点。

いくつか理解すべきキーワードがあるが、全体像を確認しながら見ていったほうが理解しやすいので、全体像から。今回は簡単な説明、ということで、remote側ではbranchは切らずに常にmasterにpushする。(最後まで読めば意味わかるので、いまはわからなくてOK)

用語説明

local:ここでは自分のPC内のgithubのこと
remote:ここではチームメンバーで共有しているオンライン上のgithubのこと
repository:プロジェクトとほぼ同義。リポジトリを作成してその中にソースコードを配置する。実際にはローカルのgithub内とリモートのgithub内で同じリポジトリを作成して同期する。
branch:masterから分岐した枝で、新機能を開発する際には必ずbranchを切ってmasterに影響しない状態で実装する。
master:各branchの大元となる幹のようなもの。最終的にはmasterに全てのソースコードを取り込んで本番環境にリリースする。
merge:branchをmasterを取り込むこと。また、branchを切る前には必ず手元のソースコードを最新にすべきで、他人が開発したソースコードを取り込むこともmergeという。
commit:実装したソースコードファイルをbranchに取り込むこと。
    * masterに直接commitも可能だが、開発ログが残らないので、必ずbranchを切ってからcommitする。
push:ローカルのgitからリモートのgitに反映すること
pull:リモートのgitからローカルのgitに取り込むこと

たとえば、functionAという機能を実装してremoteのリポジトリに公開するまでの手順

実装したソースコードを共有するまでの手順を時系列で書くと、下記のようになる。
1.ローカルでfunctionAを実装
2.「$ git checkout -b functionA」 → branch を作成し、checkoutする(そのbranchに移動)
3.「$ git add ファイル名」→ 実装したファイルをgit管理下に置く
    * 「$ git add . 」だと全てのファイルが対象になる。事前に除外するファイルを設定しておく必要がある。基本的には「. 」はつけずにファイル名を指定する。
4.「$ git commit -m "functionA"」→ functionAbranchにコミットする
5.「$ git checkout master」→ masterに移動する。
    * masterにコミットするためにはmaster側からcommit コマンドを打つ必要があるため。
6.「$ git merge functionA」→ functionAをmaster にコミット
7.「$ git push origin master」→ remote のmaster に差分をpush

図解するとこんな感じ。番号は上の説明に対応(ちょっとずれてるので修正中)



参考URL

[導入メモ] GitHubを使うために、SVNユーザーがGitを調べた ::ハブろぐ : http://goo.gl/giYg6
Git ユーザマニュアル (バージョン 1.5.3 以降用) : http://goo.gl/5BJbd
transitive.info - Git 使い方 見出し一覧 : http://goo.gl/TRei4




Railsで簡易掲示板を作ってみる。studylog

http://www.evernote.com/shard/s13/sh/7dbb790f-c7a0-4660-a1db-38d26338730f/06d65c6896653294fc6886afe3ca371c

2012/03/01 20:29
・現時点ではCSSは未対応

「RilasによるアジャイルWebアプリケーション開発 第4版」studylog


http://goo.gl/RJCwi

・赤文字が疑問点、青文字は解決策や理解した部分。
・コードが画像になっているので、今後は埋め込み型に切り替える必要がある。
・現時点ではログイン機能などの部分は未着手。
・全体的につぶやきベースなのであしからずw

今勉強している内容メモ


□今勉強している内容
Rails
Ruby
オブジェクト指向
HTML/CSS
githubの使い方
vimの使い方

□これから勉強する内容
HTML5
JS
Fireworks
Hadoop
Objective-C
Java

2012/02/29

技術を勉強するということ

エンジニアリング、すなわち技術とは、「自然法と社会的必要性という制限の中でテクノロジーを創り出す」ことである。 by Wikipedia

今最も社会に貢献できる領域はITの領域であり、エンジニアとは社会にとって最も価値のある存在である。

だから僕らが技術を勉強して世の中に有用なサービスを提供することはもっとも大きな社会貢献につながる。

今日も未来の社会、人類をよりよくするためにただひたすらに。