markdown

google bloggerってなんでmarkdown形式で入力できないんだろう。 いろんな表現を使いたいのに、全てマウス操作で入力しないと行けないなんて無理すぎる。

ってことで、markdown形式で書けないか調査

結論

結局google bloggerではまだサポートされていないので、 Mouを使って書く

専用エディターを入手

手っ取り早い&いまもこれを書いているのが、Mou http://mouapp.com/

レビュー領域があるのと、markdownのヘルプが乗っている

本当はvimも使いたいですよね

これは後で調べる

最後に

markdown記表を覚える必要があるよね。

gitのマージツール(p4merge)

gitのマージツールにGUIのp4mergeを設定する

ダウンロード

http://www.perforce.com/downloads/complete_list
こちらから対象のファイルをダウンロード
(macの方は大体こちらかな : Mac OS X 10.6 for x86_64)

インストール

p4mergeのみApplicationにインストール

設定

.gitconfig

[merge]
        tool = p4merge
[mergetool]
        keepBackup = false
[mergetool "p4merge"]
        path = p4merge
        keepTemporaries = false
        trustExitCode = false

Pathの追加


$ cat /usr/local/bin/p4merge 
#!/bin/sh
P4MERGE=/Applications/p4merge.app/Contents/MacOS/p4merge
${P4MERGE} $*

起動

マージ

基本的にコンフリクトした場合場合しか活躍はしませんが、以下のコマンドで実行
git mergetool

diff

もちろんdiffも見れます
git difftool

daemontoolsのログ表示

daemontoolsのログは一瞬???ってなりますよね。

$ tail /var/log/foo/current

@4000000050880600165e7a84 [5633] hogehoge
@40000000508806043a77d794 [5633] pogepoge
@40000000508806043a7a0dfc [5633] fugefuge

みたいな。
@400000...は「TAI64N形式」らしく、tai64nlocal コマンドを使えば正しく表示できます。

tail /var/log/foo/current | tai64nlocal

2012-10-25 00:15:02.375290500 [5633] hogehoge
2012-10-25 00:15:06.980932500 [5633] pogepoge
2012-10-25 00:15:06.981077500 [5633] fugafuga

すぐに忘れそう・・・。

git コンフリクトへの対応

$ git merge dev-branch
Auto-merged dev-branch
CONFLICT (content): Merge conflict in file
Automatic...

まず、競合ファイルを見つける
$ git status

$ git ls-files -u

次に競合の調査(まぁgitは直接ファイルにだめだしをする)
$ cat file

競合時のdiffでは
$ git diff
$ git diff HEAD
$ git diff MERGE_HEAD

そして不思議な事に、どちらかのリビジョンに内容を書き換えると、
$ git diff
の差分がなくなります

競合時のgit log
$ git log --merge --left-right -p
※--mergeは、競合を引き起こしたファイルに関するコミットだけを表示する
※--left-rightは、コミットがマージの左右を表示する
※-p パッチを作成

$ git ls-files -s
100644 ce013625030ba8dba906f756967f9e9ca394464a 1 file
100644 e63164d9518b1e6caf28f455ac86c8246f78ab70 2 file
100644 562080a4c6518e1bf67a9f58a32a67bff72d4f00 3 file

※競合しているファイルを見る場合-uを使う

$ git cat-file -p e63164
中身が見れる

どちらかの修正にする場合
HEADにする場合
$ git checkout --ours file
マージ元にする場合
$ git checkout --theirs file

競合を解決する(rebaseの場合)
$ git add file

マージの中断
コッミット前
$ git reset --hard HEAD

iOS Blocks

Blocksとは

無名関数みたいなもの

記述方法

^(引数){ /* 処理 */ }
^(void){ /* 処理 */}
^(int arg){ /* 処理 */}

処理を保存している!!


void (^sample)(int) =  ^(int arg){ print "%d", arg; }

処理 print "%d", arg; はdataセクション

int localA = 1;
void (^sample)(int) =  ^(int arg){ print "%d:%d", arg, localA; }

処理 print "%d:%d", arg, localA; はスタックに保存される

copyを使うとヒープ領域に保存される

循環参照させないために

ブロックないではselfを使わない
プライベート変数(ivar)も直接使わない

nilチェックを行うために一旦strong変数に代入する
__blockはARC無効の場合、retainカウントされる

iOS4と5以降では記述の仕方が違う

iOS4では__weakが使えないため__unsafe_unretainedを使う

UIViewControllerを使わずにUIViewをInterface builderで作成する

1.UIViewを継承したクラスを作成
#import <UIKit/UIKit.h>

@interface UserView : UIView
{
    IBOutletUIView*    contentView;
    IBOutletUILabel*   userLabel;
    IBOutletUILabel*   telLabel;
}

@property (nonatomic, retain) IBOutletUIView*  contentView;
@property (nonatomic, retain) IBOutletUILabel* userLabel;
@property (nonatomic, retain) IBOutletUILabel* telLabel;

@end

#import "UserView.h"

@implementation UserView

@synthesize contentView, userLabel, telLabel;

- (void)_makeView
{
    [[NSBundlemainBundle] loadNibNamed:@"UserView"owner:selfoptions:nil];
    [selfaddSubview:contentView];        
}

- (id)init
{
    self = [superinit];
    if (self) {
        [self_makeView];
    }
    returnself;
}
- (id)initWithFrame:(CGRect)frame
{
    self = [superinitWithFrame:frame];
    if (self) {
        [self_makeView];
    }
    returnself;
}

- (void)dealloc
{
    [contentViewrelease];
    [userLabelrelease];
    [telLabelrelease];
    [superdealloc];
}

@end

2.Interface builderを使用してレイアウト作成
[New] -> [File] -> "User Interface"からEmptyを作成
名前はUserView(UserView.xibが生成される)
 - File's Owner
    - Custom Class : UserView

今回は表示名(userLabel)と電話番号(telLabel)を追加

3.IBOutletをひも付けを行う
メインのviewはcontentViewとひも付ける。直接UserView自身には設定できないみたい
あとはuserLabel, telLabelをひも付け

4.使ってみる①
ViewControllerでいつも通り使う事が可能
    UserView* userView = [[UserView alloc] initWithFrame:CGRectMake(0, 0, 320, 80)];
    [userView autorelease];
    [self.viewaddSubview:userView];

5.使ってみる②
ViewControllerのxibに追加する方法
 - UIViewをいつも通り追加して、Custom ClassをUserViewに変更
 - UserView.mに以下を追加
- (void)awakeFromNib
{
    [self_makeView];
}



Grand Central Dispatch

 タスクを非同期に実行する技術

Dispatch Queue の種類

Serial Dispatch Queue : FIFOで現在実行中の処理を待つ
  - 1スレッドのみ
Concurrent Dispatch Queue : パラレルに実行
  - 複数のスレッドを使用する

Dispatch Queue の生成

Serial Dispatch Queue
  dispatch_queue_create(@"キュー名", NULL);
  - キューを作成した数だけスレッドが生成される

Concurrent Dispatch Queue
  dispatch_queue_create(@"キュー名", DISPATCH_QUEUE_CONCURRENT);

Dispatch Queueはcreateしたものは、releaseが必要!

Dispatch Queue の取得

    // メイン
    dispatch_queue_t main = dispatch_get_main_queue();

    // 優先度高い
    dispatch_queue_t high = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

    // 標準
    dispatch_queue_t def = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 低い
    dispatch_queue_t low = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

    // バックグランド
    dispatch_queue_t back = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

Dispatch Queueの優先度の変更
  dispatch_set_target_queue

一定時間後に実行したいもの
    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        NSLog(@"after 2.0");
    });

ミリ秒後に実行したい場合は NSEC_PER_MSEC を使用

Dispatch Group
  追加したDispatch Queueがすべて処理が完了したら処理を実行したい場合

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
   
    dispatch_group_async(group, queue, ^{NSLog(@"blk0");});
    dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
   
    dispatch_group_notify(group, queue, ^{NSLog(@"done");});
   
    dispatch_release(group);

  定期的にチェックしたい場合
    long result = dispatch_group_wait(group, 1ull * NSEC_PER_SEC);
    if (result == 0 ) {
        // すべての処理が終了
    } else {
        // 処理実行中
    }

dispatch_barrier_async : 処理を1つだけしか実行しない事を保証する
  読み込みは複数同時で良いが、書き込み中は他の読み込みも行ってほしくない場合等

  dispatch_async(queue, blk0_read);
  dispatch_async(queue, blk1_read);
  // 上記の2つの処理が終わらないとblk_writeは実行しない
  dispatch_barrier_async(queue, blk_write);
  // blk_writeが終わらないとblk2, blk3は実行しない
  dispatch_async(queue, blk2_read);
  dispatch_async(queue, blk3_read);

dispatch_apply : 複数回処理を実行する

dispatch_suspend / dispatch_resume : 処理の中断, 再開
  dispatch_suspend(queue);
  dispatch_resume(queue);

Dispatch Semaphore : データ不整合予防
  dispatch_semaphore_wait / dispatch_semaphore_signal(終了) を使い分けしてシンプルなはいた制御を行う

dispatch_onec : 一度だけ実行
  singleton等に適用

Dispatch I/O
  大きなファイルを読むときに分割して読み込み

Ruby on Raileのユニットテスト

Ruby on Railsのユニットテストについて勉強

テストの種類とサポートツール
ユニットテストサポート
 - Test::Unit
  - Rails標準のフレームワーク - 可読性の欠ける
 - RSpec
  - 可読性に優れている - Test::Unitとは異なる独特の記法
 - Shoulde
  - Test::Utilからの移行が容易 - 機能面で、RSpecに及ばない

エンドツーエンドテスト
 - Request Spece
  - rspec-rails 標準
 - Steak
  - 受け入れテストを記述できる
 - Cucumber
  - 自然言語的な記述でテストシナリオの記述が可能

RSpecによるユニットテスト
 - リポジトリの作成 Test::Utilを組み込まない
  - reils new [APP_PATH] -T
 - rspec-rails のインストール
  - Gemfileに追記
group :development, :test do
  gem "rspec", "2.4.0"
  gem "rspec-rails", "2.4.1"
end
  - ライブラリのインストール
bundle install
 - RSpecの設定ファイル生成
rails generate rspec:install

モデルのテスト
 - モデル生成
reils generate model article title:string body:text
 - マイグレーションスクリプト実行
rake db:migrate
    - ※ undefined method `prerequisites' for nil:NilClass
      エラーの場合、rspecのバージョンを消してbundle updateを実行
group :development, :test do
  gem "rspec"
  gem "rspec-rails"
end
  - データロード
rake db:test:load

 - テストの実行
  - spec/models/article_spec.rb
require 'spec_helper'

describe Article do
  context "title and  body init" do
    before do
      @article = Article.new(
        :title => "first blog",
        :body => "first body"
      )
    end
    it "title check" do
      @article.title.should == "first blog"
    end
    it "body check" do
      @article.body.should == "first body"
    end
  end
end
  - コマンド
bundle exec rspec spec/models/article_spec.rb
 - 出力の変更
  - オプション-f dをつける
  - .rspecで設定可能
bundle exec rspec -f d spec/models/article_spec.rb

 - バリデーションのテスト
  - app/models/article.rbにバリデーションを追加
class Article > ActiveRecord::Base
  attr_accessible :body, :title
  validates :title,  :presence => true
end
  - テストの追加
  context "not title" do
    before do
      @article = Article.new
    end
    it { @article.should_not be_valid }  end
  context "exist title" do
    before do
      @article = Article.new(:title => "first blog")
    end
    it { @article.should be_valid }
  end

コントローラのテスト
 - コントローラの生成
  - rails g controller articles index
 - コントローラで使うメソッド達
  - get, post, put, delete => HTTPリクエスト発行
  - response => HTTPレスポンスの取得
  - assigns => コントローラのいんスタン変数を取得
  - flash, session, cookie => 各項目の取得
 - 一覧表示機能のテスト
  - spec/controllers/articles_controller_spec.rbの実装
require 'spec_helper'

describe ArticlesController do
  describe "GET 'index'" do
    before do
      @article1 = Article.create(
        :title => "Article 1",
        :body  => "Hello"
      )
      @article2 = Article.create(
        :title => "Article 2",
        :body  => "World"
      )
     get 'index'
    end
    it "should be success" do
      response.should be_success
    end
    it "all articles" do
      assigns[:articles].should =~ [@article1, @article2]
    end
  end
end
  - コントローラに一覧取得処理を追加
class ArticlesController < ApplicationController
  def index
   @articles = Article.all
  end
end

ビューのテスト
 - 一覧画面のテスト
require 'spec_helper'

describe "articles/index.html.erb" do
  before do
    assign(
      :articles, [
        Article.create(
          :title => "first blog",
          :body  => "content"
        )
      ]
    )
    render
  end
  it "title view" do
    rendered.should =~ /first blog/
  end
  it "body view" do
    rendered.should =~ /content/
  end
end
 - viewのテストで使うメソッド
  - assign => ビューのインスタンス変数に値を設定する
  - render => ビューのレンダリングを行う
  - rendered => レンダリングした結果を返す
 - Viewの実装
<h1>Articles#index</h1>

<% @articles.each do |article| %>
  <h2><%= article.title %></h2>
  <div><%= article.body %></div>
<% end %>

ヘルパーのテスト
 - 登校日を表示するヘルパー
require 'spec_helper'
describe ArticlesHelper do
  describe "#posted_on" do
    before do
      @now = Time.now
      @article = Article.create(
        :title => 'first blog',
        :body  => 'content'
      )
    end
    it {
      helper.posted_on(@article).should == "#{@now.year}/#{@now.month}/#{@now.day}"
    }
  end
end
 - ヘルパーのテストで使うメソッド
  - assign => インスタンス変数をセットする
  - helper => 対象ページのヘルパーをインクルードしたオブジェクトにアクセス
 - ヘルパーの実装
module ArticlesHelper
  def posted_on(article)
    time = article.created_at
    "#{time.year}/#{time.month}/#{time.day}"
  end
end


Rails入門(チュートリアル)

ruby on rails の勉強がてら、本家のチュートリアルを一通り実行してみる

3.2 プロジェクトの作成
$ rails new blog
- ヘルプが見れる(rails new -h)

3.3.1 DBの内容
config/database.yml
- 各環境の設定内容が記述されている
- デフォルトではsqlite3

3.4 DBの生成
$ rake db:create

4.1 サーバ起動
$ rails server
- http://localhost:3000 にアクセスするとwelcomeページが表示される
- 終了はCtrl+c

4.2 お決まりのHello world
- コントローラの生成
$ rails generate controller home index
- 生成されるコントローラは app/controllers/home_controller.rb
- view(html)を修正app/views/home/index.html.erb
<h1>Hello, Rails!</h1>

4.3 アプリケーションのHomeページを設定
- 必ずpublic/index.html が表示されてしまうため削除
$ rm public/index.html
- ディスパッチの設定を追加
- ファイル: config/routes.rb

Blog::Application.routes.draw do
#...
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
root :to => "home#index"

- その後再度 http://localhost:3000 にアクセスすると、先ほどのテンプレートが表示される

6 リソースの作成(MVCを作る)
- scaffold を指定すると、model, view, controllerを一括で作成してくれる
$ rails generate scaffold Post name:string title:string content:text

6.1 マイグレーション
- テーブルの作成や、変更などを管理する仕組み
- ファイル :db/migrate/20120508162203_create_posts.rb
- マイグレーションスクリプト実行
$ rake db:migrate
- productionで作成する場合はオプションをつける rake db:migrate RAILS_ENV=production

6.2 Topページにリンクを追加
- ファイル : app/views/home/index.html.erb
<h1>Hello, Rails!</h1>
<%= link_to "My Blog", posts_path %>

- link_to [タイトル], 遷移先?

6.4 モデルの確認
- ファイル : app/models/post.rb

6.5 バリデータの追加
- ファイル : app/models/post.rb
class Post < ActiveRecord::Base
attr_accessible :content, :name, :title
validates :name, :presence => true
validates :title, :presence => true,
:length => { :minimum => 5 }
end

- attr_accessibleは変更可能なパラメータ
- presence は必須、lengthは長さを指定

6.6 コンソールを使ってみよう
$ rails console
>> p = Post.new(:content => "A new post")
=> #<Post id: nil, name: nil, title: nil,
content: "A new post", created_at: nil,
updated_at: nil>
>> p.save
=> false
>> p.errors.full_messages
=> ["Name can't be blank", "Title can't be blank", "Title is too short (minimum is 5 characters)"]

- バリデータが効いているのがわかる

6.7 一覧ページ
- ファイル : app/controllers/posts_controller.rb
def index
@posts = Post.all

respond_to do |format|
format.html # index.html.erb
format.json { render json: @posts }
end
end

- @postsに投稿内容をすべて取得する
- html, jsonに対応
- http://localhost:3000/posts.html
- http://localhost:3000/posts.json

- テンプレート : app/views/posts/index.html.erb
- html エスケープしない場合、
- rails 2 だと <%=h post.name %>
- rails 3 では <%= raw post.name %>

6.8 カスタムレイアウト
- フッターやヘッダーを定義
- ファイル : app/views/layouts/application.html.erb
- 各コントローラ毎に指定する場合
- app/views/layouts/posts.html.erb

6.9 新規作成ページ
- ファイル : app/controllers/posts_controller.rb
def new
@post = Post.new

respond_to do |format|
format.html # new.html.erb
format.json { render json: @post }
end
end
- テンプレート : app/views/posts/new.html.erb
<h1>New post</h1>
<%= render 'form' %>
<%= link_to 'Back', posts_path %>
- render 'form'を指定することで、app/views/posts/_form.html.erb の内容を出力する

- 投稿処理
def create
@post = Post.new(params[:post])

respond_to do |format|
if @post.save
format.html { redirect_to @post, notice: 'Post was successfully created.' }
format.json { render json: @post, status: :created, location: @post }
else
format.html { render action: "new" }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end

6.10 詳細表示
- http://localhost:3000/posts/1 にアクセスすると /posts/(id) として、:idに取得可能
def show
@post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render :json => @post }
end
end

- テンプレート : app/views/posts/show.html.erb

6.11 編集ページ
- 詳細表示と違い、jsonでのレスポンスがないためシンプル
def edit
@post = Post.find(params[:id])
end
- 更新処理
def update
@post = Post.find(params[:id])

respond_to do |format|
if @post.update_attributes(params[:post])
format.html { redirect_to @post, notice: 'Post was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
- update_attributesで必要なパラメータを更新

6.12 削除
- ファイル : app/controllers/posts_controller.rb
def destroy
@post = Post.find(params[:id])
@post.destroy
respond_to do |format|
format.html { redirect_to posts_url }
format.json { head :no_content }
end
end

7.1 モデルの追加
- テーブル間のリレーションシップを指定 post:references
$ rails generate model Comment commenter:string body:text post:references
- モデルクラスにも定義されている
- ファイル : app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :post
attr_accessible :body, :commenter
end

- 再度マイグレーションスクリプトが生成されているて、それを反映
- ファイル : db/migrate/20120508170658_create_comments.rb
$ rake db:migrate

7.2 関連づけモデル
- 1投稿に付き複数のコメントが存在するためPostのmodelを修正
- ファイル :app/models/post.rb
class Post < ActiveRecord::Base
attr_accessible :content, :name, :title
validates :name, :presence => true
validates :title, :presence => true,
:length => { :minimum => 5 }
has_many :comments
end

7.3 ルート(ディスパッチ)にコメントを追加
- ファイル : config/routes.rb
- posts部分を修正
resources :posts do
resources :comments
end

7.4 コントローラの追加
$ rails generate controller Comments
- 上記エラー出る場合、ルートファイルで追加した部分をコメントアウトする

- Postのテンプレートにcomment投稿を追加
- ファイル : app/views/posts/show.html.erb
<p id="notice"><%= notice %></p>

<p>
<b>Name:</b>
<%= @post.name %>
</p>

<p>
<b>Title:</b>
<%= @post.title %>
</p>

<p>
<b>Content:</b>
<%= @post.content %>
</p>

<h2>Add a comment:</h2>
<%= form_for([@post, @post.comments.build]) do |f| %>
<div class="field">
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
</div>
<div class="field">
<%= f.label :body %><br />
<%= f.text_area :body %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>

<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Back', posts_path %>

- コメントコントローラに処理を追記
- ファイル : app/controllers/comments_controller.rb
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.create(params[:comment])
redirect_to post_path(@post)
end


- 詳細画面でコメントを閲覧できるように修正
- ファイル : app/views/posts/show.html.erb
<h2>Comments</h2>
<% @post.comments.each do |comment| %>
<p>
<b>Commenter:</b>
<%= comment.commenter %>
</p>
<p>
<b>Comment:</b>
<%= comment.body %>
</p>
<% end %>


8.リファクタリング
- テンプレートの共通部分を外だししてみる

8.1 コレクション
- コメント部分のテンプレを外だし
- ファイル : app/views/comments/_comment.html.erb
<p>
<b>Commenter:</b>
<%= comment.commenter %>
</p>
<p>
<b>Comment:</b>
<%= comment.body %>
</p>

- 参照部分の修正 app/views/posts/show.html.erbを修正 (まだ納得は行かない><)
- 修正前
<% @post.comments.each do |comment| %>
<p>
<b>Commenter:</b>
<%= comment.commenter %>
</p>
<p>
<b>Comment:</b>
<%= comment.body %>
</p>
<% end %>

- 修正後
<%= render @post.comments %>

8.2 Form部分の外だし
- ファイル : app/views/comments/_form.html.erb
<%= form_for([@post, @post.comments.build]) do |f| %>
<div class="field">
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
</div>
<div class="field">
<%= f.label :body %><br />
<%= f.text_area :body %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>

- 参照部分の修正 : app/views/posts/show.html.erb
- 今回は単純に置き換える
<%= render "comments/form" %>

9 コメントの削除
- リンクの追加 : app/views/comments/_comment.html.erb
<p>
<%= link_to 'Destroy Comment', [comment.post, comment],
:confirm => 'Are you sure?',
:method => :delete %>
</p>

- コントローラに追加 : app/controllers/comments_controller.rb
def destroy
@post = Post.find(params[:post_id])
@comment = @post.comments.find(params[:id])
@comment.destroy
redirect_to post_path(@post)
end

9.1 関連データの削除も対応
- 親の投稿データが削除された場合、一緒に削除
- ファイル : app/models/post.rb
class Post < ActiveRecord::Base
attr_accessible :content, :name, :title
validates :name, :presence => true
validates :title, :presence => true,
:length => { :minimum => 5 }
has_many :comments, :dependent => :destroy
end

10 セキュリティー
- Basic認証
- ファイル : app/controllers/posts_controller.rb
class PostsController < ApplicationController
http_basic_authenticate_with :name => "dhh", :password => "secret", :except => [:index, :show]
# GET /posts
# GET /posts.json
def index
@posts = Post.all

- :exceptで対象外を指定
- :only => :destroy を指定すると、削除のみ認証する

11 複数モデル
- タグモデルの生成
$ rails generate model tag name:string post:references
- マイグレーション実行
$ rake db:migrate
- モデルに関連づけを行う
- ファイル : app/models/post.rb
has_many :tag

accepts_nested_attributes_for :tags, :allow_destroy => :true,
:reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }

- accepts_nested_attributes_forは親子関係の定義?
- allow_destroyはネストした属性にチェックボックスを表示
- reject_if は禁止条件を設定

- フォームテンプレートにタグを追加
- ファイル : views/posts/_form.html.erb
<h2>Tags</h2>
<%= render :partial => 'tags/form',
:locals => {:form => post_form} %>

- タグ用のフォームテンプレート
- ファイル : app/views/tags/_form.html.erb
<%= form.fields_for :tags do |tag_form| %>
<div class="field">
<%= tag_form.label :name, 'Tag:' %>
<%= tag_form.text_field :name %>
</div>
<% unless tag_form.object.nil? || tag_form.object.new_record? %>
<div class="field">
<%= tag_form.label :_destroy, 'Remove:' %>
<%= tag_form.check_box :_destroy %>
</div>
<% end %>
<% end %>

- 投稿テンプレートにタグを追加
- ファイル : app/views/posts/show.html.erb
<p>
<b>Tags:</b>
<%= @post.tags.map { |t| t.name }.join(", ") %>
</p>

12 View Helpers
- ファイル : app/helpers/posts_helper.rb
def join_tags(post)
post.tags.map { |t| t.name }.join(", ")
end

- 使ってみる : app/views/posts/show.html.erb
- 変更前
<p>
<b>Tags:</b>
<%= @post.tags.map { |t| t.name }.join(", ") %>
</p>
- 変更後
<p>
<b>Tags:</b>
<%= join_tags(@post) %>
</p>

はじめてのRuby


RVM(Ruby Version Manager)


  • RVMとは複数バージョンのRubyを使えるルーツ

rvmのインストール

$ curl -L get.rvm.io | bash -s stable
.bashrcに追加
source ~/.rvm/scripts/rvm

Rudyのインストール

rudyのインストール可能なバージョンを確認
$ rvm list known

rudyのインストール
$ rvm install 1.9.2
The provided compiler '/usr/bin/gcc' is LLVM based, it is not yet fully supported by ruby and gems, please read `rvm requirements`
 - Mac OSX Lionではインストール時にエラーが出たので、下記を実行
   - export CC=/usr/bin/gcc-4.2


インストールバージョンの確認
$ rvm list


最新版にするには
$ rvm get head
$ rvm reload

Rudy on Railsのインストール

Rudyのパッケージ管理ツール(gem)を使用してrailsをインストール
$ gem install rails

アプリケーションの作成

プロジェクトの作成

$ rails new books

パッケージの管理

Bundlerは複数のPCで必要なGemパッケージをインストールする仕組みを提供してくれる
 - Gemfile : 必要なパッケージ情報
 - Gemfile.lock : インストール済み情報

静的コンテンツ

PROJECT/public

MVC生成コマンド

コントローラの作成

$ rails generate controller [コントローラ名]

DBの作成

$ rake db:create
 - config/database.yml の内容でDBを作成する

モデルの作成

$ rails generate model title

Scaffold機能

 - controller, model, viewをコマンド一つで実行してくれる


マイグレーション

テーブルの作成や、変更の仕組みとしてマイグレーションと呼ばれる機能を提供

マイグレーションスクリプト実行

$ rake db:migrate


レスポンス関連

レスポンス形式の変更

    respond_to do |format|
      format.html
      format.json {render :json => オブジェクト}
      format.xml  {render :xml => オブジェクト}
    end

リダイレクト

    render_to :action => 'アクション名'

UIKitにBlocksを適用(UIActionSheet)

UIKitでは、ボタンを押したり、アクションが行われると、delegateの仕組みを使って通知を受けます。

例:

- (void)showActionSheet
{
    UIActionSheet* as = [[UIActionSheet alloc] initWithTitle:@"title"                                                    delegate:self                                           cancelButtonTitle:@"cancell"                                      destructiveButtonTitle:@"destruct"                                           otherButtonTitles:@"btn1", @"btn2", nil ];
    [as showInView:self.view];
    [as autorelease];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    switch(actionSheet.tag) {
        case MSG_CONFIRM_TAG:        {
            switch (buttonIndex) {
                case 1:
                    // destruct                    break;
                case 2:
                    // cancell                    break;
                case 3:
                    //btn1                default:
                    break;
            }
        }
            break;
        default::
            break;
    }
}



表示するロジックと、アクションが別々に書かれていて、直感的なコードではない。
また、異なるUIActionSheetでも同じdelegate先になってしまう(書き方しだいですが)

iOS4からはBlocksというコールバックのような仕組みが準備されていて、それを使えば、表示部分と、アクションされた部分を一カ所で記述できる。

例1:

- (void)showActionSheet{    UIActionSheet* as = [[UIActionSheet alloc] initWithTitle:@"sample"                                                       block:^(NSInteger buttonIndex){                                                           switch (buttonIndex) {                                                               case 1:                                                                   // destruct                                                                   break;                                                                                                                                  case 2:                                                                   // cancell                                                                   break;                                                               case 3:                                                                   //btn1                                                               default:                                                                   break;                                                           }                                                       }                                            cancelButtonTitle:@"cancell"                                      destructiveButtonTitle:@"destruct"                                           otherButtonTitles:@"oth1", @"oth2",                         nil];    [as showInView:self.view];    [as autorelease];}
例2:



- (void)showActionSheet
{
    UIActionSheet* as = [[UIActionSheet alloc] init];
    as.title = @"sample";
    [as addButtonWithTitle:@"btn1" withBlock:^{
// btn1
    }];
    [as addButtonWithTitle:@"btn2" withBlock:^{
// btn2
    }];
    [as addButtonWithTitle:@"cancel" withBlock:^{
// cancell
    }];
    [as setCancelButtonIndex:2];
    [as showInView:self.view];
    [as autorelease];
}

今回はcategoryとして生成しましたが、もちろんサブクラス化しても対応できます

  • initializeと、ボタンを追加する(addButtonWithTitle)にそれぞれblockを受け取る
  • delegate先のblock格納先として、UIActionSheetCallbackを作成



UIActionSheet+Blocks.h




@interface UIActionSheet (Blocks)
typedef void (^UIActionSheetCallback_t)(NSInteger buttonIndex);
typedef void (^UIActionSheetButtonCallback_t)(void);

- (id)initWithTitle:(NSString *)title block:(UIActionSheetCallback_t)block cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION;
- (NSInteger)addButtonWithTitle:(NSString *)title withBlock:(UIActionSheetButtonCallback_t)block;
@end


@interface UIActionSheetCallback : NSObject <UIActionSheetDelegate> {
    UIActionSheetCallback_t callback;    NSMutableDictionary* buttonCallbacks;
}
@property (nonatomic, copy) UIActionSheetCallback_t callback;@property (nonatomic, retain) NSMutableDictionary* buttonCallbacks;
- (id)initWithCallback:(UIActionSheetCallback_t) callback;
@end


 UIActionSheet+Blocks.m
#import "UIActionSheet+Blocks.h"
@implementation UIActionSheet (Blocks)
- (id)initWithTitle:(NSString *)title block:(void(^)(NSInteger))block cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... {
    self = [self initWithTitle:title delegate:nil cancelButtonTitle:cancelButtonTitle destructiveButtonTitle:destructiveButtonTitle otherButtonTitles:nil];    if (self) {
        self.delegate = [[[UIActionSheetCallback alloc] initWithCallback:block] autorelease];
      
        va_list args;
        va_start(args, otherButtonTitles);
        for (NSString *arg = otherButtonTitles; arg != nil; arg = va_arg(args, NSString*)) {
            [self addButtonWithTitle:arg];
        }
        va_end(args);
    }
    return self;}
- (NSInteger)addButtonWithTitle:(NSString *)title withBlock:(void(^)(void))block {
    if (!self.delegate) {
        self.delegate = [[[UIActionSheetCallback alloc] init] autorelease];
    }
  
    NSInteger index = [self addButtonWithTitle:title];
    [((UIActionSheetCallback*)self.delegate).buttonCallbacks setObject:[[block copy] autorelease] forKey:[NSNumber numberWithInteger:index]];  
    return index;
}
@end
@implementation UIActionSheetCallback
@synthesize callback;@synthesize buttonCallbacks;
- (void)_init {
    self.buttonCallbacks = [NSMutableDictionary dictionary];    [self retain];  
}
- (id)init {
    if (self = [super init]) {
        [self _init];
    }
    return self;}
- (id)initWithCallback:(UIActionSheetCallback_t)aCallback {
    if(self = [super init]) {
        self.callback = aCallback;
        [self _init];
    }
    return self;}
// UIAlertView delegate メソッド- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if(callback)        callback(buttonIndex);
    UIActionSheetButtonCallback_t buttonCallback = [buttonCallbacks objectForKey:[NSNumber numberWithInteger:buttonIndex]];    if (buttonCallback)
        buttonCallback();
    [self release];
}
- (void)dealloc {
    self.callback = nil;
    self.buttonCallbacks = nil;    [super dealloc];
}
@end





コードはこちらから
https://github.com/hrk-ys/ios-blocks

iPhoneの電話帳のグループを扱う ABAddressBookRef


ABAddressBookRef

アドレス帳データの生成、保存、削除などを行う
複数の連絡先を格納している手帳みたいなもの


ABRecordRef


アドレス帳の中にレコード(ABRecord)がある
1件毎の各個人の電話帳みたいなもの

レコードには2種類
 - Person Records
  - 氏名、電話番号、Email、住所など
 - Group Records
  - 名前、IDぐらいしかない
    - Groupには複数のPersonRecordをひも付ける事ができる

※ただiPhone単体ではグループの作成はできないらしい

グループ関連の操作

アドレス帳のグループ取得

    ABAddressBookRef book = ABAddressBookCreate();
    CFArrayRef recodes = ABAddressBookCopyArrayOfAllGroups(book);
    for (int i = 0; i < CFArrayGetCount(recodes); i++) {
        ABRecordRef group = CFArrayGetValueAtIndex(recodes, i);
        ABRecordID recodeId = ABRecordGetRecordID(group);
        NSString* str = (NSString*) ABRecordCopyValue(group, kABGroupNameProperty);
        NSLog(@"recodeId:%d group name:%@", (int)recodeId, str);
        CFRelease(str);
    }
    CFRelease(recodes);
    CFRelease(book);

グループの作成

NSString* groupName = @"会社";
    ABAddressBookRef book = ABAddressBookCreate();
    ABRecordRef group = ABGroupCreate();
    ABRecordSetValue(group, kABGroupNameProperty,groupName, nil);
    ABAddressBookAddRecord(book, group, nil);
    ABAddressBookSave(book, nil);
    NSInteger groupId = ABRecordGetRecordID(group);
NSLog(@"groupId:%d", groupId);
    CFRelease(book);
    CFRelease(group);

グループの削除

NSInteger groupId = 1;
    ABAddressBookRef book = ABAddressBookCreate();
    // 削除
    ABRecordRef recode = ABAddressBookGetGroupWithRecordID(book, groupId);
    ABAddressBookRemoveRecord(book, recode, nil);
    ABAddressBookSave(book, nil);
   
    CFRelease(book);

余談ですが、Personの最終更新日は下記で取得可能です

最終更新日の取得
        CFDateRef cfCreateDate = ABRecordCopyValue(person, kABPersonCreationDateProperty);

iOS コピー&ペースト UIPasteboard と UIMenuControllerの拡張


切り取り(cat)やコピー(copy)した内容を置き換える


 クリップボード(iOSではペーストボードというらしい)に追加や削除したタイミングでnotificationが発行される
  - 追加 : UIPasteboardChangedNotification
  - 削除 : UIPasteboardChangedTypesRemovedKey

以下サンプル
-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
 
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pasteboardChanged:) name:UIPasteboardChangedNotification object:nil];
}
- (void)pasteboardChanged:(NSNotification*)notification
{
    NSLog(@"pasteboardChanged");
    NSDictionary* userInfo = [notification userInfo];
    NSArray* keys = [userInfo objectForKey:UIPasteboardChangedTypesAddedKey];
    if ([keys count] != 0) {
        UIPasteboard *board = [UIPasteboard generalPasteboard];
        [board setValue:@"上書きする文字" forPasteboardType:[keys objectAtIndex:0]];
    }
}

ポップアップをカスタマイズする UIMenuController


 ポップアップの表示は、UIMenuControllerを使う

  1. UIMenuControllerの設定
    UIMenuController* menuController = [UIMenuController sharedMenuController];
  2. 表示するアイテムを追加
    menu.menuItems = [NSArray ...];
  3. 表示メッセージを送る
    [menu setMenuVisible:YES animated:YES];
   
そして、注意しないと行けないところは、
 - 表示するviewがfirstResponderになっていること
 - ポップアップが表示できる領域が確保されていること(こだわりない場合はDefaultで)
   menuController.arrowDirection = UIMenuControllerArrowUP; // 注意

特に2番目の所ははまりました。。
 viewをCGRectMake(10,10, 50, 44)などの位置につくってしまうと、ポップアップが表示される領域がないので。

以下サンプル
@interface ViewEx : UIView
@end
@implementation ViewEx
- (BOOL)canBecomeFirstResponder{
    return YES;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UIMenuController* menuController = [UIMenuController sharedMenuController];
    [menuController setTargetRect:CGRectZero inView:self];
 
    NSMutableArray* menuItems = [NSMutableArray array];
 
    [menuItems addObject:
     [[[UIMenuItem alloc] initWithTitle:@"メニュー"
                                 action:@selector(menu:)] autorelease]];
    menuController.menuItems = menuItems;
    [menuController setMenuVisible:YES animated:YES];
}
- (void)menu:(id)sender
{
    NSLog(@"menu1: %@", sender);
}
@end

呼び出し側
    ViewEx* viewEx = [[ViewEx alloc] initWithFrame:CGRectMake(110, 100, 50, 44)];
    [viewEx setBackgroundColor:[UIColor greenColor]];
    [self.view addSubview:viewEx];
    [viewEx becomeFirstResponder];
ちなみに標準のコピー等を表示したい場合、下記のように対応するメソッドのみYESを返して上げる必要があります

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if (action == @selector(cat:)) {
  return YES;
}
  return NO;
}

UITableView(UITableViewCell)内でのメニュー表示

やる事は同じです
まずはUITableViewCellの継承クラスを作成

@interface CellEx : UITableViewCell
@end
@implementation CellEx
- (BOOL)canBecomeFirstResponder{
    return YES;}
@end
使う側
- (void)viewDidLoad{    [super viewDidLoad];// Do any additional setup after loading the view.
    UITableView* tableView = [[[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain] autorelease];    tableView.delegate = self;    tableView.dataSource = self;    [self.view addSubview:tableView];}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{    return 10;}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{    CellEx *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];    if (cell == nil) {        cell = [[[CellEx alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"] autorelease];    }    cell.textLabel.text = [NSString stringWithFormat:@"row:%d", indexPath.row];
    return cell;}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{    CellEx* cell = (CellEx*)[tableView cellForRowAtIndexPath:indexPath];    [cell becomeFirstResponder];
    UIMenuController* menuController = [UIMenuController sharedMenuController];    [menuController setTargetRect:CGRectZero inView:cell];    menuController.arrowDirection = UIMenuControllerArrowDefault;        NSMutableArray* menuItems = [NSMutableArray array];        [menuItems addObject:     [[[UIMenuItem alloc] initWithTitle:@"メニュー"                                 action:@selector(menu:)] autorelease]];    menuController.menuItems = menuItems;    [menuController setMenuVisible:YES animated:YES];    }- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {    if (action == @selector(menu:)) {  return YES;    }  return NO;}
- (void)menu:(id)sender{    NSLog(@"menu: %@", sender);}

実行するアクションの実装場所

上記のテーブルの例だと、CellExにcanPerformActionを実装しても動きます。
もちろん、menu:(id)senderも一緒に持って行きます。
どのような仕組みかは理解してませんがなんか気持ち悪いですね。
setTargetRnage:inViewで指定したViewのメソッドを呼んでると思ったんですが、違うみたいですね。

UITextFieldやUITextViewへのカスタムキーボード適用


前回のカスタムキーボードではまりかけたところ。

単純に各view(UITextField, UITextView)に文字列を追加するだけでは、
それぞれのdelegateが呼ばれません。

-(void)selectWord:(UIButton*)button{ _textField.text = [_textField.text stringByAppendingString:str];}

標準のキーボードと同じようにdelegateを考慮した作りをしたいので、調べてみました。

UITextInput

UITextFieldや、UITextViewはUITextInputを実装しています。
UITextInputはどのようなプロトコルかというと、
 - テキスト入力と相互作用するもの
 - 位置や、範囲などを管理

テキストの入出力の中心的なプロトコルを決めている
他には
 - UITextPosition, UITextRange
 - UITextInputTokenizer, UITextInputStringTokenizer
 - UITextInputDelegate
 - UIKeyInput
などなど

そして、そこに定義してある通りにUITextViewやUITextFieldは実装してあるわけで。。。
ここら辺は表示側と、入力側がうまくやりとりできるように定義しているみたい

textInRange : 選択中の文字列を取得
selectedTextRange : 選択中の位置を取得
textInputView : 実態への参照(UITextField, UITextViewなど)
inputDelegate : UITextInputDelegate


UIKeyInput Protocol

ここが本題かも。
文字列の入力や削除等はUIKeyInput Protocolで実装されているため、
今回だと、[_textField.text insertText]を呼び出せばOK

 - insertText 文字の入力
 - deleteBackward 削除
 - hasText 文字の存在確認


UITextInputDelegate Protocol

そして、テキスト入力などはtextFieldShouldBeginEditingや、textFieldDidBeginEditingなどがあり、
そのDelegateを正しく呼び出すために
 - textWillChangeやtextDidChangeを呼ぶ

iPhone開発 DCIntrospect ショートカット


iPhone開発に置いて、Interface Builderは絶対に使わないと心に決めたのはいいですが、やっぱり微調整するのはちょっとしんどいなーって思ってると、すっごく便利なものを発見!
「DCIntrospect」というツール
https://github.com/domesticcatsoftware/DCIntrospect
実際に落としてきてサンプル(DCIntrospectDemo)を実行するとわかりますが、
起動して表示された画面の座標やサイズを確認できたり、その場で修正できたりするものです。

使い方
AppDelegateの[self.window makeKeyAndVisible]の直後に以下を追加
#if TARGET_IPHONE_SIMULATOR
    [[DCIntrospect sharedIntrospector] start];
#endif
※もちろん #import "DCIntrospect.h" も忘れずに

シミュレータで起動したあと、スペースを押してモードを切り替える。
後はマウスでコンポーネントをクリックすると、情報が表示される。



これはマジで便利すぎる!!

ショートカット
  • スペース:切り替え
  • ? : ヘルプ
表示
  • o : アウトライン
  • O : ハイライト指定してないアイテム?
  • f : drawRectを再度呼び出す?
  • c : 座標
ログ
  • p : 詳細出力
  • a : アクセサープロパティ
  • v : descriptionを呼び出す
  • ` : デバック?
  • y : 親ビューに移動
  • t : 子ビューに戻る
移動、リサイズ
  • 左矢印 or 4 : 左へ1px 移動
  • 右矢印 or 6 : 右へ1px 移動
  • 上矢印 or 8 : 上へ1px 移動
  • 下矢印 or 2 : 下へ1px 移動
  • alt + (左矢印 or 7) : 1px サイズ幅を広げる
  • alt + (右矢印 or 9) : 1px サイズ幅を狭める
  • alt + (上矢印 or 1) : 1px 高さを広げる
  • alt + (下矢印 or 3) : 1px 高さを狭める
表示系
  • - : 輝度を下げる
  • + : 輝度を上げる
  • 0 : 編集内容を出力
再描画
  • d : 再描画(setNeedsDisplay)?
  • l : 再描画(setNeedsLayout)?
  • r : 再描画?

ssh + agent forward + screen の設定

sshで接続元にある秘密鍵を使う場合、agent fowardすればOK
ssh -A [ホスト1]

すると、ホスト1からホスト2へsshする場合、ローカルの秘密鍵を使ってくれる。

ただし、ホスト1でscreenを起動している場合、うまくホスト2へ秘密鍵が受け継がれない。

agent fowardした場合、環境変数にSSH_*に接続元情報が格納されている。
screenでは、起動時の環境変数を保持しているため、デタッチした場合、初期の環境変数を保持しているので、うまく秘密鍵が取得できない

> env | grep SSH
SSH_CLIENT=XXX.XXX.XX.XXX XXX XX
SSH_TTY=/dev/pts/0
SSH_AUTH_SOCK=/tmp/ssh-xxxxxxxxxx/agent.xxxxxxxx
SSH_CONNECTION=XXXX.XXX.XXX.XXX XXX.XXX.XXX 


そこでちょっと強引ですが、ssh でログイン時に上記環境変数をファイルに保存。
デタッチ後のscreenプロセスでそのファイルを使って、最新の状態に変更するようにしてみました。


bashrcに以下を追加

SSHV="SSH_CLIENT SSH_TTY SSH_AUTH_SOCK SSH_CONNECTION DISPLAY"
for x in ${SSHV} ; do
    (eval echo $x=\$$x) | sed  's/=/="/
                                s/$/"/
                                s/^/export /'
done 1>$HOME/bin/fixssh

screen を起動してsource ~/bin/fixsshすればOK


iPhone カスタムキーボードを作る

iPhone開発に置いて、キーボード入力をデフォルトのキーボードではなく、個別に用意したものと切り替え可能にする

1.UITextView, UITextFieldのinputViewに置き換えたいviewを設定

UITextField* textField = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, bounds.size.width - 20, 30)];
    textField.borderStyle = UITextBorderStyleRoundedRect;
    CustomKeyboard* keyboard = [[CustomKeyboard alloc] initWithFrame:CGRectMake(0, 0, bounds.size.width, 200)];
    textField.inputView = keyboard;

2.キーボードが表示されている場合に切り替える場合はreloadInputViewsメッセージを送る

textView.inputView = nil; // デフォルトに戻す場合
[textView reloadInputViews]

以下サンプルコード
https://github.com/hrk-ys/ios-custom-inputview

iPhone開発の多言語化

ちょっとはまったのでメモ的な意味で投稿

Xcode4でプロジェクトを新規作成すると、「InfoPlist.strings」 が作成されます。

自分はこのファイルに多言語の文字列を設定できます。
主にシステム的な設定ですね。

例:ja.lproj/InfoPlist.strings
CFBundleName = "テストアプリケーション";
CFBundleDisplayName = "テストアプリ";
 例:en.lproj/InfoPlist.strings
CFBundleName = "Test Application";
CFBundleDisplayName = "Test App";


ただ、アプリから参照する文字列は上記のファイルでは定義できず、
ファイルを作成して「Localized.string」を定義しないと使えないのです。
(ここが結構はまりました)
こちらはアプリ内から参照できる文字列
NSLocalizedString(@"msg_get", @"message get");

例 : 日本語
"msg_get" = "メッセージ取得";
例:英語
"msg_get" = "Get Message";

Core Dataについて

CoreDateとは

  • Modelオブジェクトをファイルに保存したり、ファイルから復元したりする
  • アーカイブよりも多機能
    • Modelオブジェクトの変更履歴(アンドゥとレドゥ、オブジェクト間の相互関係の管理)
    • Modelオブジェクトのサブセットだけをメモリ内の保持する事ができる
    • GUIベースのエディタでModelクラスを定義する事ができる
    • データストアのバージョン管理および移行のためのインフラストラクチャ

登場人物

  • 管理オブジェクト(Managed Object Model)
    • NSManagedObjectまたはNSManagedObjectのサブクラス
    • 概念的にはデータベースのテーブル内のレコードオブジェクト
    • MVCのModelオブジェクト
  • 管理オブジェクトコンテキスト(Managed Ojbject Context)
    • NSManagedObjectContextのインスタンス
    • 管理オブジェクトのコレクションを管理
    • Modelオブジェクトのグループを形成
    • ライフサイクル管理、妥当性検証、関係の管理、アンドゥ/リドゥまで管理
  • 管理オブジェクトモデル
    • NSManagedObjectModelのインスタンス
    • データベースを定義するスキーマオブジェクト
    • モデルはエンティティ記述オブジェクトのコレクション
  • 永続ストアコーディネータ
    • 永続ストアコーディネータはNSPersistentStoreCoordinatorのインスタンス
    • 永続オブジェクトストア(外部ストア:ファイルなどを指す)のコレクションを管理
    • アプリケーション内のオブジェクトとデータベース内のレコードをマッピングする

サンプル

AppleのDeveloper Libraryにあるドキュメントをもとに使ってみる

プロジェクトの作成

プロジェクトの作成の作成じに、Core Dataを使用するにチェックを入れる (Use Core Data for storage)
  • アプリケーションデリゲートクラス
    • プロパティーが自動生成される
// 管理オブジェクトコンテキスト
@property (nonatomic, retain, readonly) NSManagedObjectContext
*managedObjectContext;
// 管理オブジェクトモデル
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
// 永続ストアコーディネータ
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator
*persistentStoreCoordinator;
- (NSURL *)applicationDocumentsDirectory;
- (void)saveContext;
  • Core Dataのモデル(.xcdatamodeld)ファイル(通常、管理オブジェクトモデルと呼ばれる)

データのモデリング

xcodeで[プロジェクト名].xcdatamodelを選択後、Entityを追加、そのEntityに対してAttributeを追加していく

カスタム管理クラスオブジェクト

通常のクラスファイルを作成する手順で「Managed Object Class」を選択
Entityが選択できるので、先ほど作成したEntityを選択する
プロパティはsynthesizedではなく、dynamicとなる。コンパイル時ではなく、実行時にアクセサーを生成する
実装ファイル(Event.m)にはdeallocがない。オブジェクトのライフサイクルはCoreDataが行うため

管理クラスオブジェクトの生成/保存

通常は、NSEntityDescriptionの簡易メソッド(insertNewObjectForEntityForName:inManagedObjectContext:)を使用して管理オブジェクトを作成
指定したエンティティーに対応するクラスを初期化し、コンテキストに挿入する
オブジェクトを追加したり変更したりすると、その変更はsave:が呼び出されるまではメモリ内に保持される

// Eventエンティティの新規インスタンスを作成して設定する
Event *event = (Event *)[NSEntityDescription insertNewObjectForEntityForName:@"Event" inManagedObjectContext:managedObjectContext];

// ここでeventに対してデータを設定

// データの保存
NSError *error = nil;
if (![managedObjectContext save:&error]) {
    // エラーを処理する
}

管理オブジェクトのフェッチ

フェッチ要求では、最低限、関心のあるエンティティを指定
オブジェクトが持つ値に関する制限を指定したり、オブジェクトを返す順番を指定したりすることも可能

NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event"
inManagedObjectContext:managedObjectContext];
[request setEntity:entity];

// ソート
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:@"creationDate" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor,
nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];

// 実行
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext
executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
    // エラーを処理する
}

[self setEventsArray:mutableFetchResults];
[mutableFetchResults release];
[request release];

管理オブジェクトの削除

レコードを削除するには、NSManagedObjectContextのdeleteObject:メソッドを使用する
登録同様にseveメソッドを読んで、反映させる

// 指定のインデックスパスにある管理オブジェクトを削除する。
NSManagedObject *eventToDelete = [eventsArray
objectAtIndex:indexPath.row];
[managedObjectContext deleteObject:eventToDelete];

// 変更をコミットする。
NSError *error = nil;
if (![managedObjectContext save:&error]) {
 
はじめてのCoreDataではまりどころ
"The operation couldn’t be completed. (Cocoa error 134100.)"
スキーマを変更する場合、マイグレーション処理をしましょうってことらしい。
アプリを削除して再度実行すればなおる

DataStoreの辛いところ

# DataStoreの限界 FacebookやインスタグラムのようなSNSで、投稿、公開範囲、お気に入りなどの機能をDataStoreで実現しようとするとどうしても辛くなる。 ## DataStoreで実現可能? - users - id - feeds - i...