<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 xmlns="http://purl.org/rss/1.0/"
 xmlns:content="http://purl.org/rss/1.0/modules/content/"
 xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/"
 xmlns:dc="http://purl.org/dc/elements/1.1/"
 xmlns:syn="http://purl.org/rss/1.0/modules/syndication/"
 xmlns:admin="http://webns.net/mvcb/"
 xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel rdf:about="http://blog.livedoor.jp/faulist/">
<title>As Sloth As Possible</title>
<link>http://blog.livedoor.jp/faulist/</link>
<description>可能な限りナマケモノでありたい
</description>
<dc:language>ja</dc:language>
<admin:generatorAgent rdf:resource="http://blog.livedoor.com/?v=2.0" />
<atom:link rel="hub" href="http://pubsubhubbub.appspot.com" />
<items>
 <rdf:Seq>
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1762587.html" />
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1740704.html" />
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1735593.html" />
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1735038.html" />
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1732537.html" />
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1649478.html" />
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1606073.html" />
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1561677.html" />
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1561287.html" />
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1536624.html" />
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1536261.html" />
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1527532.html" />
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1522297.html" />
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1511657.html" />
  <rdf:li rdf:resource="http://blog.livedoor.jp/faulist/archives/1487948.html" />
 </rdf:Seq>
</items>
</channel>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1762587.html">
<title>iOS Advent Calendar 2011 5日目 / NSURLProtocolの使い方</title>
<link>http://blog.livedoor.jp/faulist/archives/1762587.html</link>
<description>どうも、「iOS Advent Calendar 2011」5日目担当のfaultierです。つい最近使ったのでNSURLProtocolネタで。

NSURLProtocolって何？

Foundationフレームワークで最初から扱えるプロトコルはhttp、https、ftp、fileの4つ。これ以外のプロトコルでの通信をNSURLConnectionやNS...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2011-12-05T16:39:41+09:00</dc:date>
<dc:subject>プログラミング</dc:subject>
<content:encoded><![CDATA[<p>どうも、「<a href="http://atnd.org/events/22792" title="iOS Advent Calendar 2011">iOS Advent Calendar 2011</a>」5日目担当のfaultierです。つい最近使ったのでNSURLProtocolネタで。</p>

<h3>NSURLProtocolって何？</h3>

<p>Foundationフレームワークで最初から扱えるプロトコルはhttp、https、ftp、fileの4つ。これ以外のプロトコルでの通信をNSURLConnectionやNSURLDownloadなどで扱う場合や、特定のリクエストに限って特別な処理をしたい場合などに、NSURLProtocolを継承して登録することで使えるようになる。ちなみに、他のアプリからopenURLしたときにアプリを起動させるカスタムURLスキームとはまた別なので注意。こちらはアプリ内でURL Loading Systemを使うときにだけ影響するもの。</p>

<h3>使い方</h3>

<p>最低限必要なのは、+canInitWithRequest:、+canonicalRequestForRequest:、-startLoading、-stopLoadingの4つ。</p>

<p>まずはこんな感じで、どんなリクエストのときにそのURLProtocolをが処理するのかを決める。</p>

<pre class="prettyprint">
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    return [[[request URL] scheme] isEqualToString:@"udon"];
}
</pre>

<p>ここでYESを返すとこのクラスがインスタンス化されて通信処理に進む。NOの場合は他に登録されているURLProtocolのこのメソッドが登録時の逆順に呼ばれて行く。この場合はudonというスキーム、例えばudon://marukame/bukkake/coolみたいなURLへのリクエストの時に処理をすることになる。別にこれは独自のスキームである必要ではなく、httpやfileなどをフックすることもできるし、特定のホストや特殊なヘッダが付いている時だけ処理するようなこともできる。</p>

<p>次は+canonicalRequestForRequest:。リクエストをcanonicalな形に変える必要がある場合はここで弄るのだけど、特に何もすることが無ければrequestをそのまま返してやればいい。</p>

<pre class="prettyprint">
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
    return request;
}
</pre>

<p>その後実際の通信が始まり、-startLoadingが呼ばれる。NSURLProtocolのオブジェクトは、id&lt;NSURLProtocolClient&gt;のclientと、NSURLRequestのrequestというプロパティを持っているので、requestからどんなリソースが必要なのかを判断して、clientに対してデータを返してやる、という形で通信の中継をする。NSURLProtocolClientのメソッドは大体NSURLConnectionのdelegateと対応しているので、NSURLConnectionでの通信を実装したことがあれば分かるはず。NSURLConnection側でキャンセルされたときはstopLoadingが呼ばれる。簡単な例としては以下のようになる。</p>

<pre class="prettyprint">
- (void)startLoading
{
    // 本来非同期で通信するのだけど、
    // この例では単に文字列データを返すだけなので、
    // その場で返してしまう
    NSData *data = [@"うどんが食べたい" dataUsingEncoding:NSUTF8StringEncoding];
    NSDictionary *headers = [NSDictionary dictionaryWithObjectsAndKeys:
                              @"text/plain", @"Content-Type",
                              [NSString stringWithFormat:@"%d", [data length]], @"Content-Length",
                              nil];
    NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:[self.request URL]
                                                              statusCode:200
                                                             HTTPVersion:@"1.1"
                                                            headerFields:headers];
    // NSURLResponseのオブジェクトを返し、
    [self.client URLProtocol:self
          didReceiveResponse:response
          cacheStoragePolicy:NSURLCacheStorageAllowedInMemoryOnly];
    // データを渡し、
    [self.client URLProtocol:self didLoadData:data];
    // 通信を終了。
    // 通信失敗の場合は -URLProtocol:didFailWithError:を呼ぶ。
    [self.client URLProtocolDidFinishLoading:self];
}

- (void)stopLoading
{
    // この例ではキャンセルしようが無い…
    // 内部で別なNSURLConnectionを使っていたり、
    // NSOperationQueueやGCDなどで非同期処理をしている場合、
    // ここでキャンセルの処理を実行する。
}
</pre>

<p>これだけだとudonスキームはまだURL Loading Systemに登録されていないので、適宜NSURLProtocolの+registerClass:、+unregisterClass:呼んであげることで使えるようになる。その独自スキームを使うUIViewControllerとか、あるいはもうクラスのloadメソッドで登録してしまうとか。</p>

<pre class="prettyprint">
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&amp;onceToken, ^{
        [NSURLProtocol registerClass:[self class]];
    });
}
</pre>

<h3>使いどころ</h3>

<p>それどういうときに使うの？http以外のプロトコルとかそんな実装しないよ？と思うかもしれないけど、意外と使い道がある。UIWebView等での通信は裏側でNSURLConnectionを使っているので、WebView内での通信をフックしてアプリ側で弄ったりできるのだ。例えばCoreDataにあるデータをJSON形式にして返すようにしておくとか、JSからのトリガーでアプリ内のバックグラウンド処理を走らせるとか、画像をファイルキャッシュしておいてオフラインでも表示するとか、複数の異なる形式のWebAPIをプロキシして同じ形で扱えるようにしたり、ということも簡単にできる。</p>

<p>また、逆に「特定のリクエストに限って特別な処理をする」ではなく「特定のリクエストはスルーするけど他はブロックする」のような使い方もできる。最近（というには結構前からだけど）話題になったのは、iPhoneのWebViewにこういう脆弱性がある、という話。</p>

<ul>
<li><a href="http://iphone-dev.g.hatena.ne.jp/laiso/20111003/1317651353">UIWebView の取り扱いによってはjavascript経由でサンドボックス外のファイルシステムにアクセスできてしまうという話と対処法</a></li>
</ul>


<p>何故デバイスのアドレス帳や着信履歴みたいなのの生のデータにJSからアクセスできる必要があるのか良く分からないし、おかしな仕様だと思うのでバージョンアップで塞がれるだろうとは思うけど、少なくともiOS5.0まではこの問題は残っているので、「アプリ内でUIWebViewにloadHTMLStringさせて、外部から取得したHTMLをfile://等のスキームで表示させている」ような場合には対処する必要がある。簡単な対策としてはきちんとbaseURLを設定しておけばいいのだけど、そうすると今度はCSS、JS、画像などのバンドル内部に持っているリソースが使えなくなる。「外部から取得したHTMLをアプリ内のUIWebViewで表示したいけど、アプリ内の安全なリソースにはアクセスさせたい」場合に、NSURLProtocolで通信をフックする仕組みが使える。</p>

<ul>
<li><a href="http://sklave.jp/logs/2011/10/4/%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%AB%E3%81%AFnsurlprotocol%E3%82%92%E4%BD%BF%E3%81%88%E3%81%B0%E8%89%AF%E3%81%84%E3%81%AE%E3%81%98%E3%82%83%E3%81%AA%E3%81%84%E3%81%8B">カスタムリソースにはNSURLProtocolを使えば良いのじゃないか？</a></li>
<li><a href="http://iphone-dev.g.hatena.ne.jp/laiso/20111130/1322649990">NSURLProtocol を定義してUIWebView で安全にローカルのリソースを読み込む</a></li>
</ul>


<p>前者の記事だと、NSURLProtocolのサブクラスでバンドル内のファイルを開いて、そのデータを返してしまう、というやり方をしている。また、後者の記事やPhoneGapの実装では、NSURLProtocolの「登録時の逆順に対応できるかどうかチェックする」「YESを返せばそこで止まり、NOを返すと次のNSURLProtocolのチェックに進む」という性質を利用して、「<strong>ホワイトリストに載ってないリクエストの場合は、canInitWithRequest:でYESを返した上でエラーなり空レスポンスなりを返し、ホワイトリストに載っている場合はNOを返して通常のhttpやfileプロトコルとして処理させる</strong>」という方法でサンドボックス外のリソースにアクセスさせないようにしている。</p>

<h3>まとめ</h3>

<ul>
<li>NSURLProtocolを使えばさくっと独自スキームを定義できるよ</li>
<li>WebViewみたいに細かく制御しづらいものの内部の通信もフックできるよ</li>
<li>セキュリティ面でも意味があるよ</li>
</ul>


<p>という感じなので、是非試してみてください。</p>
<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1762587" width="1" height="1" />
]]>
</content:encoded>
</item>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1740704.html">
<title>OPERA実験が気になる</title>
<link>http://blog.livedoor.jp/faulist/archives/1740704.html</link>
<description>
件の「ニュー速実験」ことOPERA実験のニュースがなかなか面白そうで気になる。ここんとこシュタゲにハマりまくってたタイミングで「CERNが！光速を！超えた！」なんてニュースが来たら、これはwktkせざるを得ませんなﾄﾞｩﾌﾌ、なんだけど、まぁそれを差し引いても驚くべき話で...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2011-09-25T15:01:23+09:00</dc:date>
<dc:subject>趣味</dc:subject>
<content:encoded><![CDATA[<p>
件の「ニュー速実験」ことOPERA実験のニュースがなかなか面白そうで気になる。ここんとこシュタゲにハマりまくってたタイミングで「CERNが！光速を！超えた！」なんてニュースが来たら、これはwktkせざるを得ませんなﾄﾞｩﾌﾌ、なんだけど、まぁそれを差し引いても驚くべき話ではある。
</p>
<p>
OPERA実験ってなんだったのかなーと思って色々見てみたのだけど、ええと、「スイスからイタリアに向けて素粒子ニュートリノを打ったら、どうやら光速を超える速度で到達しているらしい。精密に測定してるはずだけど、計測誤差の範囲を超える数値で"速い"ようだし、統計的に十分な回数試行しても再現する。なんぞこれ」っていう話だったという理解でいいのだろうか。ふむ、なんか凄そうだ。もうちょっとキーワードを追ってみる。
</p>
<ul>
<li>ニュートリノ、と言えば、小柴先生が史上初めて自然に発生したニュートリノを観測してノーベル物理学賞を受賞しているわけだけれど、その小柴先生のチームは超新星爆発の際発生したニュートリノは可視光とほぼ同じ速さで到達していることを観測していて、今回のOPERA実験とは矛盾する。</li>
<li>正の質量を持つ物質をどれだけ加速させても、理論上光速を超える速度は出ないとされている。今までにそういう物質や現象は確認されていない。</li>
<li>特殊相対論に反しない形で、虚数の質量を持ち、エネルギーを失なえば失なうほど減速し、どんなに減速しても常に超光速であり光速以下にならないタキオンの存在は理論上仮定されているが、実験によって観測された例はいまのところ無い。</li>
<li>ニュートリノは質量を持つことが実証されているので、質量0や虚数質量の粒子ではない。件のOPERA実験チームもニュートリノ振動を観測するための研究をしているらしい。</li>
</ul>
<p>
みたいな。うーん。「とりあえず他の研究機関が同様の実験をしてみるまでは結論保留で、あと他の方法でこの現象を確認する良いアイディアあったらよろ」って段階っぽい。そりゃそうだよなぁ。素人目にも、少なくとも「アインシュタインの相対論は間違っていた！」とか「CERNはタイムマシンを開発しようとしている！」みたいな報道は早計すぎんだろと思う。「これでタイムマシンも作れちゃいますねー」なんて、コメント求められた専門家が言ったんだとしても冗談半分のリップサービスか、聞いてきた記者が理解できなくてセンセーショナルなところだけ抜き出して書いちゃった感。もちろんそれでも面白そうな話題であることに違いは無いんだけども。
</p>
<p>
それにしても折角面白そうな話題なのに解説読んでも半分も理解できないの寂しい。一体どこから勉強しなおせばちゃんと理解できるんだろ。それこそタイムマシンでも使って高校生くらいからやり直さないとだめかしら。誰か相対論とか場の量子論が何言ってるか分かるようになるまでに何が必要か教えてくだしあ。
</p>
<p>
あと全然関係ないけど「Operaは光速より速いブラウザ」って誰かが言い出す、あるいはもう言ってるに76円28銭賭ける。
</p>
<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1740704" width="1" height="1" />
]]>
</content:encoded>
</item>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1735593.html">
<title>git flowを使ってみることにした</title>
<link>http://blog.livedoor.jp/faulist/archives/1735593.html</link>
<description>
ふと思い立ってA successful Git branching modelとかを今頃読んでみたり。


最近のお仕事、基本的に一人でiPhone/Androidアプリを作ってて同じコードを複数人で弄ることがなくて、しかも大体の案件が一月くらいかけて一から作るとか丸ごと作り直すとかそういう感じなので...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2011-09-08T00:57:41+09:00</dc:date>
<dc:subject>プログラミング</dc:subject>
<content:encoded><![CDATA[<p>
ふと思い立って<a href="http://keijinsonyaban.blogspot.com/2010/10/successful-git-branching-model.html" title="見えないチカラ: A successful Git branching model を翻訳しました">A successful Git branching model</a>とかを今頃読んでみたり。
</p>
<p>
最近のお仕事、基本的に一人でiPhone/Androidアプリを作ってて同じコードを複数人で弄ることがなくて、しかも大体の案件が一月くらいかけて一から作るとか丸ごと作り直すとかそういう感じなので、割と開発プロセスとか考えずにフリーダムにやっちゃってるので、まぁあまりよろしくないなと反省をしている。かといってgitの機能を隅から隅まで使いこなしてるとか自分にとって最高にやりやすい状況になってるわけでもなく、むしろあんまり開発サイクルが上手く回ってなくて、でも人にすぐ渡せる状態かというとそうでもなく、というわけで意識的になんかの約束事に乗ってみることにした。
</p>
<p>
で。ついでにgitconfigいじってたりああそう言えばと思い出してzshrcいじったりしてたら日が暮れてた。開発環境整備すんのなんであんな楽しいんだろな。やりすぎ良くない。
</p>
<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1735593" width="1" height="1" />
]]>
</content:encoded>
</item>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1735038.html">
<title>ちょっと見てて面白かったので</title>
<link>http://blog.livedoor.jp/faulist/archives/1735038.html</link>
<description>
なんか金縛りにあって眠れなくなったのでTwitter見てたら面白いやりとりをみかけた。すげー大雑把に要約すると、こんな感じ。


Railsの勉強をしてる人が「なんでHello worldしたいだけなのにDB必要なの！？DB使うにしたってMySQLなんか今必要じゃないしチュートリアルではS...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2011-09-06T03:24:59+09:00</dc:date>
<dc:subject>意見・主張</dc:subject>
<content:encoded><![CDATA[<p>
なんか金縛りにあって眠れなくなったのでTwitter見てたら面白いやりとりをみかけた。すげー大雑把に要約すると、こんな感じ。
</p>
<ol>
<li>Railsの勉強をしてる人が「なんでHello worldしたいだけなのにDB必要なの！？DB使うにしたってMySQLなんか今必要じゃないしチュートリアルではSQLiteとかにしといてくれたっていいのに！プリプリ！」みたいな呟きをする</li>
<li>「いやRails使うんなら普通DB要るでしょ。なんでそこに文句言ってるの。」とツッコミが入る</li>
<li>「とりあえずチュートリアルは一番簡単なやり方書いといてくれた方が親切じゃないですか。」と反論</li>
<li>「いやだから、Railsを使うのに一番当たり前の構成を作るのにはどうしたらいいかがチュートリアルでしょ。それが必要ないんなら別なの、例えばSinatraとか使えばいいじゃん。フレームワークの特性も分からずに使っておいてチュートリアルがおかしいとか筋違いだろ。」と再度ツッコミ</li>
<li>「Railsがどんな特性のフレームワークとか知らないから勉強してるのに…最初っからそういうの全部考えてやれって言われても…僕はただRailsを手っ取り早く学ぶ手段があればいいのにって思っただけなのに…」みたいに凹んでしまう</li>
</ol>
<p>
ってな感じ。んで、それと隣接して外野で「最初は知らないことだらけなんだからおまじないだって言っておけばいいじゃん、フレームワークの特性がーとか大鑑巨砲主義がーとかどうでもいいだろ、やってみなきゃ分かんないことだってあんじゃん」「いや不要なとこに不要なもの使おうとしてるから適切なのはこっちだって教えてやっただけだろ、なんでわざわざ面倒なことやろうとしてるのにフレームワークに文句付けてんだよ」みたいな論争が若干生じてたりとか。
</p>
<p>
なんちゅーかね。この件に関して言えば、「それRailsに文句付けるとこじゃねーよ」って指摘は妥当だとは思う。Railsは「中規模のアプリケーションを、一般的な構成で」作るときに楽になるように設計されてるものなので、それを知らないまま進むと大概後で苦労する（レールに乗るから楽なんであって、レールから外れると荒野だ。今は大分良いけど、昔は黒魔術に手を染めないとレールから外れることすらできなかったんだよ！）。
<p>
そんなの知らないから勉強してるんだろ、それは後から考えさせろって？いや、WAFの選択って、「どんな目的に何を使うか」も重要なことなので、何かWAFを学ぶってときにはそういう選択の力も付ける必要はあると思うよ。WAF一般について知りたいんじゃないんだ、Railsについて勉強してるんだって？だったらとりあえずRailsの流儀に沿っておきなよ、何か意味があるからそういう構成になってるんだよ。分からないなら文句付けてないで、何でそうなってるか考えようよ。
</p>
<p>
少しまとめる。
</p>
<ul>
<li>何かにとりかかろうとして、チュートリアルの時点で「なんでこんなことせにゃならんのだ？」って思うなら、「自分がやろうとしてることに適切じゃないものを使おうとしている」か、「今取り組んでいることを正しく理解できてない」可能性を先に疑った方がいい。大概の場合は「あー俺が間違えてたわー」ってなるか「あーこういうことだったのかー」って後で分かる。まぁ本当に提供側が不親切だったりいい加減だったりすることもままあるけど、それにつっこめるのはある程度使い熟せてからだ。</li>
<li>教える側は「これはおまじないだから、そういうことだって思ってやりなさい」を安易に使うべきじゃない。何にか躓いてるときは、何が間違ってて何が理解できてないのか把握する・させるチャンスなので、そのタイミングで教えてあげればいいのにと思う。あんまり深入りさせてなかなか最初のステップに到達しないのはアレだけど、「おまじないだからとりあえず無視して」って言っちゃうとほんの少し潜ってみる足掛かりすら失なう。</li>
<li>でも、分からないことを責めるな、とは思う。「うぇぶかいはつしゃもすなるれいるずをしてみんとてするなり」みたいな人に「お前はゴキブリ退治に第七艦隊を呼ぶのか？」みたいなツッコミ入れたってそもそも分かってなかったからそんなことしてるんでしょと。単に教えてあげればいいじゃない。</li>
</ul>
<p>
どうでもいいけどRailsは「最初の一歩」には丸っきり向いてないと思うんだけど、なんでみんなRailsやりたがるし教えたがるんだろね。不思議。
</p>
<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1735038" width="1" height="1" />
]]>
</content:encoded>
</item>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1732537.html">
<title>#isucon で学ぶWebアプリの高速化の話</title>
<link>http://blog.livedoor.jp/faulist/archives/1732537.html</link>
<description>あるいは、お遊びチーム2号は一体何をしていたのかについて。

ISUCONという大変白熱した楽しいお祭を開催するにあたって、その前夜祭的な環境試験のためのチューニング祭が社内の有志数名で行われていて、そのときに色々学んだことをおまけとして書いておきます。

ISUCONて...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2011-08-28T19:13:27+09:00</dc:date>
<dc:subject>レポート・レビュー</dc:subject>
<content:encoded><![CDATA[<p>あるいは、<strong>お遊びチーム2号は一体何をしていたのかについて</strong>。</p>

<p>ISUCONという大変白熱した楽しいお祭を開催するにあたって、その<del>前夜祭的な</del><ins>環境試験のための</ins>チューニング祭が社内の有志数名で行われていて、そのときに色々学んだことをおまけとして書いておきます。</p>

<h3>ISUCONて何？</h3>

<p>下記参照。</p>

<ul>
<li><a href="http://blog.livedoor.jp/techblog/archives/66528186.html" title="なんでもありのWebアプリケーション高速化バトル、#isucon 開催のお知らせ">なんでもありのWebアプリケーション高速化バトル、#isucon 開催のお知らせ</a></li>
<li><a href="http://blog.livedoor.jp/techblog/archives/66595391.html" title="【締め切りました】Webアプリケーション高速化バトル、#isucon 詳細と参加者募集開始">【締め切りました】Webアプリケーション高速化バトル、#isucon 詳細と参加者募集開始</a></li>
<li><a href="http://d.hatena.ne.jp/do_aki/20110827/1314460762" title="ISUCON に参加してきました">ISUCON に参加してきました</a></li>
<li><a href="http://blog.riywo.com/2011/08/28/070009" title="#isucon に参加してきました＆isuconツールを試してみました">#isucon に参加してきました＆isuconツールを試してみました</a></li>
<li><a href="http://d.hatena.ne.jp/sfujiwara/20110827/1314460582" title="#isucon で優勝してきました">#isucon で優勝してきました</a></li>
<li><a href="http://d.hatena.ne.jp/tmatsuu/20110827/1314467819" title="isuconに参加してきた＆チーム「いんふらえんじにあー」の戦略など">isuconに参加してきた＆チーム「いんふらえんじにあー」の戦略など</a></li>
<li><a href="http://d.hatena.ne.jp/hideden/20110828/1314490713" title="isuconお遊びチーム（事前社内β組）の設定あれこれ">isuconお遊びチーム（事前社内β組）の設定あれこれ</a></li>
<li><a href="http://www.songmu.jp/riji/archives/2011/08/isucon.html" title="#isucon で優勝させてもらってきました">#isucon で優勝させてもらってきました</a></li>
<li><a href="http://d.hatena.ne.jp/karupanerura/20110828/1314469620" title="#isucon に参加してきました。">#isucon に参加してきました。</a></li>
</ul>

<p>要するに、「閲覧者視点での振る舞いさえ満たしてくれれば何をしようと構わんからWebアプリのレスポンスを改善しなさい」というお題で、誰が一番速くできるか競うイベント。</p>

<p>最初にある程度環境が整備されてるサーバ4台と、主催者側が用意した参考実装のアプリとテスト用データが渡される。このアプリってのはごくシンプルな個人ブログだと思ってもらえば良くて、最新記事10件が表示されるインデックスページと、記事全文が見られる詳細ページがあり、記事の投稿とコメントの投稿ができて即時反映される。全てのページには「最近コメントが付いた記事10件」が表示されるサイドバーがあり、ヘッダ画像、スタイルシート、JavaScriptが読み込まれる。テストデータにはそれなりに大量の記事データとコメントデータが入っている。フロントのWebサーバはApache、DBはMySQL、アプリはPerlとRubyとNode.jsのものが用意されている。</p>

<h3>「お遊び組」って？</h3>

<p>ISUCONは見学席が用意されていたのだけど、参加者以外はぶっちゃけ暇なので、空いたサーバ1チーム分を使って好きに遊んでいいことになっていた。で。そのお遊び用サーバで、事前に社内βで散々いじくり倒した俺とhidedenさんが空気読まずに自分のアプリで参加者達に対抗する、という大人気ないことをしていた。（すいません…）</p>

<p>一応ちゃんと言っておくと、中の人なので裏ルールや罠の存在についてある程度聞いていた上、6時間どころか2日3日費して試行錯誤していて、加えてhidedenさんに圧倒的な差を付けられた時は教えを乞うなどしていて、完全なるチートです<span style="font-size: 0.8em; color: #999">※</span>。正直あの短時間でここまでやってくるって本当凄いな参加者のみなさん、と感動しっぱなしでした。勝手に熱くなって最後の方はムキになってたのは内緒です。</p>

<p style="font-size: 0.8em; color: #999">
※ あ、でもでも、参加者の条件と対等じゃない「強くてニューゲーム」だった（会場に着くやいなや会社に行ってソース取って来た）という意味での「チート」で、コンテスト向けの実運用ではありえない実装にするとかはしてないです。なるべく突飛なことをせず普通にWAFを使い普通に配置するように心掛けてます。サーバのセットアップとかは手伝って貰ってるけど、アプリの実装は一人でやってるし、戦略そのものも自分で考えてます。
</p>

<p>というわけで以下は俺のアプリで実際にやったこと。<a href="http://d.hatena.ne.jp/hideden/20110828/1314490713" title="isuconお遊びチーム（事前社内β組）の設定あれこれ">お遊び組ベストスコアを出した方</a>とは別のアプリです。ちなみに使用言語はRuby。隣に座ったCTOと向かいに座った部長に「うちPerlの会社だからね？」とニヤニヤされながらも100%趣味全開のチョイス。「言語処理系の性能の違いが戦力の決定的差では無いということを教えてやる！」と<strong>赤くて速い人</strong>気取りで息巻いてみたものの、実際問題<strong>別な意味で全くその通りだった</strong>。</p>

<h3>糞クエリ対策とキャッシュ</h3>

<p>殆どのチームがまずはクエリの見直しとDBのチューニングに手を付けてた様子。テーブル構造の見直しからMySQLのオプションやストレージエンジンの変更とかをやってたチームもあったみたいだけど、俺は必要なカラムにインデックスを貼る程度に留めて、データをガンガンキャッシュしてなるべくDBまで到達しないようにする戦略にすることにした。</p>

<p>まぁまずみんな真っ先に気付いてたところとしては、アプリ内に非常に残念なクエリがわざと仕込んであるということ。素晴しく分かりやすいお手本のような糞クエリだった。サイドバーのデータを取得するのが次のようなクエリ。</p>

<pre class="prettyprint">
SELECT a.id, a.title
FROM comment c
LEFT JOIN article a ON c.article = a.id
GROUP BY a.id
ORDER BY MAX(c.created_at) DESC LIMIT 10;
</pre>

<p>記事が数千件、コメントが十数万件あるので、これは大分辛い。しかもサイドバーは全ページに表示されるので、全てのリクエストでこのクエリが発行されるという鬼畜さ。なので、次のように変更する。</p>

<ol>
<li>キャッシュからサイドバーに表示する記事IDのリスト取得を試みる

<ol>
<li>なければ、commentテーブルから記事IDのみのリストを取得する</li>
<li>キャッシュする</li>
</ol></li>
<li>記事IDのリストを元に、キャッシュから記事データ10件のリスト取得を試みる

<ol>
<li>それでも無かった数件をarticleテーブルからWHERE id INで取得する</li>
<li>キャッシュする</li>
</ol></li>
</ol>

<p>あと、サイドバー・インデックスページ・記事詳細ページでそれぞれ必要なカラムだけを取って使ってたけど、これは逆にID・タイトル・本文・投稿日時全てを取得してIDをキーにキャッシュに突っ込むようにした。こうすることでサイドバーとインデックスと記事詳細でキャッシュを共有できるようになる上、コメントの投稿も記事の参照も新しい記事に偏っているので、サイドバーを読み込む時点では記事データはほぼ全てキャッシュに載っていることが期待できる。</p>

<p>commentテーブルから記事IDのリストを取得してるところはまだ重いと思うけど、当初のクエリよりは遥かに速いし、並行してコメントが投稿されたときの整合性の担保とかするのが地味に面倒だったので、とりあえず後回しにする。</p>

<p>コメントはコメントのIDではなく記事のIDでまとめてキャッシュするようにした。ページングや並べかえ、コメントの削除や編集などは仕様になく、コメントのパーマリンクなどもないので記事ページ以外では表示されないため、1回で全部取れるのがよかろうという判断。それらがあるようだったら、記事と同じようにコメントIDをキーにして「リストの順番や内容が変更されてもIDだけを取ってくるようにして、データ自体はキャッシュに載ってるものは使う」みたいにしたかもしれない。でもあんまりDBやmemcachedへのリクエストが増え過ぎるのはアレなので何らかのまとまりでキャッシュするかなぁ、とか色々考えてたけど、複雑になって余計悪化する、みたいなことにもなりかねないし、微妙に難しい。これもとりあえずこれでいいやってことにして後回し。</p>

<h3>ノンブロッキングなフレームワーク</h3>

<p>若干工夫してみたのは、フレームワークには<a href="https://github.com/postrank-labs/goliath">Goliath</a>を選んだあたり。EventMachineベースのRubyのWebアプリケーションフレームワークおよびサーバで、これでI/Oを多重化して、同時接続数が増えてきても重いI/O処理でブロックしないで効率的に処理してくれることを期待する。まぁ、ぶっちゃけこれは実際にはそんなに効果なかった。</p>

<p>当初はPOSTのリクエストがばんばん飛んでくるとか、複数のテーブルに跨ってデータを取ってきて複雑な集約をするとか、画像アップロードみたいな長時間コネクション張り続けるようなリクエストがあるかなとか、そういう状況を想定してたんだけど、実際にはGETの比率のが圧倒的に高いしテーブル構造もシンプルで投稿はテキストのみ、みたいなパターンだったので「並列にI/O処理をする」ことがあまりなかった。</p>

<p>「遅延書き込みをするようにしてPOSTの際は即座にレスポンスを返してしまう」みたいなこともちょっとはやろうとしてたのだけど、「POSTリクエスト<strong>完了後</strong>1秒以内に表示に反映すること」ってルールにひっかかってテスト通らないことが頻繁にあったりして地味に嫌な感じになったので方針転換した。ここはむしろ同期処理にして「終わったら即書き込み即キャッシュ破棄」するようにして（実際には反映までにかかる時間に大差は無いはずなんだけど、こう振る舞った方がクライアント側からは速く反映されてるように見える）、POSTでは若干の時間をかけてしまってもいい、それよりもGETリクエストが来たときに既に準備が整ってるようにここで再取得再キャッシュまでやってしまう。</p>

<p>同時接続数の方はどうかというと、そもそもベンチマークスクリプトの並列数が最大10とかだったしコネクションも一瞬で切断されるし、じゃあワーカプロセスを10個立ち上げちゃえばいいじゃん、というオチが付いた。CPUもメモリもスッカスカでアプリサーバが遊んでたし、RubyやPythonみたいな言語を使う分には、1プロセス辺りの並列処理の効率化みたいなとこよりもUnicornの割り切りっぷりの方が現状理にかなってると思う。てことで言えばぶっちゃけ最初の素のSinatraで別に問題なかったような…どうしてもEventMachine使いたいなら<a href="http://rainbows.rubyforge.org/">Rainbows!</a>と<a href="https://github.com/raggi/async_sinatra">Async Sinatra</a>とかでも良かったような…まぁもう書いちゃったしいいか…。</p>

<p>余談だけどGoliath採用に併せて関連ライブラリも選び直すことになったので、MySQLクライアントには<a href="https://github.com/brianmario/mysql2">Mysql2</a>を、mechachedクライアントには<a href="https://github.com/astro/remcached">remcached</a>を、ついでに趣味でテンプレートエンジンに<a href="http://rubygems.org/gems/slim">Slim</a>を使った。Mysql2やSlimは大分良かったので何か機会があれば使っていきたいところ。</p>

<h3>Web「アプリ」って何？</h3>

<p>とにかくもうDBに複雑なことさせたら負け、それ以前にDBに行ったら負け、と来たら次に来るのは「ていうかアプリに行ったら負け」。この段階ではフロントのWebサーバはアプリサーバ2台のロードバランサとしての仕事しかしていなくて、静的ファイルもアプリ側でファイル読んで返してたし、更新してないページも毎回アプリにリクエストが来ていた。バックエンドのアプリケーションサーバがどんなに速くなってもフロントエンドのWebサーバの処理速度とは文字通り桁が違うので、できればなるべくバックエンドに行って欲しくない。ならばということで、フロントエンドでページ丸ごとキャッシュしてGETリクエストは全部そっちで返してしまうことにする。</p>

<p><a href="https://www.varnish-cache.org/docs/3.0/reference/vcl.html">VCL</a>の記述力やキャッシュ管理のしやすさ、<a href="https://www.varnish-cache.org/docs/3.0/tutorial/esi.html">ESI</a>機能、<strong>あと名前のかっこよさ</strong>などが魅力的だったので、最初は<a href="https://www.varnish-cache.org/">Varnish</a>を使ってみた。<a href="https://www.varnish-cache.org/docs/3.0/reference/vcl.html#directors">director</a>でアプリサーバ2台をまとめて、GET以外のリクエストは素通りするようにして、GETのレスポンスはページ単位でキャッシュするようにする。このままだと当然「投稿は1秒以内に反映されること」というルールが満たせないので、<a href="https://www.varnish-cache.org/docs/3.0/tutorial/purging.html">HTTPでパージできるように設定</a>して、POSTのリクエストを処理したらアプリ側からVarnishにPURGEリクエストを送るようにした。</p>

<p>そうなると今度はキャッシュの破棄のタイミングが問題になってくる。記事の投稿はまぁいい。記事が投稿されて内容が更新されるのはインデックスページだけなので、1ページ破棄してやればいい。が、問題はコメントの方。コメントが投稿されるとまず記事ページが更新されて、「最近コメントが付いた記事10件」が表示されるサイドバーも更新される。が、このサイドバーは全てのページで表示されている。どこかの記事に1件コメントされる度に全ページのキャッシュが破棄されてたのでは殆どキャッシュの意味を為さない。なのでサイドバーは各ページのレンダリング時には生成せず、ESIでVarnishの段階でincludeさせるようにした。こうすれば、コメントが投稿されたときにパージするのは該当する記事ページとサイドバーだけになる。</p>

<p>そこまでやると最初の数回とPOST直後の数回以外は全部Varnishが返してくれるようになるので、アプリサーバの方は殆ど遊んでいる状態になる。前の段で「リソース余ってるからガンガンプロセス増やしちまおうぜ」って言えたのはこのおかげ。数万リクエスト処理しても数百とか数千くらいしか後ろに到達しなくて、さらにその後ろのDBまで行くのはもっと絞られてくる。この段階で初期状態から10倍くらいパフォーマンスが向上している。</p>

<p>ところがこの辺りで地味に嫌な罠を踏む。VarnishでESIを使うとContent-Lengthヘッダを返さなくなるので、<del style="display: none" datetime="2011-08-29T09:18:00+09:00">Keep-Aliveで接続してるクライアントがいつレスポンスが終わったのかよくわからなくてタイムアウトするまで</del><ins style="text-decoration: none" datetime="2011-08-29T09:18:00+09:00">一部のクライアントできちんと扱えないのにKeep-Aliveのリクエストを送ってきたときにいつレスポンスが完了したのか判断できず、コネクションが切れるまで</ins>待ち続けてしまう。設定でどうにかできそうな気がするけど不慣れなVarnishに四苦八苦してなかなかうまくいかず、前段にもう一段Nginxを立ててリクエスト<ins style="text-decoration: none" datetime="2011-08-29T09:18:00+09:00">/レスポンス</ins>をいじってみたりするも今度は多段にしたのが祟ってそのオーバーヘッドで大分パフォーマンスが落ちてしまう。</p>

<p>結局Keep-Alive問題は真面目に対応するのをやめていかなる場合でも無効にしてしまおうかーとか考えてるあたりで、hidedenさんのNginx/SSI+SCGI構成にダブルスコアをつけられてしまって、NginxマジはえーほんとパネェつかVarnishってぶっちゃけNginxより遅いのに何で選んだの、みたいな声に負けてVarnishと戯れるのを放棄することにした。いや多分、俺のVarnish力の低さのせいで真の実力を発揮できてなかっただけでそこまでオワコンでは無いと信じたいのだけど、と一応擁護しておく。でもしばらくはNginx一択だけど。</p>

<p>ちなみに、ベンチマークスクリプトはそもそも<del style="display:none" datetime="2011-08-29T09:18:00+09:00">Keep-Aliveに対応してない</del><ins style="text-decoration: none"  style="display:none" datetime="2011-08-29T09:18:00+09:00">その「一部のクライアント」と同じ挙動をしていたわけではない</ins>ので、この時点ではこの問題は割り切って無視するという選択肢もあった。が、本番当日の講評の際に<a href="http://blog.nomadscafe.jp/2011/08/http-load-isucon.html" title="チート対策とhttp_loadに仕掛けた罠の話 #isucon">kazeburoさんが恐しい罠を仕込んでた</a>ことを知らされる（っていうか社内では普通に話してたらしいけど聞いてなかった）。なんと、<del style="display:none"  datetime="2011-08-29T09:18:00+09:00">HTTP1.1じゃない</del><ins style="text-decoration:none" datetime="2011-08-29T09:18:00+09:00">持続的接続ができない</ins>のにそう<ins style="text-decoration:none" datetime="2011-08-29T09:18:00+09:00">できるかのように</ins>偽装してKeep-Aliveって付けた嫌がらせリクエストを3%程混ぜており、これに律儀に応えると、ベンチマークスクリプトはコネクション切れるまで待ち続けてしまって致命的に遅くなる、というもの。Nginxが素晴らしいのは周知の事実なので本番でも使ってくるチームが多いことを予想して、「Nginx（や、他の高速Webサーバやキャッシュサーバ）をチューニングせずにただ設置するとハマる罠」を仕掛けたんだとか。そうと知らずにそれを回避することに成功していて怪我の功名だった。本当運営の人達悪魔や…。</p>

<h3>Nginxのチューニング</h3>

<p>気を取り直して<a href="http://nginx.net/">Nginx</a>の設定。まずNginxはWebサーバなので、餅は餅屋ということで静的なファイルはアプリサーバからフロントサーバに全部持ってきてNginxに返させてしまう。これでアプリから静的ファイルの配信機能を取っ払うのに成功して、本当に若干だけど無駄な処理を減らせる。<a href="http://wiki.nginx.org/HttpUpstreamModule">リバースプロキシの設定</a>は簡単なのでこれも普通に設定してしまう。それからVarinishのESI同様NginxでもSSIを有効にする。</p>

<p>それからキャッシュ。Nginxのキャッシュの方法はいくつかある。まず直感的なのはファイルキャッシュ。キャッシュファイルの置き場所を決めておいて、upstreamに飛ばすlocationのところでcacheを使うよって指定してあげれば、upstreamからのレスポンスを自動的にそこに貯めてってくれて、二回目以降は勝手にそのファイルから返すようにしてくれる。Varnishのときはキャッシュストレージにメモリキャッシュとファイルキャッシュが指定できて、ファイルの方を指定すると格段に遅くなるので、Nginxもそうなるんじゃないかと思ったけどこれが驚く程高速でびっくりする。むしろVarnishのメモリキャッシュの時より速いくらいだった気がする。ちゃんとは検証してない。ただ、このファイルキャッシュはuriをハッシュしてディレクトリに配置してしまうため、外からはどれが何のキャッシュなのか分からなくて、Varnishに比べるとキャッシュの管理が難しい、と思ってたら、ちゃんと<a href="http://labs.frickle.com/nginx_ngx_cache_purge/">こういうモジュール</a>作ってる人がいた。これならVarnishのときに作ったHTTP越しのパージの仕組みがそのまま使える。</p>

<p>もう一つ、<a href="http://wiki.nginx.org/HttpMemcachedModule">memcachedをまるでアプリサーバのように見立てて、pathをキーにしてmemcachedにあるデータをそのままレスポンスボディにして返してしまうという驚きのモジュール</a>もある。こっちの利点は、アプリ側からもキャッシュが扱い易いということ。普通にアプリからmemcachedに繋いで、Nginxにレスポンスを返すときに同時にmemcachedにも書き込んでおくと、次はそっちから読んでくるようにしてくれる。破棄するのも普通にdeleteすればいい。HTTPリクエストを投げてパージするよりは分かりやすいし、memcachedプロトコルのがHTTPよりは速そうだ。が、問題は、<strong>前述のファイルキャッシュより遥かに遅い</strong>ということ。これはmemcachedが遅いというよりmemcachedに毎回コネクションを張り直すコストがファイルキャッシュからの読み込み（これはおそらくかなり内部で最適化されているはず）のコストよりも高いせいらしい。ほぼ同じ状態でファイルキャッシュをmemcachedに切り換えたら、スコアが半分以下になってしまって絶望的な気分になった。仮にHTTP越しのパージよりもmemcachedのがアプリ側からは扱い易くてコストも低かったとしても、大半はNginxの段階で完結するキャッシュ済みGETリクエストなので、そっちのオーバーヘッドの方がもろに結果に影響する。ので最初はファイルキャッシュを採用した。</p>

<p>けど、hidedenさんの方はmemcachedを使っててそれでも俺のやつよりパフォーマンス出てるのでなんでだろうと聞いてみたら、<a href="http://wiki.nginx.org/HttpUpstreamKeepaliveModule">upstreamとのコネクションを繋ぎっぱなしにしていた</a>からだったらしい。試しにkeepaliveを設定してみたら、アプリ側何もせずに一気に3倍くらいのスコアになってhidedenさんのスコアに肉迫するレベルになった。同じことを当日もやらかした。hidedenさんが毎分11万リクエストというハイスコアを出した一方、前日まで大差はついてなかった俺の方は3万程度しか出てなくて焦ったのだけど、nginx.confを見直してkeepalive付け忘れに気付いて再起動したらちゃんと動いた。</p>

<p>で、結果はというと、ギリギリ100000req/minを越えるくらい。「お前はチートしてそれかよショボいな」と言われないくらいの結果は出せたと思うのでちょっとホッとした…。</p>

<h3>この戦略の意味</h3>

<p>POSTよりGETが圧倒的に多く、大半のリクエストが新しいデータに集中し、一度書き込まれた投稿はその後はあまり書き換えが起こらない、というのは、「大部分がほとんど書き換わらない動的コンテンツ」ではなくて「一部分が書き換わることがある静的コンテンツ」だと思っちゃえば少し話が簡単になる。</p>

<p>GETリクエストなんか静的なHTMLファイルを自動生成するためのトリガー、くらいに考えて、ただそれがファイル書き出しじゃなくてメモリ上のキャッシュに突っ込む方が扱い易いよねって発想で行けば、どんなミドルウェアが欲しいかとか、アプリは何をするべきなのかとか、どこで一番頑張るんだそうかフロントのWebサーバか、みたいなことでやることが決まる。てか、どっか1チームくらい本当にHTMLファイル書き出してデプロイしちゃうとこ無いかなーとか思いながら見てた。多分それはそれで速い。絶対面倒なのであまりやりたくは無いけど。</p>

<p>もっとぶっちゃけ話をすると、ライブドアブログの閲覧側チューニング戦略が（実現してる方法は違うけど）大体この形なので、ページのキャッシュと更新の局所化は最初からやる予定でいた。アプリエンジニアの性でついついアプリいじりに時間を割きたくなっちゃうとか、ミドルウェアやサーバ管理の知識経験が致命的に不足していたので時間ばっかり食ってしまったとか、ってのが「時間内には終わらなかったけど最終的には出来た」の実情だったりします。アプリエンジニアは常日頃からそういう知識をちゃんと収集しておけ、あとインフラチームと仲良くしておけ、色々捗るぞ、というお話でした。</p>

<h3>上手く行かないケースと使い回せるテクニック</h3>

<p>ブログ型の単純な表示系のリクエストが多いお題だったからWebサーバの性能が結果に直結してたけど、更新系のリクエストが多くて条件によって表示要素の個数や種類が大きく変わる、みたいな場合だと今度はアプリやDBの方に比重が移ってくる。例えばTwitterのサブセットみたいなのがお題だったらまた全然結果が違ったはず。</p>

<p>データのキャッシングやSSIみたいな仕組みはその場合でも有効だろうけど、「Nginx置いたらパフォーマンス20倍になったwwwwマジうめぇwwwwアプリ関係ないwwww」みたいなシンプルなことにはならないので、そっちの場合はアプリの実装力を鍛えてないと死ぬ。ISUCONに「部門別」とかあったら面白いかもねーとか思ったけど準備する側が死にそうなので軽々しく言うのはやめときます。僕お手伝いって名目なのに普通に遊んでただけでほんとごめんなさい。</p>

<h3>反省点とか</h3>

<p>
DBロクに見てない。上位陣の方々見てるとMySQLバリバリチューニングしてるので、もっとちゃんといじればもう少し速度出るはず。一番効果が高いところを優先したと言えばそうだけど、あんま詳しくないところを放置しただけだったりもするので（アプリの全面書き直しとかマジで要らんかった）、ちゃんと勉強する。
</p>

<h3>みんなもやってみるといいよ</h3>

<p>ISUCON運営チーム謹製の<a href="https://github.com/tagomoris/isucon">ベンチマークツールと各言語の参考アプリ</a>は公開されてるので、是非触ってみてくだしあ。ゲーム感覚で楽しいし、各参考実装や意図的に仕込まれてる罠は、新人教育なんかにもうってつけだと思う。ええはい。自分の実力不足をガチで痛感した次第です。いや本当勉強しよう。ちゃんと。</p>

<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1732537" width="1" height="1" />
]]>
</content:encoded>
</item>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1649478.html">
<title>ライブドア第五回テクニカル・セミナーで発表してきた</title>
<link>http://blog.livedoor.jp/faulist/archives/1649478.html</link>
<description>
「スマートフォンについて何か喋ってよ」というかなり大雑把なネタ振りをされたので、第五回ライブドア・テクニカルセミナーでこんな感じの話をしてきました。



140字で分かるfaultierの発表: 「iOSとAndroidは違うものだって認識しよう」「両方やってみたらどっちかが圧...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2011-02-27T17:44:14+09:00</dc:date>
<dc:subject>レポート・レビュー</dc:subject>
<content:encoded><![CDATA[<p>
「スマートフォンについて何か喋ってよ」というかなり大雑把なネタ振りをされたので、<a href="http://blog.livedoor.jp/techblog/archives/65434269.html" target="_blank" title="第五回ライブドア・テクニカルセミナー開催のお知らせ">第五回ライブドア・テクニカルセミナー</a>でこんな感じの話をしてきました。
</p>
<div class="twitter_timeline">
<a href="http://twitter.com/faultier/status/41382472201674750" target="_blank"><img src="http://a2.twimg.com/profile_images/790605194/sloth06_normal.jpg" class="left" /></a>
<p class="left">140字で分かるfaultierの発表: 「iOSとAndroidは違うものだって認識しよう」「両方やってみたらどっちかが圧倒的に作り易いわけじゃなかった」「お前これやりたかっただけだろ <a href="http://bit.ly/e429B1" target="_blank">http://bit.ly/e429B1</a>」 #ldtech</p>
<br class="twitter_timeline_bottom" />
</div>
<p>
あとで動画や資料は公開されるはずですが、大変申し訳ないことにテンパりまくって見るに耐えない感じになっちゃってると思うので、一応何を言いたかったのかを補足しておきます。
</p>
<h3>iOSとAndroidは違うものだという話</h3>
<p>
開発環境の違いについてはまぁいいとして、例えばアプリの設計思想。iOSでは「まずアプリケーションという大きなプログラムがあって、その中で画面を表示したり通信したりしてる」って感じの構成になってるのだけど、Androidの場合「画面とかデータ管理機能とかそういう独立した部品があって、それらをまとめたものに名前を付けてアプリケーションという風に見せる」という感じになっていて、後者の方が若干まどろっこしい感じはある。一方でアプリ間でのやりとりについて考えてみるとiOSでは「openURLで他のアプリを起動する」とか「Keychainだとかアルバムだとかを使って（限定的に）データを共有する」とかその程度に留まっているのだけど、Androidの場合はOSが提供する機能も同じアプリ内の機能も他のアプリの機能も全て「部品」という形になっているので、きちんと設計しさえすればデータや機能をシームレスに連携させられて大分可能性が広がる。この考えかたの違いはアプリの作り方の違いにモロに出てくる。
</p>
<p>
それから、デバイスの違い。iOSの場合はiPhone/iPod touch系とiPad系の二系統しかなく、どちらもApple一社が開発しているので、OSや周辺サービスも含めたプラットフォーム全体のコンセプトから良くも悪くも外れない。一方のAndroidはと言うと、Googleが主導してはいるものの、端末メーカー各社それぞれがOSにカスタマイズを加えてたり、独自のハードウェア（キーボードが付いてたり、先行してFeliCa対応してたり、裸眼立体視ディスプレイが付いてたり、そう言えばゲームのコントローラが付いてたりするようなのも出るんだっけ？）を搭載してたり、小さな画面のものがあったり、横固定だったり、多種多様だ。ハードウェアの違いがソフトウェアに影響を与えないわけが無く、「使い易いアプリって何？」って問いの答えはiOSとAndroidでは当然違うし、Android端末間でも異なってくることもある。
</p>
<p>
ここまでは技術者視点だけど、当然ビジネス的にも文化的にも異なる展開をするだろうね。と、いうことを考えたら、アプリの企画や開発をするのに「iOSとAndroidは違うもの」って認識は当然持っておく必要はあるだろう。
</p>
<h3>iOSアプリとAndroidアプリはどっちが作り易いか</h3>
<p>
率直に言うと、最初のとっつき易さに関して言えばAndroidの方が圧倒的に上。僕の元々のお仕事はPerlやRubyでWebアプリケーションを開発することで、今もアプリ作りのかたわらWebやってるのだけど、そういう人が、ObjCとXcode/IBでアプリを作りクローズドなプラットフォームでお仕事するのと、JavaやJVM上で動く言語とEclipse+ADTでアプリを作りオープンなプラットフォームでお仕事するのとで、どっちが大変かなんてのは言うまでもない。
</p>
<p>
で、だからAndroidのが素晴しいって話になるのかというとそれはそれで違うんだよってのが、<a href="http://www.flickr.com/photos/koyhoge/5477708475/" target="_blank" title="このスライドはどうなんだwww #ldtech">無茶苦茶RTされまくってハッシュタグを埋めてしまった件のスライド</a>（本当にすいませんでした！）の意図。「最初は薔薇色の世界に見えるAndroidも作り続けて行くと段々難しさに気付いていく」「最初はとても理解できないと思ったiOSも作り続けて行くと良さに気付いていく」「だからつまり、最初のとっつき易さだけでどっちかが圧倒的に優れていると言えるわけではない、最初のハードルを越えたら別の問題が見えてくる」っていうのを（一部の人には）視覚的に分かってもらえるように、と思ってあのイラストを入れたんだけど、前日にまどか☆マギカの8話を観て「<b>正直やりすぎた</b>」と反省はしました。あのスライドを見て「きっとこの人はAndroidよりiPhone派で、あと赤い子が好きなんだろう」ってコメントしてる人がいて、いやうん割とその通り（「杏子に『食うかい？』って言ってもらう」ことを願って白い獣と契約しかねないです）だけど、<b>いくらなんでもアレに例えなきゃいけないほどAndroidを悪くは思ってない</b>。Androidが孵す卵はもう少し夢のあるものだって信じてますし、iOSのあの林檎を輝かせ続けるために何が犠牲になってるかに思い至らないわけでもないですし。
</p>
<p>
魔法少女の話は置いといて、じゃあその「別の問題」ってなんなの、という話でいうと、まず一つはマルチデバイス/OS対応。Androidは自由度の高さが最大の売りだけど最大の欠点でもあって、Androidに取り組んでるところは軒並「端末やキャリアによってお約束事が違い戸惑う」とか「バージョンアップしない端末があってどのバージョンのOSを対象にするか迷う」とかそういうことに悩んでいる。正直、Android開発に関わってない人が想像してる以上にデバイスごとの差異が大きいし、今後収束するどころかもっと個性的なものが増える方向に進むだろう、だってそれが一番の売りなんだから。それに真面目に対応していくことの面倒臭さは、iOSアプリの最初の学習コストを上回ると思う。iOSだってもちろんiPhoneとiPadで両対応ってのは凄く大変だし、最近はケータイだと思って買っていくので家にパソコンも無くアップデートしないみたいな人も増えてると聞くので、その問題からは無縁じゃないんだけど、それでも現時点ではAndroidよりは幾分マシだ。
</p>
<p>
それから、パフォーマンスチューニング。いくらスマートだスマートだって言ったって所詮はモバイルデバイスなので、タイトなリソースをやりくりしていかなきゃいけない。今まで組み込み機器やローレイヤーなところの開発をしてた人からすれば「何を贅沢な」「ゆとりめ」と冷笑されそうな話だけどさ。スマートフォンアプリに求められる「リッチさ」はどんどん上がって行く一方だけど手持ちの戦力は高性能な戦闘機と言ったところで、その気になれば空母の艦隊を投入できるサーバサイドの開発とは全然違う。これに関して言えばiOSでもAndroidでも同じで、そういう段階に至ったらどっちにしてもそれなりに「難しい」。
</p>
<h3>クロスプラットフォーム開発に関して思うこと</h3>
<p>
きっと伊藤直也さんが肯定的な文脈で言及するに違いないと思って（実際してた）内心ビクビクしながら話してたんだけど、どうしても言わざるを得なかったので言ってしまった。<b>一つのコードで両方のプラットフォームに対応するのは、現時点ではあまり現実的じゃないと思う</b>。
</p>
<p>
もちろんアリだろうと思ってる領域もあって、例えばゲームエンジンみたいなものは、凄く効果が高いと思う。最近は一般的な携帯ゲーム機向けのものと同じくらいのクオリティの高いゲームがどんどん増えてるけど、ああいうのは大概UIから自前で作ってしまうし、中のロジックもiPhoneだからAndroidだからということも無いだろうから、細かい差異はライブラリに吸収させちゃうのが定石だろう。それから、コアな部分をHTML5+JSで作って、ブラウザ上でできないことやパフォーマンスが必要なことをやらせるためにネイティブのUIでWebViewをラップする、というハイブリッド戦略も良いと思う。SNSとかブラウザゲームとかだと期間限定のイベントがあったり速いペースで機能改善していったりする必要があって、それで言うとアプリ開発して（iOSならAppleの審査を通して）ユーザにアップデートしてもらう、というプロセスを通すと遅すぎて全然上手くいかない。うちで作ってるアプリだとロケタッチとかは部分的にそんなアプローチをしてるし、今後そういうアプリも増えると思う。
</p>
<p>
懐疑的なのはトランスレータとかミドルウェアとかの話。Titaniumとかが盛り上がってるのはもちろん知ってるし追ってるけど、最近だと「一つのコードで両方のプラットフォームに対応」みたいな話より「iPhone（or Android）のアプリ開発を楽にする」って文脈で語られることのが多くなってきてるかなーという印象を受ける。まだまだ発展途上でどっちかのプラットフォームのサポートは手厚いけどもう片方は追いついてないだけで、今後は次第に改善していくだろう、ってところもあったりするんだけど、それとは別に個人的には「これだけ"違い"があるんだから、無理して同じ扱いをするよりそれぞれに合ったやりかたをした方がいいんじゃないか」って思ってる。特にツール系のアプリはそうで、それぞれにUIのお作法があるのでちゃんとそれぞれの特性を活かしたUIにした方がよくて、そうなったときに内部のロジックだって無理に同じにしておくより別プロジェクトにしておいた方がかえってメンテしやすかったりする。将来的にどうなるかは分かんないけども、今現在両方のアプリを作ってての実感としてはそんなところ。
</p>
<h3>というところまで書いて</h3>
<p>
そんな話が全然出来てなかった自分の話下手っぷりに絶望したのでもう少し練習しないとな、と思いました。あたしって、ほんとバカ。
</p>
<h3>謝辞</h3>
<p>
キュゥべえドロイドくんは<a href="http://twitter.com/SEN1227" target="_blank">SENさん</a>に、杏子の林檎は<a href="http://twitter.com/iciro" target="_blank">ニチロさん</a>に描いてもらいました。素敵なイラストありがとうございました！
</p>
<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1649478" width="1" height="1" />
]]>
</content:encoded>
</item>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1606073.html">
<title>3DSを体験してきた</title>
<link>http://blog.livedoor.jp/faulist/archives/1606073.html</link>
<description>
ニンテンドー3DSの体験会があるから行かないかと友達に誘われたので、今日は朝から幕張メッセまで行ってきた。恐しく混んでてろくに遊べないんじゃないかと不安だったんだけど、タイミングが良かったのか案外しっかり触ってこれた。体験したのはSTEEL DIVERとARゲームズとni...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2011-01-08T20:52:36+09:00</dc:date>
<dc:subject>レポート・レビュー</dc:subject>
<content:encoded><![CDATA[<p>
<a target="_blank" href="http://www.nintendo.co.jp/3ds/experience/nw2011/index.html">ニンテンドー3DSの体験会</a>があるから行かないかと友達に誘われたので、今日は朝から幕張メッセまで行ってきた。恐しく混んでてろくに遊べないんじゃないかと不安だったんだけど、タイミングが良かったのか案外しっかり触ってこれた。体験したのはSTEEL DIVERとARゲームズとnintendogs+cats。ゼルダやMGSはまぁ、流石に長蛇の列で映像を見るだけに留めた。パルテナの鏡かパイロットウイングスかどっちかは試しておけばよかったなぁとちょっと後悔。んでまぁ、総合的な感想としては、予想してたより面白いデバイスで好感触。
</p>
<h3>裸眼立体視ディスプレイ</h3>
<p>
3DSというくらいなので目玉機能なわけだけど、思ったことは二つ。「かなりちゃんと3Dしてるし、綺麗」「んでもまぁ、面白い映像だなーっていうくらい」。
</p>
<p>
実はこないだ誘惑に負けて<a href="http://www.nttdocomo.co.jp/product/foma/smart_phone/sh03c/" target="_blank">LYNX 3D</a>を買ってしまったんだけど、個人的にはこれよりも立体感あるなぁと思ったし、ディスプレイそのものも現行のDSより大きく綺麗になってるので映像は良かった。
</p>
<p>
一方で、3Dテレビや3D Androidのときも思ったんだけど、「おおすげー、3Dだ！…確かに凄いけど、凄いんだけど、それで？って感じだなぁ…」という印象は拭えなかった。面白い演出は出来るようになるんだろうけど、ゲームそのものには(今までとまるで違うというほどには)影響しなさそうというか。見え方にも個人差があるし（ガチャ目や乱視持ちにはちょいキツい）、結構目や脳にかかる負荷もあるし、映像展示の方で色々見てたらソフトによって大分差が出てたし、3Dオフにしてもちゃんとゲームできないといけないし、というわけで、まぁぶっちゃけオマケ感は否めない。本当に立体映像でゲームががらっと変わるとしたら、やっぱり本当に立体投影できるようになるまで待たないとなんだろうなぁと思った。
</p>
<h3>モーションセンサー、ジャイロセンサー</h3>
<p>
個人的におっと思ったのはこっちの方。STEEL DIVERの潜望鏡モードやARゲームズでは「3DS自体を動かす」というのがゲームの操作の一部になってて、これがかなり良かった。これとあとスライドパッド（アナログスティック）が追加されて、タッチパネルやフロント/リアカメラと合わせれば携帯ゲーム機としては「入力手段」がかなり多いデバイスになるので、それこそゲーム性にかなり影響してくると思う。
</p>
<p>
特にARゲームズ！twitterとかでも行ってる人が口を揃えて「ARゲームズ面白い！」って言ってたのが分かると思うけど、実際これが大分良かった。ゲーム自体は他愛のないもので、机の上に置いたマーカを3DSのカメラで認識すると、そこに的やらドラゴンやらが出てきてそれを撃ったりして遊ぶ（体験版なのでそれだけだったけど、実際にはもっと色んなミニゲームが入るとのこと）というものなのだけど、マーカを置いてる机ごとぼよんぼよん波打つのを上から横から狙いを付けるとか、ドラゴンの背後に回り込んで背中を攻撃するとか、「ゲームの演出として」ARとモーションセンサーをしっかり組み込んでて、拡張現実で遊ぶってのはこういうことだよなーと思わせるものだった。
</p>
<p>
ていうか、一応スマートフォンアプリの開発者のはしくれとしては、「何でこれをiPhoneやAndroidで先に"ちゃんと"やらなかったんだろう…」と大分反省した。モーションセンサーとカメラが付いててOpenCVやARToolKitを使えてある程度の処理性能があるデバイス、もうあるのに、ね。
</p>
<h3>ステージイベント</h3>
<p>
ゲームミュージックライブがとても良かった。ジャズアレンジのマリオやゼルダがすごく素敵で、もし明日明後日行く人がいたら、12:30からライブなのでそれに間に合うように行くことをおすすめする。並んで待ってる間に演奏を聴けたので、退屈せずに待ってられた。ちなみにステージイベントの様子はインターネット中継されてるようなので、ストIVやバイオのソフト紹介とかを見たい人は時間を確認して見てみるといいですよ。
</p>
<h3>全然関係ない話</h3>
<p>
隣でペット博をやってたらしく、会場付近にはそこかしこにnintendogsじゃない本物のdogsが走り回っていて、一旦外出て休憩してる間に凄く和んだ。うん、本当に全然関係ない話。
</p>
<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1606073" width="1" height="1" />
]]>
</content:encoded>
</item>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1561677.html">
<title>Objective-Cで正規表現を使う その2</title>
<link>http://blog.livedoor.jp/faulist/archives/1561677.html</link>
<description>
前の記事で予告した通り、今度はNSRegularExpressionの話。

正規表現でマッチした部分文字列を取得する

まずNSRegularExpressionオブジェクトを作って、それのメソッドにNSStringのオブジェクトを渡す、という形で使う。まぁ説明するよりコード見た方が早い。


NSString ...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2010-11-12T01:48:05+09:00</dc:date>
<dc:subject>プログラミング</dc:subject>
<content:encoded><![CDATA[<p>
<a href="http://blog.livedoor.jp/faulist/archives/1561287.html" title="Objective-Cで正規表現を使う その1">前の記事で予告した</a>通り、今度はNSRegularExpressionの話。
</p>
<h3>正規表現でマッチした部分文字列を取得する</h3>
<p>
まずNSRegularExpressionオブジェクトを作って、それのメソッドにNSStringのオブジェクトを渡す、という形で使う。まぁ説明するよりコード見た方が早い。
</p>
<pre class="prettyprint">
NSString *string = @"「そんな正規表現で大丈夫か？」「大丈夫だ、問題ない」";
NSError *error   = nil;
NSRegularExpression *regexp =
  [NSRegularExpression regularExpressionWithPattern:@"「そんな(.+)で大丈夫か？」「(.+)」"
                                            options:0
                                              error:&amp;error];
if (error != nil) {
  NSLog(@"%@", error);
} else {
  NSTextCheckingResult *match =
    [regexp firstMatchInString:string options:0 range:NSMakeRange(0, string.length)];
  NSLog(@"%d", match.numberOfRanges); // 3のはず
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:0]]); // マッチした文字列全部
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:1]]); // "正規表現"
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:2]]); // "大丈夫だ、問題ない"
}
</pre>
<p>
地味にややこしい。Rubyで書いたらこんなんで済むのに。
</p>
<pre class="prettyprint">
# coding: utf-8
if "「そんな正規表現で大丈夫か？」「大丈夫だ、問題ない」" =~ /「そんな(.+)で大丈夫か？」「(.+)」/
  puts $&amp;
  puts $1
  puts $2
end
</pre>
<p>
まぁRubyやPerlと比べるのは（少なくとも文字列操作や正規表現に関して言えば）フェアじゃないですけど！とにかくこれで正規表現で部分文字列を探せるようになりました、と。
</p>
<p>
ちなみに、-firstMatchInString:options:range:というメソッド名で分かると思うけど、これは最初にマッチした箇所しか取ってこない。マッチした箇所全て欲しければ、-matchesInString:options:range:を使えば、NSTextCheckingResultが入ったNSArrayが返ってくる。別に返り値はずっと取っておく必要はなくて、単にマッチする毎になんか処理をしたいんだよ、ってときは、-enumerateMatchesInString:options:range:usingBlock:が使える。さっきの-firstMatchInString:options:range:を書き換えるとこんな感じになる。
</p>
<pre class="prettyprint">
NSRegularExpressionOptions options = 0;
NSRange range = NSMakeRange(0, string.length);
id block = ^(NSTextCheckingResult *match, NSMatchingFlags flag, BOOL *stop){
  NSLog(@"%d", match.numberOfRanges);
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:0]]);
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:1]]);
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:2]]);
};
[regexp enumerateMatchesInString:string options:options range:range usingBlock:block];
</pre>
<p>
Blocksの使い方は<a href="http://blog.livedoor.jp/faulist/archives/1470672.html" title="RubyエンジニアのためのObjective-C Blocks入門">以前書いた記事</a>とか読んでもらえると分かるかもしれない。あの記事を書いた時点ではiOS4.0を想定してアプリ作れなかったので実質まともに使えるのがSnow Leopardだけだったのだけど、今ならiPhone/iPadともに4系前提で作れるし、そもそもNSRegularExpression自体がiOS4.0以降にしか無いのでNSRegularExpressionを使える環境ならBlockも使えるので問題ない。
</p>
<h3>置換する</h3>
<p>
正規表現が使えるなら一番やりたいのは置換だろう、ということでもちろん置換もできる。-stringByReplacingMatchesInString:options:range:withTemplate:というのがそれ。
</p>
<pre class="prettyprint">
  NSString *string = @"「そんな正規表現で大丈夫か？」「大丈夫だ、問題ない」";
  NSString *template =
    @"$0\n→（$2砕け散る）\n→「神は言っている、ここで死ぬ運命ではないと」\n→「$1」「一番いいのを頼む」";
  NSRegularExpression *regexp =
    [NSRegularExpression regularExpressionWithPattern:@"「(そんな(.+)で大丈夫か？)」「.+」"
                                              options:0
                                                error:nil];
  NSString *replaced =
    [regexp stringByReplacingMatchesInString:string
                                     options:0
                                       range:NSMakeRange(0,string.length)
                                withTemplate:template];
  NSLog(@"%@",replaced);
</pre>
<p>
最初は話を聞かなかったあいつもちゃんと一番いいのを頼んできたので、今度は大丈夫だろう。しれっと$0とか$1とか使ってるけど、もちろんちゃんと置換文字列の中でキャプチャした部分文字列を参照したりできてるはず。
</p>
<p>
ただ、-stringByReplacingMatchesInString:options:range:withTemplate:は文字列そのものを置換してるわけじゃなくて、引数のNSStringのオブジェクトをcopyして置換したものを返してくる。なので、元のstringは何も変わってないので変わったつもりで使おうとしたらアレ？ってなるし、毎回文字列のコピーをするので場合によっては無駄になる。その場合は-replaceMatchesInString:options:range:withTemplate:の方を使う。基本的には-stringByReplacingMatchesInString:options:range:withTemplate:と同じなんだけど、以下の点が違う。
</p>
<ul>
<li>引数にNSStringでは無くNSMutableStringを取る</li>
<li><strong>引数のオブジェクトのコピーではなく引数のオブジェクト自体を置換する</strong></li>
<li>返り値は置換後の文字列ではなく整数値で、置換箇所の数を返す</li>
</ul>
<p>
というわけで、ある正規表現で置換した文字列をさらに別な正規表現で置換して、みたいなことをやる場合はこっちのメソッドを使うべき。
</p>
<p>
ちなみに、上記二つのメソッドはマッチした箇所を全部置換する。例えば下のようなコードだと「大丈夫か」と「大丈夫だ、」が両方置換される。
</p>
<pre class="prettyprint">
  NSString *string = @"「そんな正規表現で大丈夫か？」「大丈夫だ、問題ない」";
  NSString *template =
    @"チョ☆チョニッシーナ☆まっソコぶれっシュ☆エスボグリバンバーベーコンさんだね！";
  NSRegularExpression *regexp =
    [NSRegularExpression regularExpressionWithPattern:@"大丈夫(か|だ)、?"
                                              options:0
                                                error:nil];
  NSString *replaced =
    [regexp stringByReplacingMatchesInString:string
                                     options:0
                                       range:NSMakeRange(0,string.length)
                                withTemplate:template];
  NSLog(@"%@",replaced);
</pre>
<p>
もしマッチした箇所の内特定の部分だけを置換したい場合は、-firstMatchInString:options:range:とか-matchesInString:options:range:でNSTextCheckingResultのオブジェクトを取得しておいてから、-replacementStringForResult:inString:offset:template:を使う、みたいな感じになるかしら。ちょっと面倒な気もするけど。
</p>
<h3>RegexKitLite or NSRegularExpression</h3>
<p>
両方書いてみた感想で言うと、個人的にはRegexKitLiteのNSStringにメソッド生やしてくアプローチのAPIのが使い易いと思った。CoreFoundation使ってごりごり書いてるのでパフォーマンスも悪くないし、割と早い段階からBlocksに対応してたりとアクティブに開発されてるし、その気になればソース読めるし（まぁ、チラ見しては見たもののあんまり読む気にはならないのだけども）…とか考えると、既にRegexKitLiteを使ってるなら別に無理にNSRegularExpressionに乗り換える必要は無い気がしてくる。iOS4.0以前のバージョンもターゲットにするなら他に選択肢はないし、あと何故かNSRegularExpressionクラスはiOSにしか無くてMacOSXでは使えないという面白いことになってるので、iOSでもMacでも動くようなコードを書く場合もやっぱりNSRegularExpressionは使えない。
</p>
<p>
とは言えNSRegularExpressionの方はFoundationの一部なので、数カ所正規表現での置換を使いたいが為に外部のコード落としてきてプロジェクトに組み込んでlibicucoreに忘れずにリンクして…ってしないで済むなぁとか、万が一iOSの内部の実装が変わったりなんかの規約が変わったりしてもおそらく書き換えないで済むだろうなぁという多少の安心感とかはある。ので、これから作るアプリで、4.0以降のみをターゲットにしてる場合は、NSRegularExpressionを使って書こうかなぁなんて思ったりしてたり。
</p>

<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1561677" width="1" height="1" />
]]>
</content:encoded>
</item>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1561287.html">
<title>Objective-Cで正規表現を使う その1</title>
<link>http://blog.livedoor.jp/faulist/archives/1561287.html</link>
<description>
ちょっと前に書こうと思ってて忘れてたネタ。iOSアプリ内で正規表現を使ってごにょごにょしようと思ったらRegexKitLiteを導入するのが一番てっとりばやいのだけど、iOS 3.2以降はFoundation Framework内でも地味に正規表現が使えるようになってきてるのでメモがてら記事にし...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2010-11-11T01:26:20+09:00</dc:date>
<dc:subject>プログラミング</dc:subject>
<content:encoded><![CDATA[<p>
ちょっと前に書こうと思ってて忘れてたネタ。iOSアプリ内で正規表現を使ってごにょごにょしようと思ったら<a href="http://regexkit.sourceforge.net/RegexKitLite/" target="_blank" title="RegexKitLite">RegexKitLite</a>を導入するのが一番てっとりばやいのだけど、iOS 3.2以降はFoundation Framework内でも地味に正規表現が使えるようになってきてるのでメモがてら記事にしておく。
</p>
<h3>NSRegularExpressionSearch</h3>
<p>
Cocoaで文字列中に別な文字列が含まれているかどうかを知りたいときは、NSStringの-rangeOfString:というメソッドを使う。RubyのString#indexみたいな感じで、見付かった文字列がどこにあるかの<strong>位置</strong>を返してくれる。こんな感じ。
</p>
<pre class="prettyprint">
NSString *string = @"I love Udon.";
NSRange match = [string rangeOfString:@"Udon"];
if (match.location != NSNotFound) {
  NSLog(@"Found: %@",[string substringWithRange:match]);
} else {
  NSLog(@"Not Found");
}
</pre>
<p>
これにもう少し細かく色々なオプションを指定できる-rangeOfString:options:というメソッドがあるのだけど、iOS3.2以上のバージョンだとこのオプションにNSRegularExpressionSearchというのが指定できるようになっている。実際に使うときはこう。
</p>
<pre class="prettyprint">
NSString *string = @"1日3食のうち4食はうどんを食べたいと思っている。";
NSRange match = [string rangeOfString:@"[0-9]+食" options:NSRegularExpressionSearch];
if (match.location != NSNotFound) {
  NSLog(@"Found: %@",[string substringWithRange:match]);
} else {
  NSLog(@"Not Found");
}
</pre>
<p>
rangeOfString:に正規表現（の文字列）を渡せるようになってちょっと便利。書式はICU-comaptibleだそうだけど、RegexKitLiteもlibicucoreを使ってるので、RegexKitLiteを使ってた人は得に気にすることなく使えると思う。
</p>
<p>
これだけでも大分マシにはなったんだけど、さっきのサンプルコード見て分かる通り最初にマッチした部分しか取ってこれないし、もしかして-stringByReplacingOccurrencesOfString:withString:options:range:とかにも正規表現使えるのかなとwktkしたのだけど、「You can use this option only with the rangeOfString:... methods.」だそうで。マッチした箇所を全部取ってくるとか置換するとかは別な方法でやるようだ。
</p>
<h3>NSRegularExpression</h3>
<p>
さっきのはNSStringの文字列検索のオプションだったけど、正規表現そのものを扱うNSRegularExpressionというクラスがある。NSRegularExpressionSearchオプションは3.2以降であれば使えるけど、NSRegularExpressionクラスは4.0以降。つまり今までiPadでは使えなかったので、RegexKitLiteを置き換えるには至らなかった。
</p>
<p>
が。そろそろiPad版を含むiOS4.2がリリースされるので、ようやくiPadでも4系の機能が使えるようになるのです。弟の仇をトルノデス。ということで次回はNSRegularExpressionを使った文字列検索を記事にします。予告。
</p>

<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1561287" width="1" height="1" />
]]>
</content:encoded>
</item>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1536624.html">
<title>RubyでMongrel2のハンドラを書いてみる</title>
<link>http://blog.livedoor.jp/faulist/archives/1536624.html</link>
<description>
こないだ、と言っても2週間くらい前の話なんだけど、社内でZeroMQとMongrel2の勉強会をやった。Mongrelと言えば、俺がRails（たしか当時1.2くらいだったと思う）で仕事してた頃にアプリケーションサーバとして使ってたけど、最近だとThinとかPassengerとかUnicornとかの人気...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2010-09-18T16:12:43+09:00</dc:date>
<dc:subject>プログラミング</dc:subject>
<content:encoded><![CDATA[<p>
こないだ、と言っても2週間くらい前の話なんだけど、社内で<a href="http://www.zeromq.org/" title="Fastest. Messaging. Ever. - zeromq" target="_blank">ZeroMQ</a>と<a href="http://mongrel2.org/home" title="mongrel2: mongrel2" target="_blank">Mongrel2</a>の勉強会をやった。Mongrelと言えば、俺がRails（たしか当時1.2くらいだったと思う）で仕事してた頃にアプリケーションサーバとして使ってたけど、最近だとThinとかPassengerとかUnicornとかの人気に押されてついぞ聞かなくなったアレだよなぁ、なんでPerlの会社の勉強会でMongrelなんだろう、と思ってたんだけど、Mongrel2はもはやRailsのアプリケーションサーバじゃなくて、通信にZeroMQなるものを使った汎用的なWebサーバになってたらしい。
</p>
<p>
大分野心的なプロジェクトではあるものの、今はZeroMQもMongrel2も「とりあえず出た」って感じらしく、今年一杯くらいは地雷原を突き進む気がある人だけ触るといいんじゃないかなという話だった。プロダクションで使うようなレベルになるにはもうしばらくかかりそうだけど、今なら各言語の実装も追い付いてないようなので、遊んでおくなら今のうち。いずれMongrel2が大流行したときに「faultierさん、是非本を書いてください！」ってお願いされることを夢見て色々ごにょごにょしてみたよ。
</p>
<h2>準備</h2>
<p>
何はともあれZeroMQとMongrel2をインストールする。とは言っても、<a href="http://mongrel2.org/wiki?name=GettingStarted" title="mongrel2: GettingStarted" target="_blank">Getting Started</a>の通りにインストールするだけ。例に書いてある奴は若干バージョンが古いので、それぞれの最新版を取ってきた方がいいと思う。ちなみにZeroMQはhomebrewにもFormulaがあった。pyzmqは無かったので自分で作るなどした。
</p>
<p>
今回はRubyで試すので、RubyのZeroMQバインディングも入れておく。これは普通にgem install zmqで入るはず。<a href="http://github.com/perplexes/m2r" target="_blank" title="perplexes's m2r at master - GitHub">Rackのハンドラの例</a>の方はffi-rzmqを使ってるんだけど、なんか手元の環境で上手くインストールできなかったのでそっちは試してない。まぁ今回やることにはどっちがどうとかあんまり関係ないのでzmqの方でいいか。
</p>
<p>
そこまでできたら今度はMongrel2の設定を用意する。なんでCで書かれてるはずのMongrel2がやたらとPythonのライブラリ入れまくるんだろうと思ったんだけど、Mongrel2の操作にはm2shというPythonで書かれたスクリプトを使うかららしい。設定の仕方が面白くて、まずはPythonで書かれた設定ファイルを用意して、それをm2shでsqliteのファイルに書き出し、それを使ってMongrelが起動する、というようになってる。だから多分m2sh相当のものをPerlなりRubyなりで用意してしまえば、別にPythonは必要ないはず。まぁ、面倒なので大人しくm2shを使う。設定ファイルはこんな感じにした。
</p>
<pre class="prettyprint">
# m2test.py
from mongrel2.config import *

main = Server(
    uuid ="2f62bd5-9e59-49cd-993c-3b6013c28f05",
    chroot="./",
    access_log="/logs/access.log",
    error_log="/logs/error.log",
    pid_file="/run/mongrel2.pid",
    default_host="localhost",
    name="main",
    port=6767,
    hosts=[
        Host(
            name="localhost",
            routes={
                r'/m2test': Handler(
                    send_spec="tcp://127.0.0.1:9997",
                    send_ident="70D107AB-19F5-44AE-A2D0-2326A167D8D7",
                    recv_spec="tcp://127.0.0.1:9996",
                    recv_ident=""
                )
            }
        )
    ]
)
settings = {"zeromq.threads": 1}
commit([main], settings=settings)
</pre>
<p>
Mongrel2のexamplesに入ってたのを参考にした。なんとなくわかると思うけど、hostsの中のHandlerってやつが今から作るハンドラと通信する為の設定になる。これが出来たら、
</p>
<pre>
$ mkdir run log tmp
$ m2sh init -db m2test.db
$ m2sh load -db m2test.db -config m2test.py
$ m2sh start -db m2test.db -host localhost
</pre>
<p>
とかすると、Mongrel2が立ち上がる。http://localhost:6767/m2testでアクセスすると裏のハンドラに処理が渡るはずだけど、まだ作ってないのでこの時点ではレスポンスが返ってこず、延々待たされる。
</p>
<h3>プロトコルを調べる</h3>
<p>
Mongrel2は内部にアプリケーションサーバを持つわけではなく、基本的にやることはZeroMQを使った通信をするだけ。上に書いた設定だと、ローカルの9997ポートと9996ポートにZeroMQのソケットが用意されて、ハンドラは9997ポートからリクエストを受けとり、処理したら9996ポートにレスポンスを送ってやるようにする。ちなみにこのソケットは別にUnixソケットのことではなく、ファイルを経由したりプロセス内通信したりネットワーク越しに通信したり色々できるらしい。詳しくはZeroMQを調べてみるといいと思う。
</p>
<p>
リクエストのメッセージは、「センダのID コネクションのID パス ヘッダの長さ:ヘッダ,ボディの長さ:ボディ」の形式で飛んでくる。実際にはこんな感じ。
</p>
<pre>
70D107AB-19F5-44AE-A2D0-2326A167D8D7 2 /m2test 542:{"PATH":"/m2test","METHOD":"GET","VERSION":"HTTP/1.1","URI":"/m2test","PATTERN":"/m2test","Accept":"application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5","Accept-Charset":"Shift_JIS,utf-8;q=0.7,*;q=0.3","Accept-Encoding":"gzip,deflate,sdch","Accept-Language":"ja,en-US;q=0.8,en;q=0.6","Cache-Control":"max-age=0","Connection":"keep-alive","Host":"localhost:6767","User-Agent":"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.62 Safari/534.3"},0:,
</pre>
<p>
んで、レスポンスは「センダのID コネクションIDの長さ:コネクションのID HTTP/1.1 ステータスコード ステータスメッセージ ヘッダ ボディ」という形式で返す。例えばokと返すだけのレスポンスならこんな感じ。
</p>
<pre>
70D107AB-19F5-44AE-A2D0-2326A167D8D7 1:3, HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 2

ok
</pre>
<p>
つまりハンドラが何をすればいいかというと、9997ポートから来た上の形式のメッセージをパースしてリクエストを判断し、処理結果を下の形式に加工して9996ポートに送ってやる、とこういうことです。
</p>
<h3>ミニマムなハンドラを作る</h3>
<p>
そしてこちらが調理済みのハンドラになります（料理番組風）。
</p>
<pre class="prettyprint">
#!/usr/bin/env ruby
# coding: utf-8

require 'zmq'

sid   = "70D107AB-19F5-44AE-A2D0-2326A167D8D7"
con   = ZMQ::Context.new
rsock = con.socket(ZMQ::UPSTREAM)
ssock = con.socket(ZMQ::PUB)

rsock.connect('tcp://127.0.0.1:9997')
ssock.connect('tcp://127.0.0.1:9996')
ssock.setsockopt(ZMQ::IDENTITY, sid)

loop do
  str = rsock.recv
  sender, conn_id, path, str = str.split(' ', 4)
  ssock.send "#{sender} #{conn_id.size}:#{conn_id}, HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 2\r\n\r\nok", 0
end
</pre>
<p>
こいつとMongrel2を起動させといて、http://localhost:6767/m2testにアクセスすると、okとそっけない返事が返ってくる、というだけのハンドラ。「今夜は帰したくない」とか「結婚しよう」とかいうリクエストを投げても「ok」って返してくれます。まぁ「別れよう」でも「ok」って返ってきますけど。
</p>
<p>
上のコードだとreceiveしたメッセージのセンダIDとコネクションIDしか見てないけど、ヘッダとボディをパースすればRackアプリに渡すENVを作ることができるし、Rackアプリが返すレスポンスの仕様は決まってるのでそれをMongrel用のレスポンスメッセージに変換してやるのも簡単にできるわけで、そこまですればMongrel2と連携できるRackハンドラが作れる、というわけ。あとは、このハンドラ自身でリクエストを処理しなくても、スレッドを一杯作ってその中でRackアプリの処理をさせて、ハンドラ自身はプロセス内通信でリクエスト/レスポンスの中継役になってやるとかすれば、ワーカをがんがん増やせるとか、そんな風にもできる。
</p>
<p>
あと、Mongrel2とハンドラ間はZeroMQで通信してるだけなので、お互いが生きてるか死んでるか、何個あるのか一個もないのか、などについて何も感知しない。ので、急に負荷が上がったらプロセスやサーバを増やして緊急投入してもMongrel側の設定は変更する必要なかったりとか、デプロイ時にはもう一個ハンドラプロセスを立ち上げて起動し終えたら古いプロセスを殺すとかしてやればダウンタイム無しでデプロイできたりとか、するんじゃないのかな。多分。
</p>
<p>
あ、ちなみに今回作ったものは<a href="http://gist.github.com/585455" target="_blank">Gist</a>に置いといた。
</p>

<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1536624" width="1" height="1" />
]]>
</content:encoded>
</item>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1536261.html">
<title>うどんげQuineに対抗して遊んでみた</title>
<link>http://blog.livedoor.jp/faulist/archives/1536261.html</link>
<description>
「RubyKaigiが終わったら真面目にやろう」とか言ってたくせに、中々やる暇無くて放置してたら大変に分かりやすい作り方講座が出てしまった上、弾さんまで乗ってきてしまって完全にタイミングを逃したfaultierですこんばんは。

悔しいので対抗してみる

うどんげが出たなら...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2010-09-17T19:06:27+09:00</dc:date>
<dc:subject>プログラミング</dc:subject>
<content:encoded><![CDATA[<p>
<a href="http://blog.livedoor.jp/faulist/archives/1527532.html" title="RubyKaigi2010に行ってきたよ">「RubyKaigiが終わったら真面目にやろう」とか言ってた</a>くせに、中々やる暇無くて放置してたら<a href="http://d.hatena.ne.jp/mickey24/20100915/ruby_udonge_quine" target="_blank" title="RubyでうどんげQuine(とAA型Quineの作り方講座)">大変に分かりやすい作り方講座</a>が出てしまった上、<a href="http://blog.livedoor.jp/dankogai/archives/51519405.html" target="_blank" title="perl - Quine.pm で(ほぼ)あらゆるPerl Scriptをquineに">弾さんまで乗ってきてしまって</a>完全にタイミングを逃したfaultierですこんばんは。
</p>
<h3>悔しいので対抗してみる</h3>
<p>
うどんげが出たならてゐもいてもいいだろう、ということでてゐ。AAは上記の記事同様<a href="http://uguu.org/src_tewi_ps.html" title="tewi.ps - 因幡てゐ">こちら</a>を使わせてもらった。初春もいいなーと思ったんだけど表示してみたら大き過ぎて自分のターミナルで表示できなかったのでやめといた。コードはこんな感じ。
</p>
<pre style="color:#ffffff;background-color:#000000;font-size:0.8em;">
# tewi.rb
eval$s=%w't=true;e="eval($s=join("&lt;&lt;34&lt;&lt;34&lt;&lt;",qw{$t=1;$s=~/"&lt;&lt;92&lt;&lt;"[[0-9,]+"&lt;&lt;92&lt;
&lt;"]/;$n=eval($&);$e=          "&lt;&lt;39&lt;&lt;"eval$s=%w"&lt;&lt;39&lt;&lt;    ".chr(39)."&lt;&lt;39&lt;&lt;($s+(
($s.length&gt;1756)?"   ":"#"&lt;&lt;$    s.gsub(/[^0-9a-zA-   Z]/,      "")[0,(1755-$s.l
ength)]));e[-312,  311]=""&lt;&lt;39&lt;&lt;   ";$e.=          ("&lt;&lt;39&lt;&lt;"#"&lt;   &lt;39&lt;&lt;".substr(
join("&lt;&lt;39&lt;&lt;39&lt;&lt;  ",split(/[^0-9a-z              A-Z]/,$s)),0,(200  9-length($s)
))).chr(39)."&lt;&lt;  39&lt;&lt;".join"&lt;&lt;39&lt;&lt;";@o   =       map{$t=!$t;split(//  ,((!$t)?su
bstr($e,0,$_,"&lt; &lt;39&lt;&lt;39&lt;&lt;"):chr(32)x$_)         )}@$n;for(1..34){spli  ce(@o,$_*
81,0,chr(10))}  ;print(join("&lt;&lt;39&lt;&lt;39&lt;&lt;         ",@o).chr(10      )    )}))";o=[
101,10,24,4,40 ,3,8,4,18,3,4,6,33,2,13           ,3,6,10,1            2,3,30,2,1
7,14,17,2,27,  2,21,3,1,7,19    ,2                     ,              25,1,23,9,
21,2,23,2,23,  9,12,6,1,                           4                   ,23,1,23,
11,9,12,23,2, 13,4,2,2                   1                             ,1,14,23,
2,9,27,1,19   ,22,1,8         ,                  1    9   ,             1,29,20,
3,7,9,1,      18,1,4      ,         1        ,          3           ,    1,13,16
,6,6,6,      1,9,1,8                          ,1           ,    1    0,    1,11,
1,4,14,      6,7,26,                   2,1     1,1,4   ,1              ,4,   2,4
,12,6,7     ,19,3,5,             5,  3,2,14,3  ,3,1   0,5               ,8,13  ,
2,2,8,    2,4,3,3,15,          5,2    ,7    ,  4,11 ,10,3,              4,2,4,1,
2,4,    1,6,14,12,4,1      3  ,   6,1,     2,1,3,4,5,18,3,1,3   ,   1   ,3,12,3,
12,1   3,5,1,2,4,15             ,1,1, 11    ,10,3,14,12,6,1 ,           2,5,13,2
,1   ,1,1,6,12,2,16            ,11,7, 8,     13,2,1,7,13,1  , 1      8,10,8,6,14
,  1,2,10,10,1,20,1           2,5,3,1        2,1,6,13,7,2,  1       9,13,26,14,7
, 2,16,16,11,1,13,14          ,8,2,14,      16,13,7,5,15,1 0,          3,8,10,1,
8 ,25,10,1,4,13,27,23,            12,4,   2,12,5,1,1,1 ,8,1,1             3,17,1
0  ,1,3,5,1,12,14,4,8,             4,10,2,13,25,2,3,9,4,2,1,3              ,15,1
3,  33,4,5,1,4,3,20,                8,182].map{ |i|t=!t;((!t)              ?e.sl
ice  !(0,i):32.chr*                i)}.join;1.up       to(35               ){|i|
o[(i*   81)-1,0]          =        10.chr};puts(o)#ttrueeeva          l    sjoin
3434qwt1                           s920992nevale39evalsw39            chr3  939s
slength1     7 5        6             sgsub09azAZ01755s          l   ength e3123
1139e39              39su        bstr          jo             #t1s09nevaleevalsw
chr39tt  rue         eeva  l   sjoin3434qwt1s9             20992nevale39evalsw39
chr3939sslen    gth17 56sg   sub09azAZ01755slengt        he31231139e3939substrjo
in3939split09azAZs02009lengthschr3939join39omapttsplittsubstre03939chr32xnfor134
spliceo810chr10printjoin3939ochr10o10110244403841834633213361012330217141'.join
</pre>
<p>
<a href="http://gist.github.com/582383" target="_blank" title="gist: 582383 -  GitHub">Gist</a>にも置いてあります。はい。
</p>
<h3>Quineじゃない件</h3>
<p>
上のコードをコピペしてRubyに実行させると何やらコードを吐くので、それをさらにRubyに流し込んで実行させてやると…なんということでしょう！エラーを吐くではありませんか！Quineになってねぇじゃねーか、このド低能が！
</p>
<pre style="color:#ffffff;background-color:#000000;font-size:0.8em;">
# tewi.pl（tewi.rbの出力結果）
eval($s=join("",qw{$t=1;$s=~/\[[0-9,]+\]/;$n=eval($&);$e='eval$s=%w'.chr(39).'t=
true;e="eval($s=join(          "&lt;&lt;34&lt;&lt;34&lt;&lt;",qw{$t=1;$s=    ~/"&lt;&lt;92&lt;&lt;"[[0-9,]+"&lt;&lt;
92&lt;&lt;"]/;$n=eval($&)   ;$e="&lt;&lt;3    9&lt;&lt;"eval$s=%w"&lt;&lt;39   &lt;&lt;".      chr(39)."&lt;&lt;39&lt;&lt;
($s+(($s.length&gt;17  56)?"":"#"&lt;&lt;$   s.gsub          (/[^0-9a-zA-   Z]/,"")[0,(17
55-$s.length)]));  e[-312,311]=""&lt;&lt;3              9&lt;&lt;";$e.=("&lt;&lt;39&lt;&lt;  "#"&lt;&lt;39&lt;&lt;".
substr(join("&lt;&lt;3  9&lt;&lt;39&lt;&lt;",split(/[^0-9   a       -zA-Z]/,$s)),0,(200  9-length(
$s)))).chr(39)." &lt;&lt;39&lt;&lt;".join"&lt;&lt;39&lt;&lt;";@o         =map{$t=!$t;split(//,  ((!$t)?s
ubstr($e,0,$_,"  &lt;&lt;39&lt;&lt;39&lt;&lt;"):chr(32)x$_         ))}@$n;for(1      .    .34){spl
ice(@o,$_*81,0, chr(10))};print(join("&lt;           &lt;39&lt;&lt;39&lt;&lt;            ",@o).chr
(10))}))";o=[1  01,10,24,4,40    ,3                     ,              8,4,18,3,
4,6,33,2,13,3,  6,10,12,3                           ,                   30,2,17,
14,17,2,27,2,2 1,3,1,7,                   1                             9,2,25,1
,23,9,21,2,2   3,2,23,         9                  ,    1   2             ,6,1,4,
23,1,23,1      1,9,12      ,         2        3          ,           2    ,13,4,
2,21,1,1      4,23,2,                          9,           2    7    ,1    ,19,
22,1,8,1      9,1,29,                   20,     3,7,9   ,1              ,18   ,1
,4,1,3,1     ,13,16,6             ,6  ,6,1,9,1  ,8,1   ,10               ,1,11  
,1,4,14    ,6,7,26,2,1          1,1    ,4    ,  1,4, 2,4,12              ,6,7,19
,3,5,    5,3,2,14,3,3,      1  0   ,5,8     ,13,2,2,8,2,4,3,3,   1   5   ,5,2,7,
4,11,   10,3,4,2,4,1             ,2,4, 1,    6,14,12,4,13,6, 1           ,2,1,3,
4,5   ,18,3,1,3,1,3,            12,3,1 2,     13,5,1,2,4,15  , 1      ,1,11,10,3
,1  4,12,6,1,2,5,13,           2,1,1,1        ,6,12,2,16,11  ,       7,8,13,2,1,
7, 13,1,18,10,8,6,14,          1,2,10,1      0,1,20,12,5,3, 12          ,1,6,13,
7, 2,19,13,26,14,7,2,16            ,16,1   1,1,13,14,8, 2,14,1             6,13,
7,  5,15,10,3,8,10,1,8,             25,10,1,4,13,27,23,12,4,2,              12,5
,1,  1,1,8,1,13,17,10                ,1,3,5,1,12 ,14,4,8,4,10,              2,13
,25,  2,3,9,4,2,1,3,                15,13,33,4,5,       1,4,3               ,20,
8,182]   .map{|i|          t        =!t;((!t)?e.slice!(0,i):3          2    .chr
*i)}.join                           ;1.upto(35){|i|o[(i*81)            -1,0  ]=1
0.chr};pu     t s        (             o)#ttrueeevalsjoi          n   3434q wt1s
920992ne              vale        39ev          al             sw39chr3939ssleng
th1756sg  sub         09az  A   Z01755slengthe3             1231139e3939substrjo
';$e.=('#'.su    bstr( join   ('',split(/[^0-9a-zA        -Z]/,$s)),0,(2009-leng
th($s)))).chr(39).'.join';@o=map{$t=!$t;split(//,((!$t)?substr($e,0,$_,''):chr(3
2)x$_))}@$n;for(1..34){splice(@o,$_*81,0,chr(10))};print(join('',@o).chr(10))}))
</pre>
<p>
ええはい。良く見てもらえると分かると思うけど、実はこのコードは自分自身を出力してない。何を出力してるかと言うと、Perlのコードを出力している。んで、そのPerlのコードは何をするかというと、最初のRubyのコードを出力する。つまり、最初のコードは「『このRubyのコードを出力するPerlのコード』を出力するRubyのコード」で、後のコードは「『このPerlのコードを出力するRubyのコード』を出力するPerl」のコードになっていて、お互いがお互いの自分自身を相手に出力させるという、「人は一人では生きて行けないんだ」というメッセージ性を持った難読コードなわけです。まぁ嘘です。RubyもPerlもやられちゃったので、そのまま真似してもつまらなかっただけです。こんな感じで遊んでみるといいよ！
</p>
<pre>
$ cat tewi.rb | ruby | perl | ruby | perl | ruby
</pre>
<h3>何をしたの？</h3>
<p>
実のところ別になんということはなくて、やってることは基本的にはうどんげQuineと一緒。配列リテラルとjoinとevalを使ったQuineはPerlでもRubyでも全く同じ要領でできるので、自分自身を吐く代わりに相手のコードを吐くように書き換えると簡単に行ったり来たりできるようになる。
</p>
<pre class="prettyprint">
# 任意に加工できるRubyコードを出力する、
# 任意に加工できるPerlコードを出力する、
# 任意に加工できるRubyコードの例
eval$s=%w'puts("eval(join("&lt;&lt;39&lt;&lt;39&lt;&lt;",qw{print("&lt;
&lt;39&lt;&lt;"eval$s=%w"&lt;&lt;
39&lt;&lt;".chr(39)."&lt;&lt;39&lt;
&lt;$s&lt;&lt;39&lt;&lt;".chr(39)."&lt;&lt;39&lt;&lt;".join"&lt;&lt;39&lt;
&lt;")}))")'.join
</pre>
<p>
あとは、二言語分のコードが一つのAAの中に入ることになるし、PerlとRubyだとリテラルがそっくりなのでデータ部は共有したいので生のデータを書きたい、となると流石に元のAAデータだと文字数の制限がキツいから、反転して白抜きのAAにした。ちなみに、てゐの方はうどんげのとはAAデータ持ち方は換えてる（元記事のロジックをPerlで実装するのが面倒だっただけ）。
</p>
<p>
あとはまぁ、出力時に尻尾に適当なゴミを付けて文字数調整してるんだけど、文字列リテラルの入れ子が簡単に崩れるので面倒臭いとかそんな程度。chr(39)濫用しまくり。あとドットがRubyのドットなのかPerlのドットなのか良くわかんなくなったりします。
</p>
<h3>まとめ</h3>
<p>
途中で「俺は一体何をやってるんだ」と思ったら多分負けなんだと思います。一度ベースができちゃうと後はデバッグと文字数の調整が面倒なだけで、割と簡単にできます。あと段々哲学的な気分になります。
</p>
<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1536261" width="1" height="1" />
]]>
</content:encoded>
</item>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1527532.html">
<title>RubyKaigi2010に行ってきたよ</title>
<link>http://blog.livedoor.jp/faulist/archives/1527532.html</link>
<description>
まぁなんだ、本来なら書くべきことは一杯あるんだけども、眠くてまとめてる時間が無かったので、これだけ書いておきますね。


eval(%w(eval(a=&quot;eval(%w(b=[35,35,35,
32,34,69   ,11   5,1   11,116,101,1,
14,105,9   9,  32,79   ,98,102,117,
115,99,9      7,116,   10...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2010-08-29T01:59:57+09:00</dc:date>
<dc:subject>レポート・レビュー</dc:subject>
<content:encoded><![CDATA[<p>
まぁなんだ、本来なら書くべきことは一杯あるんだけども、眠くてまとめてる時間が無かったので、これだけ書いておきますね。
</p>
<pre style="background-color:#000;color:#fff">
eval(%w(eval(a="eval(%w(b=[35,35,35,
32,34,69   ,11   5,1   11,116,101,1,
14,105,9   9,  32,79   ,98,102,117,
115,99,9      7,116,   101,100,32,82
,117,98,   121,32,80   ,1        14,11
1,103,11   4,97,109,      109,1   05,
110,103,   34,32,105     ,115,3   2,103,
11   4,1   01,97,116   ,  33];p   uts
(b   .pa   ck('C*'))   ).        join)
;printf(\"eval(a=%p)\n\",a)")).join)
</pre>
<p>
うん、あれだね、中途半端なのはよーく分かってるんだ。日本語で出したかったしちゃんと2回目以降も整形されたコード出すべきなんだけど、えーと、RubyKaigiが終わったら真面目にやろうとは思うんだ。衝撃を受けたって気持ちだけでも伝わるといいな。
</p>
<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1527532" width="1" height="1" />
]]>
</content:encoded>
</item>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1522297.html">
<title>Web Storageで遊んでみた</title>
<link>http://blog.livedoor.jp/faulist/archives/1522297.html</link>
<description>
.jakigan {
    background-color: #999;
    color: #fff;
    margin: 1em;
    padding: 1em;
    border-radius: 10px;
    -webkit-border-radius: 10px;
    -moz-border-radius: 10px;
}


本当に遊んでみただけだけど、例えばこんなの。



訪問回数0
メッセージな...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2010-08-16T19:57:35+09:00</dc:date>
<dc:subject>プログラミング</dc:subject>
<content:encoded><![CDATA[<style scoped>
.jakigan {
    background-color: #999;
    color: #fff;
    margin: 1em;
    padding: 1em;
    border-radius: 10px;
    -webkit-border-radius: 10px;
    -moz-border-radius: 10px;
}
</style>
<p>
本当に遊んでみただけだけど、例えばこんなの。
</p>
<div id="lsdisplay" class="jakigan">
<dl>
<dt>訪問回数</dt><dd id="lscount">0</dd>
<dt>メッセージ</dt><dd id="lsmessage">なんか多分エラってる</dd>
</dl>
</div>
<script>
//<![CDATA[
(function(){
    var display = document.getElementById('lsdisplay');
    if (typeof(localStorage) == 'undefined') {
        display.innerHTML = '貴様は既に漆黒の闇に囚われている…抜け出すことなどできぬ…';
    } else {
        var count   = parseInt(localStorage.devilCount || 0) + 1;
        var message = document.getElementById('lsmessage');
        var button  = document.createElement('button');
        if ((count % 15) == 0) {
            message.innerHTML =
                'くっ…奴が…来る…早く逃げろ…俺の理性が残ってるうちに…ッ！'
                + '<br />'
                + '我を目覚めさせてしまうとは…愚かな者共よ…無に帰してしまうがよい…！';
            button.innerHTML  = '世界を滅ぼす';
        } else if ((count % 5) == 0) {
            message.innerHTML = '我を目覚めさせてしまうとは…愚かな者共よ…無に帰してしまうがよい…！';
            button.innerHTML  = '世界を滅ぼす';
        } else if ((count % 3) == 0) {
            message.innerHTML = 'くっ…奴が…来る…早く逃げろ…俺の理性が残ってるうちに…ッ！'
            button.innerHTML  = '邪気眼を封印する';
        } else {
            message.innerHTML = 'あなたは' + count + '回目の訪問ですね';
            button.innerHTML  = '訪問回数をリセットする';
        }
        document.getElementById('lscount').innerHTML = count;
        button.onclick = function(){
            display.innerHTML = 'フハハハハ…世界は無に帰した…';
            delete localStorage.devilCount;
        };
        display.appendChild(button);
        localStorage.devilCount = count;
    }
})();
//]] >
</script>
<noscript>JS有効にしといてね</noscript>
<p>
リロードする度に訪問回数がインクリメントされて、3の倍数の時には「奴」が疼き出し、5の倍数の時には「奴」が目覚めてしまうという、訪問回数カウンタとFizzBuzzを組み合わせたしょーもない何かです。まぁこれだけだと「そうですね…で？」みたいな話で終わりなんだけど、一応無駄にlocalStorageを使ってみてます。ちなみに「貴様は既に漆黒の闇に囚われている…」云々て表示されてる場合はlocalStorageが使えないブラウザなので、違うのが入ってたらそれで試してみてください。Firefox、Safari、Chromeあたりの最新版だと動いてるはず。多分。Operaも動いてるっぽい。
</p>
<h3>ろーかるすとれーじ？</h3>
<p>
Web Storageって何なのって人は<a href="http://www.html5.jp/trans/w3c_webstorage.html" target="_blank">HTML5.JPにある資料</a>でも読んでもらうとして、まぁ大雑把に言うとブラウザにKey-Valueストレージが付いててなってJSから簡単に扱えるようになってるよって話。使い方は簡単。単に、こんな風にすればいいだけ。
</p>
<pre class="prettyprint">
localStorage.devilCount = 1;
</pre>
<p>
これでlocalStorageにdevilCountってKeyで'1'って値が保存されて、同じドメインのページからだったらlocalStorage.devilCountって形で参照できるようになって、ブラウザが終了しても永続化されてるという。かんたん。リロードするだけじゃなく、試しにブラウザ閉じてもう一度この記事を見てもっても、ちゃんとカウントアップされてるのが分かるはず。べんり。この例は流石にしょーもなさすぎるけど、まぁ上手く使えば色々できるよね。
</p>
<p>
こんな風にアクセスすることもできる。
</p>
<pre class="prettyprint">
localStorage['devilCount'] = 1;
localStorage.setItem('devilCount', 1);
localStorage.getItem('devilCount');
</pre>
<p>
消すときはこんな感じ。
</p>
<pre class="prettyprint">
delete localStorage.devilCount; // or localStorage.removeItem('devilCount');
localStorage.clear();           // 全て消す
</pre>
<p>
ちなみに、localStorageに保存されてる値は文字列として保存されるので、文字列以外の値を入れたければJSONとしてシリアライズ/デシリアライズするとかして使う必要がある。上の例ではparseIntして数値として扱ったりしてる。
</p>
<p>
あと、localStorageはorigin単位で別のストレージになることに注意。ドメインやポートが変わるとlocalStorageも変わるので、例えばhttp://image.blog.livedoor.jpとかhttps://blog.livedoor.jpとかからはアクセスできないし、一方で別のブログ（例えばhttp://blog.livedoor.jp/faulist-mobile/とか）からはアクセスできてしまうはず。
</p>
<h3>せっしょんすとれーじ</h3>
<p>
同じものをsessionStorageで実装してみる。インターフェースは全く同じなので、ソース見てもらえれば分かるけど基本的にはlocalStorageとしてたものをsessionStorageに書き換えるだけ。
</p>
<div id="ssdisplay" class="jakigan">
<dl>
<dt>訪問回数</dt><dd id="sscount">0</dd>
<dt>メッセージ</dt><dd id="ssmessage">なんか多分エラってる</dd>
</dl>
</div>
<script>
//<![CDATA[
(function(){
    var display = document.getElementById('ssdisplay');
    if (typeof(sessionStorage) == 'undefined') {
        display.innerHTML = '貴様は既に漆黒の闇に囚われている…抜け出すことなどできぬ…';
    } else {
        var count   = parseInt(sessionStorage.devilCount || 0) + 1;
        var message = document.getElementById('ssmessage');
        var button  = document.createElement('button');
        if ((count % 15) == 0) {
            message.innerHTML =
                'くっ…奴が…来る…早く逃げろ…俺の理性が残ってるうちに…ッ！'
                + '<br />'
                + '我を目覚めさせてしまうとは…愚かな者共よ…無に帰してしまうがよい…！';
            button.innerHTML  = '世界を滅ぼす';
        } else if ((count % 5) == 0) {
            message.innerHTML = '我を目覚めさせてしまうとは…愚かな者共よ…無に帰してしまうがよい…！';
            button.innerHTML  = '世界を滅ぼす';
        } else if ((count % 3) == 0) {
            message.innerHTML = 'くっ…奴が…来る…早く逃げろ…俺の理性が残ってるうちに…ッ！'
            button.innerHTML  = '邪気眼を封印する';
        } else {
            message.innerHTML = 'あなたは' + count + '回目の訪問ですね';
            button.innerHTML  = '訪問回数をリセットする';
        }
        document.getElementById('sscount').innerHTML = count;
        button.onclick = function(){
            display.innerHTML = 'フハハハハ…世界は無に帰した…';
            delete sessionStorage.devilCount;
        };
        display.appendChild(button);
        sessionStorage.devilCount = count;
    }
})();
//]] >
</script>
<noscript>JS有効にしといてね</noscript>
<p>
何回かリロードしたあと、一旦ウインドウを閉じる、別のウインドウで同じページを開く、などしてみると、違いが分かると思う。上のlocalStorageを使ってる方はブラウザ終了させようが別のウインドウで開こうがその度にインクリメントされるけど、こっちは「このウインドウが開かれてから閉じられるまで」の間だけ値が保存されるので、ブラウザを終了させると当然リセットされるし、別のウインドウで開くとまた1からカウントされる。
</p>
<p>
インターフェースが全く同じなのは楽でいいな。次は何やってみよう。コミュニケーションAPIかな。
</p>
<h3>補足</h3>
<p>
セッションストレージはブラウザ閉じたら消えるけど、ローカルストレージの方は消えないので、気持ち悪い場合はこの記事中の「訪問回数をリセットする」ボタンを押すか、SafariやChrome、Operaだと開発者向けのツールから消せる。もう一度記事見たらまた保存されちゃうけど。Firefoxだとどっから消すんだろうな。
</p>
<p>
あと、上記のコードを自分で書いて試してみるときは、Firefoxの場合はローカルファイルを開いても駄目で、Webサーバを介して見る必要がある。ローカルファイルとして開いた場合、ローカルストレージはリロードする度にリセットされてしまって値が保存されないし、セッションストレージは無効になっててエラーが出る。Chrome、Safariでは特に問題ない。
</p>
<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1522297" width="1" height="1" />
]]>
</content:encoded>
</item>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1511657.html">
<title>iPadでキーボードの表示に合わせたViewのサイズ変更</title>
<link>http://blog.livedoor.jp/faulist/archives/1511657.html</link>
<description>
どうも。「いらないって言ってたのに開発機をいじってたら欲しくなっちゃいましたの法則」が発動してiPadも先週結局買っちゃった僕です。


iPad買ったら当然JailBreakしてターミナルでコード書きまくってやる、と思ってたんだけど、いきなりmobileterminalが動かなくて躓く...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2010-07-24T16:26:23+09:00</dc:date>
<dc:subject>プログラミング</dc:subject>
<content:encoded><![CDATA[<p>
どうも。「<a href="http://blog.livedoor.jp/faulist/archives/997831.html">いらないって言ってたのに開発機をいじってたら欲しくなっちゃいましたの法則</a>」が発動してiPadも先週結局買っちゃった僕です。
</p>
<p>
iPad買ったら当然JailBreakしてターミナルでコード書きまくってやる、と思ってたんだけど、いきなりmobileterminalが動かなくて躓く。ふぁっきん。とは言え自分で修正するとか新しく作るとかまでする気も起きず、あーそうだJSとかHTMLくらいだったらiPad上で書いたりデバッグしたりできるエディタあったよねーと思って、AppStoreで探してみたらちらほら見付かるも、コレ、というのはまだ無い様子。当たり前だけど、Syntax Highlightができるメモ帳程度なんだよなー。
</p>
<h3>エディタのViewを作る</h3>
<p>
ここで「タッチインターフェースを有効活用したコードエディタ」ってのを思い付いたら一時代築けそうなんだけど、別に今のところなんかアイディアがあるわけでもないので、誰かが作ればいいなー、もしくはアイディアくれるといいなーとか言うだけ言っておいて、とりあえずごく普通のテキスト入力画面の話をする。
</p>
<p>
例えば
</p>
<ul>
<li>画面いっぱいにTextViewが表示されてて</li>
<li>Viewが表示されたらTextViewにフォーカスが当たるようにして</li>
<li>キーボードが表示されたらそれに合わせてViewをリサイズもしくはスライドさせて</li>
<li>キーボードが非表示になったらまたViewをリサイズもしくはスライドさせる</li>
</ul>
<p>
ようなごく普通のテキスト入力画面を作りたいとする。
</p>
<p>
TextViewにフォーカスを当てるのは簡単で、例えばViewControllerのviewDidAppearとかの中でTextViewのresignFirstResponderメソッドを呼んでやればいい。そうするとTextViewにフォーカスが当たってキーボードがせりあがってくる。キーボードのサイズは、UIKeyboard(Will|Did)(Show|Hide)Notificationていう名前でキーボードの表示、非表示の際に通知が飛ぶので、それをViewControllerで受けてViewをいじってやればいい（この記事とかが参考になった: <a href="http://www.lancard.com/blog/2010/04/06/dont-want-hide-uitextview-behind-keyboard/" title="画面いっぱいのUITextViewがキーボードに隠れないようにする ? LANCARD.LAB｜ランカードコムのスタッフブログ" target="_blank">画面いっぱいのUITextViewがキーボードに隠れないようにする ? LANCARD.LAB｜ランカードコムのスタッフブログ</a>）。リンクの記事だとアニメーションさせてるけど、UIKeyboardDidShowNotificationを受けて単にself.view.frameの値を変えてやるだけでもそれっぽくなる。
</p>
<h3>あれ、Viewが消えた</h3>
<p>
iPhone/iPod Touchだとこれで上手く行くんだけど、実はiPadだとこのままだと上手く行かない。どうなるかというと、「日本語キーボードと英語キーボードを切り換えるとViewがどんどん小さくなる、消える」みたいなことになる。これ何でなのかなと思ったら、iPadだと日本語キーボードのときはキーボードの上に変換候補を表示するバーが出るのでその分だけ英語キーボードと表示領域のサイズが違うんだけど、表示領域のサイズが変わる度にUIKeyboard(Will|Did)ShowNotificationが通知される。前述のコードだとkeyboardWillShow:が呼ばれる度にキーボードの高さの分だけViewの高さを縮めてるので、連続でkeyboardWillShow:が呼ばれちゃうとどんどん小さくなってしまうという話。
</p>
<p>
これを防ぐには、単にself.view.frame.size.heightからキーボードの高さ分丸ごと引くんじゃなくて、UIKeyboardFrameBeginUserInfoKeyでキーボードの表示領域変更前の、UIKeyboardFrameEndUserInfoKeyで変更後のサイズが取れるので、その差分を取ってViewのサイズを調整してやる必要がある。例えばこんな感じ。
</p>
<pre class="prettyprint">
- (void)keyboardWillShow:(NSNotification *)notification {
  NSDictionary *userInfo = [notification userInfo];
  UIView *superview      = self.view.superview;
  CGRect beginRect       = [superview convertRect:[[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue] fromView:nil];
  CGRect endRect         = [superview convertRect:[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue] fromView:nil];
  CGRect superviewFrame  = superview.frame;
  CGRect viewFrame       = self.view.frame;

  viewFrame.size.height -=
    (beginRect.origin.y &gt; superviewFrame.size.height) // キーボードが表示されてないときは origin.y が全体の表示領域より下になってる
    ? endRect.size.height
    : endRect.size.height - beginRect.size.height;

  // 他にもやることがあればごにょごにょ

  self.view.frame = viewFrame;
}
</pre>
<p>
hideの方は連続で呼ばれることはないのでそのままでも使えるけど、これも「既に非表示になってたら処理をスキップする」とかちゃんと入れといた方がいいです。あと、iPhoneSDK 3.2からUITextFieldやUITextViewにカスタムキーボードを付けてあげることができるようになったけど、iPhoneではまずやらないと思うけどiPadでは「それぞれの入力欄にそれぞれのカスタムキーボードを設定してあり、しかも全部形が違う」みたいなこともやろうと思えばできる。そうなるとコードのカオス度が格段に上がるし、上記のコードだとまた残念なことになるのでご注意を。
</p>
<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1511657" width="1" height="1" />
]]>
</content:encoded>
</item>
<item rdf:about="http://blog.livedoor.jp/faulist/archives/1487948.html">
<title>久々にブログのデザインを変えた</title>
<link>http://blog.livedoor.jp/faulist/archives/1487948.html</link>
<description>
まぁ、表題通りです。たまにはHTMLとかCSSとか書かないと忘れかねないのと、単にそろそろ前のデザインに飽きたので。


無駄にCSSの独自拡張とかHTML5の要素とか使ってるのでIEだとどうなってることか怪しいもんですが、アクセス解析見る限りだとこのブログの読者って、Mac...</description>
<dc:creator>faulist</dc:creator>
<dc:date>2010-06-14T00:57:22+09:00</dc:date>
<dc:subject>告知</dc:subject>
<content:encoded><![CDATA[<p>
まぁ、表題通りです。たまにはHTMLとかCSSとか書かないと忘れかねないのと、単にそろそろ前のデザインに飽きたので。
</p>
<p>
無駄にCSSの独自拡張とかHTML5の要素とか使ってるのでIEだとどうなってることか怪しいもんですが、アクセス解析見る限りだとこのブログの読者って、Macが4割越えるとかWinXPのChrome5のシェアがIE7を上回ってるとか、大分愉快なことになってるので気にしません。今んとこさらっと見て、Safari5(Mac)、Chrome5(Ubuntu/Mac)、Mobile Safari(iOS 4)あたりだったらおそらくそれなりにまともに見られるのを確認済み。<del>Firefox3.6(Mac)だとなんかindexとかが微妙におかしくなる。うーん？</del><ins>タグ書き間違えてただけだった。Firefoxでも大丈夫そう。</ins>
</p>
<p>
あと、Opera10だとかなり残念なことになるんでそのうちなんとかしようかなと思ってるけど、シェアで言うと<strong>Camino以下</strong>なので手が空いたらやる感じ。…っていうかCaminoって使ってる人いるんだ…Safariが安定してなかった頃ならともかく、今わざわざCaminoを使う意味ってあるんだろうか…。
</p>
<a href="http://livedoor.blogimg.jp/faulist/imgs/4/c/4c6e9f80.png" target="_blank" title="Operaくんどこ行ってもうたんや…"><img src="http://livedoor.blogimg.jp/faulist/imgs/4/c/4c6e9f80-s.png" width="399" height="168" alt="ブラウザシェア" class="pict" title="Operaくんどこ行ってもうたんや…"  /></a>
<p>
うん、なんていうかね、久々に真面目にマークアップとかデザインとかしてたら疲れた。俺、これでも昔はWebデザイナになりたかったんだぜ…。多分あんま向いて無いと思う。正直テンプレ書きながらサーバの方のコード書き直したい誘惑に駆られたのは内緒。
</p>
<h3>追記</h3>
<p>
Google Font APIを（と言うよりは特定の形式のWebフォントを）使ったときに、ブラウザによって挙動がまちまちで、とても残念なことになるブラウザが多いのでやめた（iOSのSafariとかOperaとか。挙動としては間違ってないようだけどね）。まぁ試したかっただけなのであんまりそこで頑張らないで、対応してるブラウザが増えたり日本語フォントが提供されたりすればいいなーって望む程度に留めとく。
</p>
<img src="http://counter2.blog.livedoor.com/c?ro=1&act=rss&output=no&id=2548817&name=faulist&pid=1487948" width="1" height="1" />
]]>
</content:encoded>
</item>

</rdf:RDF>

