propertyとretainとautoreleaseと

Objective-Cの@property (retain)なプロパティへのautoreleaseされているオブジェクトの代入ではまったのでメモしておきます。

自分のクラスの中ではselfを使わないとプロパティ経由にはならない

@interface MyClass {
  SomeObject *_prop;
}
@property (nonatomic, retain) SomeObject *_prop;
@end
@implementation MyClass
@synthesize _prop;

  - (void) func:(SomeObject *) prop {
    self._prop = prop; //propのretainCountが+1される
    _prop = prop; //propertyのsetterを経由しないで_propにアクセスするためretainされない
  }
@end

autoreleaseされているオブジェクトを継続的に使う場合は自分でretainする必要がある

@interface MyClass2 {
  SomeObject *_prop2;
}
@end

- (void) func2 {
  _prop2 = [[SomeClass getAutoreleaseObj] retain]; //自分のクラス内で使い続けることをretainで明示する
  _prop2 = [SomeClass getAutoreleaseObj]; //この場合イベントループに戻ったときに解放されてしまう
}

retainしないで代入した場合はautorelease poolが解放されるタイミングで解放されてしまいます。
感覚的にはfunc2メソッド内でしか使えないと思ったほうがいいです。

retainするpropertyにautoreleaseされたオブジェクトを代入する

最後にこの2つを合わせるとどうなるか。

@interface MyClass3 {
  SomeObject *_prop3;
}
@property (nonatomic, retain) SomeObject *_prop3;
@end
@implementation MyClass3
@synthesize _prop3;

  - (void) func3 {
    self._prop3 = [SomeClass getAutoreleaseObj]; //propertyのsetterでretainされる
    _prop3 = [[SomeClass getAutoreleaseObj] retain]; //直接メンバ変数にアクセスするので自分でretainする必要がある
  }
@end

プロパティ経由なら自分でretainを書いてはいけないし、直接メンバ変数に代入する場合はretainする必要があります。
個人的にはクラス内での代入は下のパターン(自分でretainを書く方法)の方が分かりやすいと思います。
とはいえselfを付けないとsetterを経由しない、ということを知らないと「なんでpropertyでretainしてるのにさらにretainする必要があるの??」と誤解する僕のような人がいるかもしれないのでまとめてみました。

Rails3でjQueryを使う方法

前回の記事の続きでjQueryも使えるようにする。

インストール

Gemfileにjquery-railsを追加

gem 'jquery-rails'

jQueryをインストール。(prototype.js関連のファイルが削除される)

$ bundle install
$ rails g jquery:install
      remove  public/javascripts/controls.js
      remove  public/javascripts/dragdrop.js
      remove  public/javascripts/effects.js
      remove  public/javascripts/prototype.js
      create  public/javascripts/jquery.min.js
      create  public/javascripts/jquery.js
    conflict  public/javascripts/rails.js
   Overwrite  public/javascripts/rails.js? (enter "h" for help) [Ynaqdh] y
       force  public/javascripts/rails.js

プロジェクト作成時にprototype.jsが不要だと分かっている場合は-Jで生成を抑止できる。

$ rails new APP_NAME -J

動作確認

views/samples/index.html.hamlに以下の記述を追加。

  3 %table#fade_table
(snip)
 19 :css
 20   #fade_table {
 21     display: none;
 22   }
 23 :javascript
 24   window.onLoad = (function(){
 25     $('#fade_table').fadeIn('slow');
 26   })();

これでサーバを再起動して/samplesにアクセスするとデータのtableがフェードインしながら表示されます。
内の読み込むjsもprototypeからjQueryに変わっています。

Rails3でRspecを使う方法

Haml、Sassを使えるようになったので今度はRspecを使えるようにします。

インストール

Rails3ではデフォルトでTest::Unit用のディレクトリやファイルが生成されるのですが、これは不要なのでプロジェクト作成時にTest::Unitを使わないようにします。

$ rails new rails3_rspec -T #-TでSkip Test::Unit

次にGemfileに必要なgemを記述します。
※同時にHaml、Sassを使う方法については以前の記事を参照のこと
Rails3とRuby1.9.2でHamlを使う方法
Rails3でSassを使う方法

gem 'haml-rails'

group :development, :test do
  gem 'rspec-rails', '>= 2.0.0.beta.20'
  gem 'autotest'
  gem 'webrat'
end

Gemfileができたらインストール

$ bundle install vendor/bundle
$ rails generate rspec:install

動作確認

動作確認のためにscaffoldで一通りのファイルを生成してテストを実行

$ rails generate scaffold sample
$ rake db:migrate
$ rake db:test:prepare #テスト用DBを作成
$ rake spec
...............**............
(省略)
29 examples, 0 failures, 2 pending

こんな感じでRspecが使えるようになります。
また、autotestを使うとファイルに変更があったときに自動でテストが走るようになります。

$ bundle exec autotest

Rails3でSassを使う方法

せっかくHamlを使えるようにしたのでSassも使えるようにしたいところ。
とはいってもSassはHamlに同梱されているので前回の記事の手順でインストールまでは既に済んでいます。

設定

config/initializers/sass_config.rbを作成して以下の記述を追加。

#デフォルトのスタイルシートパスを削除
Sass::Plugin.remove_template_location("./public/stylesheets/sass")
#app/stylesheetsをSass用のディレクトリに設定
Sass::Plugin.add_template_location("./app/stylesheets")
#最小サイズのCSSに変換する
Sass::Plugin.options[:style] = :compressed

デフォルトのパスでも問題ないのですが、Sassは.sassファイルから.cssファイルへコンパイルする仕組みのためソースファイルと生成されたCSSの場所を分離しておいたほうがスッキリすると思います。
またSassの変換結果のスタイルは以下のドキュメントに書かれていますので好きなものを設定してください。
http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#output_style

動作確認

Sass用のディレクトリとファイルを作成

$ mkdir app/stylesheets
$ vim app/stylesheets/sample.sass

以下の記述をsample.sassに追加

h1
  :font
    :size 200%
    :weight bold
  :border 16px solid #DD66CC
  :width 400px
  :padding 5px 
p
  :font-weight bold
  :border 16px solid #6666DD
  :width 400px
  :padding 5px 
$ rails server
$ open http://localhost:3000/sample/index

こんな画面が表示されたら動作確認OK。public/stylesheets/sample.cssが生成されているはず。
f:id:y_310:20100906005347p:image

Rails3とRuby1.9.2でHamlを使う方法

会社でRails3とHamlを使っているんですが、既に他の方が環境を作った上でコードを書いていたので、勉強のために一から環境構築をやってみました。

Rails環境構築

$ rvm 1.9.2
$ gem install rails
$ rails new rails3_haml
$ cd rails3_haml
$ bundle install vendor/bundle #必要なgemをvender/bundleに一括インストール
$ rails server

これでブラウザからhttp://localhost:3000にアクセスして動作していればひとまず最低限のRailsの環境構築はOK.

Hamlインストール

Gemfileにhaml-rails*1を追加。

gem 'haml-rails'

haml-railsはRails3にHaml Generatorを追加するためのプラグインです。
元々はrails3-generatorsだったものがHaml部分だけ分離したものらしいです。

もう一度bundle install

$ bundle install

これで準備は完了。
config/application.rbにg.template_engine :hamlのような記述を追加する必要はありません。

あとは試しにcontrollerをgenerateして動作確認してみましょう。

$ rails g controller sample index
      create  app/controllers/sample_controller.rb
       route  get "sample/index"
      invoke  haml     #template_engineがhamlに変わっている
       exist    app/views/sample
      create    app/views/sample/index.html.haml
      invoke  test_unit
      create    test/functional/sample_controller_test.rb
      invoke  helper
   identical    app/helpers/sample_helper.rb
      invoke    test_unit
   identical      test/unit/helpers/sample_helper_test.rb
$ rails server

これでhttp://localhost:3000/sample/indexにアクセスしてこんな画面が表示されればhamlが動作しています。
f:id:y_310:20100831005131p:image

*1:haml-railsを入れずにgem 'haml'だけでもHamlを使うことはできますが、rails gでファイルをgenerateする際にerbファイルが生成されてしまいます。これを手動で書き換えるか自分でファイルを作成する場合はhaml-railsは必要ありません。

やってみた

makeplex salon:あなたのスキルで飯は食えるか? 史上最大のコーディングスキル判定 (1/2) - ITmedia エンタープライズ
3時間でできなければ優秀でないことが証明されてしまうことで一部で話題になっている問題をやってみた。
結果は...



“ほぼ確実に”優秀ではない!orz

とりあえずコード晒しとく。Rubyで書いたのにやけに長い。
真面目にアルゴリズムの勉強しないとだめだなぁ。

Kotsu = 1
Shuntsu = 2
Any = 3
class Array
    def Array.deepcopy(arr)
        Marshal.load(Marshal.dump(arr))
    end

    def top
        self[self.length - 1]
    end

    def car
        if self.length > 0
            self[0]
        else
            nil
        end
    end

    def cdr
        new_a = Array.deepcopy(self)
        new_a.shift
        new_a
    end

    def rest(i)
        new_a = Array.deepcopy(self)
        new_a.delete_at(i)
        new_a
    end
end

class Machi
    def initialize
        @machi = nil
        @atama = nil
        @triple = []
    end
    attr_accessor :machi, :atama, :triple

    def eql?(m)
        self.hash == m.hash
    end

    def hash
        str = @triple.sort.join
        str += @atama.join if @atama
        str += @machi.join if @machi
        str.hash
    end

    def print
        @triple.sort!
        @triple.each{|t|
            STDOUT.print "(#{t.join})"
        }
        STDOUT.print "(#{@atama.join})" if @atama && @atama.length > 0
        STDOUT.print "[#{@machi.join}]" if @machi
        STDOUT.print "\n"
    end

    def deepcopy
        new_machi = Machi.new
        new_machi.machi = Array.deepcopy(self.machi)
        new_machi.atama = Array.deepcopy(self.atama)
        new_machi.triple = Array.deepcopy(self.triple)
        new_machi
    end
end

def check_four_tiles(n)
    $four_tiles.each{|t| return true if t == n}
    return false
end

def search_same(temp, rest)
    return {} if temp.length == 0
    v = temp[0]
    for i in 0...rest.length
        if v == rest[i]
            return {v => rest.rest(i)}
        end
    end
    return {}
end

def search_forward_backword(temp, rest)
    return {} if temp.length == 0
    if temp.length == 2
        return {} if temp[1] - temp[0] != 1
    end
    forward = temp.first - 1
    backward = temp.last + 1
    result = {}
    for i in 0...rest.length
        if rest[i] == forward || rest[i] == backward
            if !result.key?(rest[i])
                result[rest[i]] = rest.rest(i)
            end
        end
    end
    return result
end

def search_aida_machi(temp, rest)
    return {} if temp.length != 1
    forward = temp.first - 2
    backward = temp.last + 2
    result = {}
    for i in 0...rest.length
        if rest[i] == forward || rest[i] == backward
            if !result.key?(rest[i])
                result[rest[i]] = rest.rest(i)
            end
        end
    end
    return result
end

def get_machi(shuntsu2)
    return [] if shuntsu2.length != 2
    case shuntsu2[1] - shuntsu2[0]
    when 1
        ret = []
        ret << shuntsu2[0] - 1 if shuntsu2[0] > 1
        ret << shuntsu2[1] + 1 if shuntsu2[1] < 9
        return ret
    when 2
        return [shuntsu2[1] - 1]
    else
        return []
    end
end

def search(type, rest, temp, result)
    if rest.length == 0 && temp.length == 0
        $final_result << result
        return
    end
    case type
    when Kotsu
        search_same(temp, rest).each{|num, new_rest|
            new_result = result.deepcopy
            new_result.triple << (temp + [num])
            search(Any, new_rest, [], new_result)
        }
    when Shuntsu
        search_forward_backword(temp, rest).each{|num, new_rest|
            new_result = result.deepcopy
            new_result.triple << (temp + [num]).sort
            search(Any, new_rest, [], new_result)
        }
    when Any
        if temp.length == 0
            search(Any, rest.cdr, temp + [rest.car], result.deepcopy)
            if !result.atama && !result.machi && !check_four_tiles(rest.car) #一個待ちパターン
                new_result = result.deepcopy
                new_result.machi = [rest.car]
                new_result.atama = []
                search(Any, rest.cdr, [], new_result)
            end
        elsif temp.length == 1
            search_same(temp, rest).each{|num, new_rest|
                search(Kotsu, new_rest, temp + [num], result.deepcopy)
                if !result.atama #頭パターン
                    new_result = result.deepcopy
                    new_result.atama = temp + [num]
                    search(Any, new_rest, [], new_result)
                end
                if !result.machi && !check_four_tiles(num) #刻子待ちパターン
                    new_result = result.deepcopy
                    new_result.machi = temp + [num]
                    search(Any, new_rest, [], new_result)
                end
            }
            search_forward_backword(temp, rest).each{|num, new_rest|
                search(Shuntsu, new_rest, (temp + [num]).sort, result.deepcopy)
                exist_machi = false
                get_machi((temp + [num]).sort) .map{|m| !check_four_tiles(m)} .each{|m| exist_machi |= m}
                if !result.machi && exist_machi #順子待ちパターン
                    new_result = result.deepcopy
                    new_result.machi = (temp + [num]).sort
                    search(Any, new_rest, [], new_result)
                end
            }
            if !result.machi
                search_aida_machi(temp, rest).each{|num, new_rest|
                    exist_machi = false
                    get_machi((temp + [num]).sort) .map{|m| !check_four_tiles(m)} .each{|m| exist_machi |= m}
                    if exist_machi
                        new_result = result.deepcopy
                        new_result.machi = (temp + [num]).sort
                        search(Any, new_rest, [], new_result)
                    end
                }
            end
        end
    end
end

def check_number_of_every_tile(tiles)
    arr = []
    ret = []
    arr.fill(0, 1..9)
    tiles.each{|t| arr[t.to_i] += 1}
    for i in 1..9
        ret << i if arr[i] == 4
    end
    ret
end

$final_result = []
$four_tiles = []
input = [
    "1112224588899",
    "1122335556799",
    "1112223335559",
    "1223344888999",
    "1112345678999",
    "1113335558888",
    "1222233456788",
    "1223567789789"
]

input.each{|i|
    $final_result.clear
    $four_tiles.clear
    puts i
    nums = i.split(//).map{|n| n.to_i}.sort
    $four_tiles = check_number_of_every_tile(nums)
    search(Any, nums, [], Machi.new)
    $final_result.uniq.each{|r| r.print}
    puts
}

id<Protocol>と書くときの@protocolの宣言の方法

delegateをメンバに持つようなクラスを作るとき、delegateの型をidじゃなく特定のProtocolが実装されていることを指定したい時がある。そういう時はid delegate;と書けばいいんだけど、自作のProtocolだとrespondsToSelectorとかのNSObjectで定義されているメソッドが無い、と警告が出る。
調べてみたところ以下のページに解説があった。
id<Protocol>, Retain/Release and Protocol Inheritance | Mobile Orchard
Protocolの宣言時にNSObject Protocolを継承すればいいらしい。
つまり

@protocol MyProtocol <NSObject>
- (void)hoge;
@end

と書けば警告が出なくなる。