[Ruby] NSURLRequestとか
新たな問題
最初にアイテムを追加する時はアイコンをいわゆるスピナーにしておいて、非同期に画像を読みにいって、順次入れかえていくという挙動を実現してみました*1
最初にやったのは、Ruby側でThreadを立てておいて、アイテムを追加する度にQueueを経由でそのスレッドに画像読みこみタスクを渡すという方法です。
これで、カートを読みにいく動作がアイコンの読み込みでブロックされる事もなくなり、スピードアップするだろうと期待したのですが、なんだか返って遅くなったような気が…
測ってみると、ダウンロードがメインスレッドで実行してた時の10倍くらい遅くなってます。
家のネットワークでは、以下のページを最後まで読むのに1.7秒くらい、それから画像のURLを抜きだして画像そのものを読むのに0.4秒くらいかかります。
http://www.amazon.co.jp/gp/aw/d.html?a=4798023809
ところが、別スレッドで動かすとそれが18秒と4秒くらいになってしまったのです。
NSURLRequest
ネットでいろいろ調べると、そもそもRubyCocoaのアプリでTreadを使ってGUIをアップデートするのはお勧めではないらしい。
http://www.unfitforprint.com/articles/2008/02/14/working-with-threads-in-rubycocoa
を見ると"never call anything GUI-related from a separate thread,"とか書いてあるし…
バックグラウンドでダウンロードしたい時はNSURLConnectionがお勧めらしい。
MacRubyのチュートリアルに丁度NSURLConnectionを使ったコードがあったので、それを丸ごと真似して以下のようにやってみました。
class DataRequest < OSX::NSObject include OSX def get(url, &blk) @buf = NSMutableData.new @blk = blk req = NSURLRequest.requestWithURL(NSURL.URLWithString(url)) NSURLConnection.alloc.initWithRequest_delegate_(req, self) end def connection_didReceiveResponse(conn, resp) @buf.setLength(0) end def connection_didReceiveData(conn, data) @buf.appendData(data) end def connection_didFailWithError(conn, err) NSLog "Request failed" end def connectionDidFinishLoading(conn) @blk.call(@buf) end end
これを以下の様に使います。
def get(url, &blk) DataRequest.new.get(url, &blk) end def set_icon_async(asin) if(self.smallimage) self.icon = image(self.smallimage) else self.icon = SPINNER get("http://www.amazon.co.jp/gp/aw/d.html?a=#{asin}") do |data| body = NSString.alloc.initWithData_encoding_(data, NSShiftJISStringEncoding) if(/<img src="([^\"]+\.jpg)"/ =~ body) get($1) do |data| self.smallimage = data.bytes.bytestr(data.length) self.icon = NSImage.alloc.initWithData(data) self.smallimage = nil if self.icon.nil? end end end end end
getメソッド自体はHTTPのリクエストを出すだけだしてすぐ終了します。そしてダウンロードが終ったところで、元のスレッド上で指定ブロックが呼ばれるという動作です。
試してみたところ2秒ほどでダウンロードが終るようになりました。
画像のURLはの割と最初の方に記述されているので、URLを見付けたらそこで読みこみを打ち切って、次に進むようにすればさらに時間を短縮できるかもしれません。