2011年8月5日金曜日

自分流 View Controllerの作り方 その2



その1はこちら

ぼくのかんがえたさいきょうのせっけいです
主に以下の書籍に影響受けまくりであります
0321127420Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler))
Martin Fowler
Addison-Wesley Professional 2002-11-05

by G-Tools
4798116831レガシーコード改善ガイド (Object Oriented SELECTION)
マイケル・C・フェザーズ ウルシステムズ株式会社
翔泳社 2009-07-14

by G-Tools


図を適当に補足
ViewWrapperは既存のすでにあるどうしようもない設計のViewを何とか救いたいときに非常に便利、Wraper / Decoratorパターンを適用してボタンのタップを奪い取ってViewHelperに流すみたいな役目をする
ViewHelperは簡単に言うならUITableViewControllerのdelegate, datasourceだけを担うオブジェクトみたいな感じ。要するにView専用のドメインロジックを書くオブジェクト
Viewの表示を制御するドメインロジックが途方もなく大きくてViewControllerに納めるのが不可能になってしまったときに超便利

Serviceは準ドメインロジックだと思っている、たいていの場合セミシングルトンみたいにする(通信が絡むので複数画面をまたいで使うことがほとんど)
Androidの場合はここ、普通にServiceクラスでいいんじゃないでしょうかね

Managerはドメインロジックというよりもプロセス外へのリソースアクセスを行うためのクラスというイメージ、個人的にはゲートウェイみたいな感じ
  • NSFileManager
  • NSUserDefaults
  • KeychainAccessManager
  • InAppPurchaseManager
  • APIConnectionManager
Modelはドメインモデルです、ほとんどの場合はCoreDataのNSManagedObject。用がないときでもCoreData使っておけ(超便利)
と思ってましたがCoreDataのモデルは特にN:N関係を正しく扱わないと簡単に問題が発生してしまいますので、安易に採用すると危険かも。

Task, Operationってのは非同期で実行されていく特別なドメインロジックのイメージ。要するにAPI通信みたいなもんです
API通信を複数束ねて使ったりとか並列実行したりとかの制御が絶対必要になるのでそういうときに使う via @monjudoh
// MyTaskが終わったらMySuperTaskを実行して、それが終わったらさらにMySuperDuperTaskを連続して実行したい
// 終わったらselfに通知させたい
Task *root = [[[MyTask alloc] init] autorelease];
root.nextTask = [[[MySuperTask alloc] init] autorelease];
root.nextTask.nextTask = [[[MySuperDuperTask alloc] initWithId:100] autorelease];
root.delegate = self;
[root start];

自分流 View Controllerの作り方 その1



その2はこちら

以前勉強会の際に発表した View Controller の作り方のメモをまとめてみました。あくまでメモなので中身はうまくまとまっていませんが、何かのご参考になればと思います。




通信が絡んでくると、たいていの人がやりがちな問題(実例)
  • API通信のレスポンスを処理するコードがViewControllerの中に入っている
  • API通信が3種類必要で、Aを実行したあとにBとCを実行しなければならないとか
  • ABCのレスポンスJSONのパースまでViewControllerでやっている
  • というかAPIの呼び出しの組み立てだとかURLの指定だとか自体がIBActionの中に入っていたりする
API通信だけじゃなくてIn App Purchaseなどでも同様の事例が見られる

それに対する対応策。そもそもなぜこのような問題が発生するのか?
  1. Outletの生成・更新・レイアウトが分離されていない
    • そのため複数回画面が更新されるタイミングが発生するととたんに破綻する
    • 大変よく見かける初心者コードが"drawXXX"という名前のOutletを生成してデータをセットしてframeまでセットして画面に配置するコード
    • Outletを描画コードと勘違いしている。Outletはペンやブラシに相当するものであって、実際に線を書くコードではない
    • この初心者コードでも動く唯一の理由は画面が一回(viewWillAppear時)しか更新されないから
  2. 通信という(比較的大きくなりがちな)ドメインロジックがViewControllerに混入している
そこで問題1.に対応するためにViewControllerの中でやる作業を以下のように分割する
  • Outletを生成する
    • preload(一度に生成する方法)
    • lazy load(必要になったら生成し、必要でなくなったら捨てる方法)
  • Outletのプロパティを更新する
  • Outletをレイアウトする
これらはそれぞれ(基本的に)以下のようなUIViewControllerのメソッドが対応する
  • loadView
    • Outletをpreloadする場合はコレで全く問題ない。このとき、self.viewとここで作られたOutletの生存期間は等しくなる
    • Outletをlazy loadする場合はOutletを生成するコードと削除するコードを用意しておいて、必要なタイミングで呼び出すとかする
  • なし
    • プロパティを更新するために、たとえばupdateOutletsみたいなメソッドを自分で用意してやる
  • 各種willRotate...系メソッド
    • willRotateほげほげの中にレイアウトコードを入れておくと自動的に画面の回転にも対応できて超便利
    • 自動的に必要なタイミングで適切に呼び出ししてくれて超便利
    • そういうのが嫌いな人はlayoutOutletsForInterfaceOrientation:みたいなメソッドでも作ればいいんじゃないでしょうか
次に問題2.に対応するためにAPIの呼び出しやファイルアクセスなどはService, Managerなどの層を作ってそちらに任せる
決してViewControllerの中にドメインロジックを混入させないのが大事
→混入するとドメインロジックとビューナビゲーションロジックが混ざって大爆発する
→さらにドメインロジック自身も複雑な通信が必要になると大爆発する

それとは別にイベントを受け取ってViewの状態を制御する大事なお仕事をする必要がある
  • ボタンタップしたり
  • 画面をタップしたりパンしたり
  • スクロールが発生したり
  • APIコールが完了したり
  • In App Purchaseが完了したり
ここまでがView Controllerのお仕事。決してドメインロジックを混ぜないのがポイント

2011年7月31日日曜日

Objective-C で文字列リテラルに \0 を含めたいときの作戦

Xcode 4.0 から LLVM が標準のコンパイラとなり、各種警告が非常に厳しくなっています。その中でも特に今回は文字列リテラルに \0 が含まれているときの警告について回避策を発見したのでご紹介したいと思います。

Objective-C では文字列リテラルは @"abesi" のように @"" で囲んで表現します。このリテラルは(あくまで推測で確定ではないのですが)コンパイラによってコンパイル時に CFSTR("abesi") に置換され、 CFStringRef 型としてプログラム中に定義されているようです。さて問題はここからで、 Xcode 4.0 が内部的に構文解析のために使っている LLVM がこのリテラル中に \0 、要するにNULL文字が含まれていると以下のような警告を出すようになってしまったのです
CFString literal contains NUL character
普通はNULL文字をリテラル中に含めたいということはまず無いのですが、SMTPの通信部分を書いたりしていると通信プロトコル自体が命令を \0 で区切れみたいな要件を求めてくるため、どうしてもリテラルの中に \0 を含めたいときが出てきます。 まぁ、具体的には以下のライブラリなんですけどね。

http://code.google.com/p/skpsmtpmessage/source/browse/trunk/SMTPSender/Classes/SKPSMTPMessage.m
sendState = kSKPSMTPWaitingAuthSuccess;
NSString *loginString = [NSString stringWithFormat:@"\000%@\000%@", login, pass];
NSString *authString = [NSString stringWithFormat:@"AUTH PLAIN %@\r\n", [[loginString dataUsingEncoding:NSUTF8StringEncoding] encodeBase64ForData]];
NSLog(@"C: %@", authString);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[authString UTF8String], [authString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
そして私と同じライブラリの同じ箇所でぶつかった人を発見。

http://stackoverflow.com/questions/5814520/how-do-i-create-a-login-string-that-contains-2-embedded-nulls-and-the-login-an

さて回避方法はないものかと探してみたところ、ちょうど良い回避策が発見できました。

http://stackoverflow.com/questions/6211908/nsstring-with-0

NSStringのstringWithFormat:に %C を埋め込んで、そこに 0 を渡せばうまくいくみたいです!
int main() {
NSString *string = [NSString stringWithFormat: @"Hello%CWorld!", 0];
NSData *bytes = [string dataUsingEncoding: NSUTF8StringEncoding];
NSLog(@"string: %@", string);
NSLog(@"bytes: %@", bytes);
return 0;
}
さっきの例だと、
sendState = kSKPSMTPWaitingAuthSuccess;
NSString *loginString = [NSString stringWithFormat:@"%C%@%C%@", 0, login, 0, pass];
NSString *authString = [NSString stringWithFormat:@"AUTH PLAIN %@\r\n", [[loginString dataUsingEncoding:NSUTF8StringEncoding] encodeBase64ForData]];
NSLog(@"C: %@", authString);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[authString UTF8String], [authString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
このようにすればばっちり上手く回避できました!

2011年7月18日月曜日

iPad の UIWebView で twitter.com を表示したときに出てくるアプリの宣伝広告を出さない方法

iOS アプリ内で Twitter のタイムラインを表示したり、投稿させたり、 Tweet Button を置きたいみたいな要求は結構あると思うのですが、ここで問題になるのが iPad の UIWebViewで twitter.com を表示したときです。以下の画像のように、 Twitter for iPad をオススメする広告が最初に表示されてしまうのです。



一度 Continue on mobile.twitter.com ボタンを押せば次の画面に遷移して二度と表示されなくなるのですが、大抵のお客さんはこの画面を見た瞬間意味不明になってしまうと思うので、表示されないようにしなければなりません。

最初に思いついた方法は以下の



http://mobile.twitter.com/settings/dismiss?d=2

このリンクを NSHTTPConnection なんかで踏ませればいいんじゃないかと思っていたのですが、調査してみた結果もっと簡単に何とかできることがわかりました。このアプリの宣伝広告を消すのに一番簡単な方法は、以下のように Cookie をセットしてやることです。
// mobile.twitter.comにUIWebViewからアクセスしたときに、"Get Twitter for iPad NOW"とかなんとか表示されるのを防ぐため、
// UIWebViewが使うcookieにd=2をセットしておく
// NSHTTPCookieExpiresはセットしなくても大丈夫です(その場合起動ごとにCookieがけされてしまうので、起動時に毎回セットしてください)
NSHTTPCookie *twitterForIPadAdBlockCookie = [NSHTTPCookie cookieWithProperties:[NSDictionary dictionaryWithObjectsAndKeys:
                                                                                @"d", NSHTTPCookieName,
                                                                                @"2", NSHTTPCookieValue,
                                                                                @".twitter.com", NSHTTPCookieDomain,
                                                                                @"/", NSHTTPCookiePath,
                                                                                [NSDate distantFuture], NSHTTPCookieExpires,
                                                                                nil]];
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:twitterForIPadAdBlockCookie];
これだけでご覧の通り!嘘のように広告が出なくなります。




■おまけ: javascript:スキームを使ってみよう

今回のように UIWebView をアプリ内で使って特定のWebアプリを表示する場合にWebアプリの挙動が知りたくなったときは、iPadのSafariブラウザから javascript: スキームを使って JavaScript のコードを流し込んでデバッグするとなかなかはかどると言うことがわかりました。たとえばクッキーを表示させてやるだけなら、以下のように超簡単にできます:
javascript:alert(document.cookie);


クッキーを流し込むのも、流し込むだけならこれまた簡単:
javascript:document.cookie="foo=bar";
より詳細な調査はPC側のブラウザでゆっくりやって、最後にちょっとだけ試したいところを実機でやってみることができるのがいい感じです。多分この用途のためのブラウザアプリなんかも App Store をあされば出てくると思うので、そういうのを使えばさらにはかどると思います。

BPStudy #46 での発表資料 ViewController の作り方を公開してます

かなり旧聞になってしまいますが、 BPStudy #46 で話したときの資料をpreziにアップしてますので、ご覧いただけます。
http://prezi.com/7b_joy2lcfil/bpstudy-46/

発表時にお見せしたソースコードはちょっと公開出来なかったのでこの資料だけだとイマイチよくわからないのですが、要するに
  • View Controllerにサービスを書かないようにしてサービスレイヤを分離しろ
  • View Controllerの描画コードは、アウトレットの生成、アウトレットのデータ更新、アウトレットのレイアウトの3つに分離しろ
って言う感じのお話をしました。

すげー当たり前のことしか書いてなくて恐縮なのですが><
実際、人様のコードをメンテしたりバージョンアップしたりするお仕事をいただいていると、ほとんど全てのコードがこの問題に引っかかっていて非同期通信化すると動かないとかちょっとView Controllerの呼び出され方を変えると動かないとかあって、iOS開発者の皆さんがこういうところに気を配ってコードを書いていただければなぁそして私が楽できればなぁという思いで書いてみた次第であります。

2011年6月21日火曜日

Netlogがハックしたgmailアカウントの情報を用いてspamを送っている?

やや旧聞になるのですが、お恥ずかしながらgmailのアカウントが何らかの手段によってハックされてしまったようで、私のメールアドレスから大量のspamが連絡先に飛んでしまう事件がありました。関係者の皆様には大変ご迷惑をおかけいたしまして誠にすみません><

パスワードは標準以上に強固だったはず(ハックされたアカウントが使っていたパスワードは10桁で大文字小文字数字記号全てランダムに混ざったもの)なのですが、マルウェアも見つからないし心当たりのあるフィッシング被害もないしPSNには登録してないし・・・ということでまったくどこからやられたのかわかりません。恐ろしいです。パスワードだけではもはや不十分ということかも知れません。

さてここからが本題。
Netlog (http://ja.netlog.com/) というSNSがあるのですが、今週の月曜日に知人から続々Netlogへの勧誘メールが私から飛んできたという報告が。

手口はどうも私のgmailの連絡先に入っていたメールアドレスに対して、私がin-reply-toになるようなspamを次々送りつけるというもののようです。他の経路からspamされているのではと思って調べてみたのですが、一番怪しいFacebookについてもセキュリティが破られた形跡はないし、そもそも一度もNetlogなるサービスを聞いたこともないしページを開いたことも友達に勧められたこともありません。さらに私のgmailの連絡先にしか入っていないメールアドレスにもspamが飛んでいるようで、どうもこの盗まれたアカウント情報を元にspamを送っているような気がしてなりません。

あくまで状況証拠なのですが、ともかく皆さんもgmailのセキュリティには万全を期してください><

2011年5月29日日曜日

iPhone iPad 向けスタイラスで書き味勝負をしてみた 2回戦


写真は左から順に、 Pogo SketchoStylus 初期限定生産型パワーサポート スマートペン PBJ-9XシリーズBamboo Stylus

iPhone/iPad向けのスタイラスがいくつも発売されていますが、実際にスタイラスを使ったらどんな具合なの?指でいいんじゃないの?と疑問をお持ちの方と、 私のようなスタイラスマニアの方 のために、iPhone/iPad向けのスタイラスの書き味を実際に書いてみて試してみるという企画です。

前回の記事はこちら: http://akisute.com/2011/02/iphone-ipad.html
おすすめスタイラスまとめ記事はこちら: http://akisute.com/2010/06/ipad.html
Bamboo Stylusのレビューはこちら: http://akisute.com/2011/05/ipad-bamboo-stylus.html
oStylusのレビューはこちら: http://akisute.com/2010/10/ipad-ostylus.html


■比較方法

iPad上で実際にスタイラスでいろいろ書いてみて、書き味を見て比較します。
  • 使用するiPadは iPad 2 Black 16GB (WiFi+3G GSM)
    • 表面に保護フィルムは貼っていません
    • 保護フィルムがある場合、保護フィルムのためにペン先の滑りおよび感度が変化するため、結果が異なってくる可能性があります。
    • 一般的に、保護フィルムがある方が感度が悪くなります。滑りはフィルムの材質により変化します。
  • 使用するアプリは Noteshelf
    • 無地ノート
    • 黒インク
    • ペン先10pt
    • 拡大は使用しない
  • すべて一発勝負
    • undoなし
    • 書き損じたり、ペン先の感度が落ちて線が飛んでもやり直さない。それは感度が実際に悪いという証拠になるため。

■結果

書いてみたノートをpdfにして出力してみました。以下のURLからご覧になれます。
https://docs.google.com/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxha2lzdXRlc2FtYXxneDo0OTI3MWU0MjRlMzI2ZjNm

複数同じ文言が並んでいるページは、全て上から順に、
pogo sketch, oStylus, スマートペン, Bamboo Stylus
の順に並んでいます。


■考察

以下、いくつかのページをピックアップして考察。


pogo sketchは第一世代のスポンジ式ペン先であるため、やはりペン先感度が悪く線が飛びまくります。線自体は自然に書けているのですが。


oStylusは感度はよいものの、いかんせん書きづらい。曲線が特に不自然です。しかも書くのに時間がかかります。多分一番時間がかかりました。


スマートペンはさすがの高性能。最高の感度と軽い軸の恩恵で、一番楽に書けます。ただし線自体は細かいところが弱いです。



今回新顔のBamboo Stylus。ペンが重く、スマートペンより筆圧が必要なため疲れますが、スピードのある細かい線が魅力。一番速く書けたのもこのペンです。


並べてみるとこんな感じです。上からpogo sketch, oStylus, スマートペン, Bamboo Stylus。良いと思ったところには緑○を、悪いと思ったところには赤×を引いています。
pogoは線が飛びさえしなければ・・・
oStylusはとにかく線をゆっくり引いてしまう。 Noteshelf アプリは Penultimate 同様、線を高速に引けば美しく細くなるように出来ているのですが、見てのとおり全ての線が太くてゆっくり引いてしまっている。Zとかへろへろですよ、もう。
スマートペンとBamboo Stylusは安定して良いですね。高速に美しく書けています。


■まとめ

やはり第二世代のシリコンペン先を装備したスタイラスが圧倒的に良いです。その中でも自分の好みや用途で選べる次代になってきたのがすごくいいですね。次回はプリンストンテクノロジーの専用タッチペンやAcase Stylusなんかも加えてレビューしてみたいです。