NSArray / NSMutableArray のメモリ管理

配列のメモリはちゃんと管理されてるか知りたかったんで確認がてらサンプル作って確かめてみました.

分かったことは以下のルールです.

  • 初期化 : initXXXXのメソッドで指定されたオブジェクトは全てretainされる. arrayXXXはautorelease.
  • 追加 : 追加されるオブジェクトがretainされる.
  • 削除 : 追加されるオブジェクトがreleaseされる.
  • 配列自体がdealloc : 内包しているオブジェクト全てがreleaseされる.

検証コード

NSObject *objA = [[NSObject alloc] init];

NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:objA, nil];
NSLog(@"after init     array; array retainCount:%d expect 1", [array retainCount]);
NSLog(@"after init     array; objA  retainCount:%d expect 2", [objA retainCount]);

NSObject *objB = [[NSObject alloc] init];
NSLog(@"after init      objB; objB  retainCount:%d expect 1", [objB retainCount]);
[array addObject:objB];
NSLog(@"after add       objB; objB  retainCount:%d expect 2", [objB retainCount]);
[array removeObject:objB];
NSLog(@"after remove    objB; objB  retainCount:%d expect 1", [objB retainCount]);

NSObject *objC = [[NSObject alloc] init];
[array addObject:objC];
NSLog(@"after add       objC; objC  retainCount:%d expect 2", [objC retainCount]);

[array release];
NSLog(@"after release  array; objA  retainCount:%d expect 1", [objA retainCount]);
NSLog(@"after release  array; objC  retainCount:%d expect 1", [objC retainCount]);


NSObject *objD = [[NSObject alloc] init];
NSArray *array2 = [[NSArray alloc] initWithObjects:objD, nil];  
NSLog(@"after init    array2; objD  retainCount:%d expect 2", [objD retainCount]);
[array2 release];
NSLog(@"after release array2; objD  retainCount:%d expect 1", [objD retainCount]);

結果

after init     array; array retainCount:1 expect 1
after init     array; objA  retainCount:2 expect 2
after init      objB; objB  retainCount:1 expect 1
after add       objB; objB  retainCount:2 expect 2
after remove    objB; objB  retainCount:1 expect 1
after add       objC; objC  retainCount:2 expect 2
after release  array; objA  retainCount:1 expect 1
after release  array; objC  retainCount:1 expect 1
after init    array2; objD  retainCount:2 expect 2
after release array2; objD  retainCount:1 expect 1


結果、予想通りの動きでした.

Safariの位置に関するプロパティまとめ

最近、Safari上でJavascriptを組むことが多く成ってきたのでコレを機に把握しきれていない位置関連のプロパティをまとめてみることにしました.

はじめに

  • 単位は全てpxで統一
  • 青字のプロパティはDOM(標準ではない)関連のプロパティ
  • 緑字のプロパティはCSS関連のプロパティ

CSS関連のプロパティには単位も含まれますがpxで統一しているので表記上省略しています.

DOM関連のプロパティはdocument.getElementById('id')等で取得したオブジェクトのプロパティです.

CSS関連の値の取得はdocument.defaultView.getComputedStyle(elem,null)で取得したオブジェクトのプロパティを使うと実際に表示されている値が取得できます.プロパティ名にはキャメルケース(LCC)で参照できます.

border,margin,padding関連

例えばdiv要素だとして以下のように覚えるとシンプル!

  • border含めた長さがoffsetXxxで取得(Width/Height)
  • border含めない長さがclientXxx又はscrollXxxで取得(Width/Height)
  • 上下左右のborderclientXxxで取得(Top/Right/Bottom/Left)

スクロール時の値

直下の子要素によって、挙動が違います.

どうして+4とか0とかになっているのかドキュメントが見つかりませんでしたが、事実計算方法が違うようです.試したのはimgとdivだけですが、他にも同様の事象があるとおもいます.

スクロールバーが無い場合はscrollWidth/scrollHeightは、それぞれclientWidth/clientHeightと同じくなります.

要素の位置

この例だとdiv要素の3つ重ねています.

例のoffsetTopの算出方法は以下のようになります、

offsetTop = 外側要素(pading-top) + 中間要素(margin-top + border-top + padding-top) + 内側要素(margin-top)
25 = 5 + 5 + 5 + 5 + 5


要素のborderの外側からpositionstatic以外の親要素のborderの内側との相対距離をoffsetTop/offsetLeftで取得できます.


基準となる親要素はoffsetParentで取得できます.

因にhtml要素と、body要素はposition:staticです.

Sinatra で content_for を使う.

最近、Sinatraを使い出しました. 凄く手軽です.

シンプルだけあってRailsにある機能が欲しいときが良くあります.

Railsのcontent_for

知らない人はググってください. でもまー、概要ぐらいは説明します.

RailsSinatraもテンプレートを外枠と内枠で分けることができます. でも内枠から外枠に値を渡したいときがあります。そんなときに役立つのがcontent_forです.

(例)タイトルを変えてみる

外枠のテンプレート「layout.rb」

<html>
<head><title><%= yield :title %></title></head>
<body>
<%= yield %>
</body>
</html>


内枠のテンプレート「index.rb」

<% content_for :title, 'よほほほ' %>
ブルックのステッカー買ったけど張る場所がない.


実行結果

<html>
<head><title>よほほほ</title></head>
<body>
ブルックのステッカー買ったけど張る場所がない.
</body>
</html>

こんな感じの機能です.

Sinatra での実現方法

では本題、Sinatraではcontent_forがありません.

手軽な方法としてはsinatraプラグインsinatra-content-forがあります.

でも今回はプラグインを使わずにやってみました.

以下のコードをapp.rbとかsinatraの実装ファイルから参照するなり、貼付けてください. それだけでcontent_forが使えます.

module Sinatra
  module Helpers
    def content_for(name, content=nil, &block)
      @content_for_param ||= {}
      content ||= block
      if content.nil?
        if @content_for_param.include?(name)
          @content_for_param[name].join
        else
          nil
        end
      else
        content = content.call if content.is_a? Proc
        content = content.dup if content.is_a? String
        @content_for_param[name] ||= []
        @content_for_param[name] << content
        nil
      end
    end
  end
  
  module Templates
    def render_with_content_for(engine, data, options={}, locals={}, &block)
      if block_given?
        interrupt_block = Proc.new { |name|
          if name.nil?
            block.call
          else
            content_for name
          end
        }
        render_without_content_for engine, data, options, locals, &interrupt_block 
      else
        render_without_content_for engine, data, options, locals
      end
    end
    alias_method :render_without_content_for, :render
    alias_method :render, :render_with_content_for
  end
end

っで使い方ですがRailsのcontent_forと全く同じってわけではないです. blockを使った表記がerbのプリコンパイルが期待通りに成ってなかったのでちょっと工夫する必要があります.

本来なら以下のように書けると思ったのですが

<% content_for(:title) do %>
FooBar
<% end %>

プリコンパイルを見てみると以下のような感じで内枠の文字列として追加されちゃってますorz

content_for(:title) do ;
@_out_buf.concat "FooBar";
end;

そこで工夫してあげると.以下の書き方で回避できました.

<% content_for(:title){
  "FooBar"
} %>

ちなみに以下のようにも書けます.

<% content_for(:title, "FooBar") %>

さらに同じ名前で数回コールすると結合されるようにしました。

<% content_for(:title, "FooBar") %>
<% content_for(:title, "Boo") %>
=> FooBarBoo

[Life] IMO for iPhone まとめ Wiki の検索自動リンクbookmarklet

http://gist.github.com/472351

iPhone上で確認するとき、メンドクサイのでbookmarklet化してみました.

githubに上げたのでご自由にどうぞ.

bookmarklet登録方法

  1. http://gist.github.com/472351 を開く
  2. auto_search_link.bookmarklet.jsのrawをクリックしてファイルを表示します.
  3. 全選択してソースをコピー
  4. ブックマークする. 名前は「AutoSearchLink」とかにする.
  5. とりあえず「保存」ボタンで完了する.
  6. ブックマークから「編集」ボタンを押す
  7. 先ほど保存した「AutoSearchLink」を編集する.
  8. URLを入れる場所にコピーしたソースを貼付けて完了する.

使い方

IMO for iPhoneまとめ Wiki* の適当なページでブックマークの「AutoSearchLink」を開くと自動的に検索リンクが張られます.

リンクが邪魔な場合は、リロードすれば無く成ります.

コマンドラインでHTTPレスポンスを確認する

HTTPのレスポンスヘッダ情報が期待通りか確認する際に使ったのでメモ

wgetの場合

wget --server-response --spider [URL]
または
wget -S --spider [URL]

実行すると、ファイルをダウンロードせずにレスポンス情報が表示されます.

$ wget --server-response --spider http://127.0.0.1:4567/offline/cache.manifest
スパイダーモードが有効です。リモートファイルが存在してるか確認します。
--2010-07-07 17:57:28--  http://127.0.0.1:4567/offline/cache.manifest
127.0.0.1:4567 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 
  HTTP/1.1 200 OK 
  Last-Modified: Wed, 07 Jul 2010 08:43:02 GMT
  Connection: Keep-Alive
  Content-Type: text/cache-manifest
  Date: Wed, 07 Jul 2010 08:57:28 GMT
  Server: WEBrick/1.3.1 (Ruby/1.8.7/2010-01-10)
  Content-Length: 61
長さ: 61 [text/cache-manifest]
リモートファイルが存在します。

curlの場合

curl --verbose [URL]
または
curl -v [URL]

実行すると、以下の情報が画面にでます.
wgetより分かりやすいかも!

$ curl --verbose  http://127.0.0.1:4567/offline/cache.manifest
* About to connect() to 127.0.0.1 port 4567 (#0)
*   Trying 127.0.0.1... connected
* Connected to 127.0.0.1 (127.0.0.1) port 4567 (#0)
> GET /offline/cache.manifest HTTP/1.1
> User-Agent: curl/7.20.0 (i386-apple-darwin10.3.0) libcurl/7.20.0 OpenSSL/0.9.8n zlib/1.2.5 libidn/1.18
> Host: 127.0.0.1:4567
> Accept: */*
> 
< HTTP/1.1 200 OK 
< Last-Modified: Wed, 07 Jul 2010 08:43:02 GMT
< Connection: Keep-Alive
< Content-Type: text/cache-manifest
< Date: Wed, 07 Jul 2010 09:00:14 GMT
< Server: WEBrick/1.3.1 (Ruby/1.8.7/2010-01-10)
< Content-Length: 61
< 
CACHE MANIFEST

stylesheets/msafari.css

offline/editor.html
* Connection #0 to host 127.0.0.1 left intact
* Closing connection #0

[Javascript]jQueryで二度押し防止対策のプラグイン

そんなに難しくないので自分で書いてみた.
Gistに公開しているのでご自由にどうぞ.

jquery.singleclick.js · GitHub

(function($) {
  var lockKey = 'clickLockedId';
  $.fn.releaseClickLocked = function(){
    var tid = this.data(lockKey);
    if(tid!==undefined){
      clearTimeout(tid);
    }
    $(this).removeData(lockKey);
  };
  $.fn.singleClick = function(handler, timeout) {
    timeout = timeout===undefined ? 3000:timeout;
    $(this).click(function(evt){
      var self = $(this);
      if(self.data(lockKey)!==undefined){
        return false;
      }
      var tid = setTimeout(function(){
        self.releaseClickLocked();
      },timeout);
      self.data(lockKey, tid);
      return handler.apply(this,[evt]);
    });
  };
})(jQuery);

使い方

以下はリンクをクリックしまくった時の挙動がコメントで説明しています.

// 3秒間に1度だけコールされます.
$('a.sample1').singleClick(function(evt){
  alert('foo');
});

// 1秒間に1度だけコールされます.
$('a.sample2').singleClick(function(evt){
  alert('foo');
}, 1000);

// releaseClickLockedはタイムアウトを待たずにロックを解除します.
$('a.sample3').singleClick(function(evt){
  var self = $(this);
  setTimeout(function(){
    self.releaseClickLocked();
  },1000);
});

[Ruby] Railsのエラー管理ツールのHoptoadを使ってみた

Railsのエラー管理がラクに出来るらしいので試しに設定してみました.

この手のツールは見てて楽しいので. 苦になりやすいエラー対応も気分良く出来そう.

導入方法やハマったこと等をまとめてみました.

Hoptoadとは

Hoptoadの仕組みは、Railsの例外等をキャッチしたらHoptoadのサイトに通知して見やすくしてくれる.

プラグインとして用意されているので導入は結構簡単.

アカウント登録

試してみるだけなので迷わずタダで使えるプランで!と見てみると

「Try Hoptoad FREE for 30 days」(お試し30日無料!)


なんて出てて無料プランが無いのか?と勘違いしました.

よーーく見ると下の方に

「Egg Plan for Free」(エッグプランは無料ですよ)

と小さく目立たずに書いてある。

あとは適当に入力項目を埋めていって「Create Account」

プロジェクト登録とRailsの設定

ログインしてプロジェクトを追加すると、Railsへのセットアップ方法がかかれています.

1. Remove your existing Exception Notifier from your ApplicationController(ExceptionNotifiableを使ってる場合は消してね):
class ApplicationController < ActionController::Base
   <del>include ExceptionNotifiable</del> 
2. Add the hoptoad_notifier gem to config/environment.rb(config/environment.rb に gem hoptoad_notifierを追加):
config.gem 'hoptoad_notifier
3. Install the gem(rakeコマンドでインストール):
rake gems:install
4. Unpack the gem(gemをアンパック、vendor/plugins以下に配置されるだけなのでやらなくてもOK):
rake gems:unpack GEM=hoptoad_notifier
5. Configure the notifier(設定ファイルの生成):
script/generate hoptoad --api-key apikey123apikey123apikey123apikey123
6. Look for the exception to appear in your errors list(例外はHoptoadのエラーリストを見えると表示されるよー).

準備がこれで完了しました. あとは実際に通知をさせればHoptoadにエラーリストが表示されます.

エラーが見れるかテスト

プロジェクトルートで以下のコマンドを実行

rake hoptoad:test
$ rake hoptoad:test
(in /Users/satoruk/Developments/foo)
** [Hoptoad] Notifier 2.3.0 ready to catch errors
Configuration:
        http_open_timeout: 2
               proxy_host: nil
             notifier_url: "http://hoptoadapp.com"
                     port: 80
               proxy_user: nil
        http_read_timeout: 5
                  api_key: "apikey123apikey123apikey123apikey123"
         notifier_version: "2.3.0"
                   ignore: ["ActiveRecord::RecordNotFound", "ActionController::Rou
 development_environments: []
           params_filters: ["password", "password_confirmation"]
               proxy_pass: nil
        ignore_by_filters: []
                   secure: false
                framework: "Rails: 2.3.8"
             project_root: "/Users/satoruk/Developments/foo"
        backtrace_filters: [#<Proc:0x00000001017033c8@/opt/local/lib/ruby/gems/1.8
               proxy_port: nil
        ignore_user_agent: []
                 protocol: "http"
                     host: "hoptoadapp.com"
       development_lookup: true
            notifier_name: "Hoptoad Notifier"
         environment_name: "development"
Setting up the Controller.
Processing request.
Raising 'HoptoadTestingException' to simulate application failure.
Sending request to http://hoptoadapp.com/notifier_api/v2/notices/:
XMLのコードがずらりと
** [Hoptoad] Success: Net::HTTPOK
** [Hoptoad] Environment Info: [Ruby: 1.8.7] [Rails: 2.3.8] [Env: development]
** [Hoptoad] Response from Hoptoad: 
<?xml version="1.0" encoding="UTF-8"?>
<notice>
  <error-id type="integer">1986027</error-id>
  <url>http://bar.hoptoadapp.com/errors/0000000/notices/00000000</url>
  <id type="integer">000000</id>
</notice>

Rendered layouts/_head (2.6ms)
Rendered layouts/_groval_header (1.5ms)
Rendered layouts/_copyright (0.8ms)
Rendered layouts/_footer (7.6ms)

っでHoptoad上で見てみるとエラーがでてますと.

各エラーはUnresolvedとResolvedのステータスが変えられるのでエラーを調べ終わったらResolvedにしてく感じです.

ハマったところ

このまえのエントリーのエラー処理でrescue_action_in_publicを上書きしてたので、Hoptoadが動きませんでした. コードを読むとHoptoadはrescue_action_in_publicを利用しているので、以下のような回避策が必要です.

def rescue_action_in_public(exception) #:doc:
  unless hoptoad_ignore_user_agent?
    HoptoadNotifier.notify_or_ignore(exception, hoptoad_request_data)
  end
  # :
  # :
  # :
end

Hoptoadがまともに動いてなかったとき、「rake hoptoad:test」の結果に以下のような内容があります. 

Raising 'HoptoadTestingException' to simulate application failure.