ほげぐらまの別館

プログラムに限らずてきとーに、ね?

電撃的なニュースだ!、と思いきやその直後に「いくらなんでもiPhone4Sの発表翌日に死去とかおかしいよね。Apple社ぐるみで隠していたんぢゃないの?」と疑うところが私の性格の悪さです。

iPod Touchをかれこれ計3台、iPadを計2台、MacMiniを1台という、一般的に見ればMac信者と思われがちな私ですが決してMac信者ではないのであしからず。一応私が属するところは、Microsoft信者・SunMicro信者(買収されたけど)・Pioneer信者となっております m(_”_)m

信者ではないですがiPhoneを皮切りに一挙スマートフォンの世界を塗り替えるという偉業を達成したジョブズさんへ、お疲れ様でした。

 

ちなみに、iPhone for Stive.と言われている噂のiPhone4Sですが買いません。だって、どれほど性能が上がったとしても私にはiPod Touch + 3Gとしてしか目に映らないので。やはり、携帯電話としての唯一存在するWindows Phone 7しか私には無いでしょう。 (WP7タブレット、ないよね。)

以前に9月末まででフレッツ光ネクストの勧誘があったよ~、っとblogにボロっと書いただけなのですがまったく予想だにしていないほど電話番号での検索が多いようで・・・・(※ 検索されたキーワードはblogネタの活用にのみ参照しています m(_”_)m )。ちなみにこちらが元ネタ記事。

一応その結果を書くと、当初9月末までに返答をして切り替える予定でした。が、思いのほか「DarkSoul」に忙しくてすっかり忘れていました。まぁ・・・結果的に割引を逃してしまったのですね。楽観視するなればまだ割引のチャンスはあるのではないかな、と。というのも、フレッツ光ネクストに切り替えることで何より大きく変わるのが電話がIP電話になる、ということで私的には電話網とインターネット網の2つの回線維持を行わなければならない電話会社としては大きく割り引いてでも維持費のかかる電話網を消したいわけで、それがより強く進めば進むほど割引が良くなるのかな、とか。えぇ、私の脳内妄想です。

ともあれ、現状では何不自由なくインターネットに接続できていますし、現在もWindows 2008 ServerなPPTP-VPNが元気にがんばっています。というわけでまたキャンペーンが始まるまで当面そのまま待ち続けます。

iOSのプログラムをしていたらひょんな事にハマりました。処理の例としては以下の感じ。

  1. ユーザーに「DOWNLOAD」ボタンを押させる
  2. NSURLConnection:sendAsynchronousRequest:queue:completionHandler:を使って非同期的に所定のNSURLRequestにリクエストを送信、NSDataを取得する (もちろん、NSDataで受信時はプログレスバーを更新する)
  3. NSDataをGZipのストリームと判断し解凍する
  4. 2~3の操作を数回繰り返す

2. で受信プログレスを更新する際にはUIProgressView.progressにより随時更新の後、100%=1.0の場合はそのまま解凍処理を走らせるのだから処理上では問題ないですが、以下のプログラムにすると恐らくプログレスバーが100%にならないです。

// TODO: 受信したデータを結合する
...(略)...

// プログレスバーを更新する
_progressView.progress = CLAMP([data length] / filesize, 0.0f, 1.0f);
if ([data length] < filesize) { return; }

// TODO: 100%ダウンロードできたので解凍処理をする
...(略)...

// 次のダウンローダをリクエストする
[self requestNextRequest];

処理が連続する場合にコントロールの再描画が必要となるプロパティ変更を行っても即時で再描画されない、言い換えれば、UIView:setNeedsDisplayが画面に対して即時処理されないということ、です。ここでWindowsの話を持ち出すのはナンセンスだとは思っていますが、あえて出させてもらうとInvalidateRect(HWND, LPRECT, BOOL)だけコールしてUpdateWindow(HWND)をコールしてない事になります。さて、ではどのようにiOSのコントロールを再描画させるか。あ、ちなみに間違ってもUIView:drawRect:を直接コールしないでねっ!

答えは簡単、NSRunLoop:runUntilDate:をコールして現在のRunLoopに対して処理のゆとり?を与えるということ。再描画フラグ自体は立っているのでその処理時間を与える、というイメージだと思われます(iOS専門ぢゃないから怪しいけど)。引数がNSDateなのでどこまでの時間を渡せばいいかということですが、かなり適当です。

// TODO: 受信したデータを結合する
...(略)...


// プログレスバーを更新する
_progressView.progress = CLAMP([data length] / filesize, 0.0f, 1.0f);
if ([data length] < filesize) {
    return;
}

// 解凍処理(しばらくの間の重い処理)に入る前に、再描画をさせる
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.05]];

// TODO: 100%ダウンロードできたので解凍処理をする
...(略)...


// 次のダウンローダをリクエストする
[self requestNextRequest];

という感じ。

0.05=50ミリ秒ですが、これで十分だと思われます。10ミリ秒でもiPod Touch 4genでは正常に再描画されましたので、恐らくそれでも大丈夫だと思います。

あ、後、CLAMPマクロは独自定義なのであしから。やってることは #define CLAMP(v, x, y) MIN(MAX((v), (x)), (y)) なだけです。

プログラムをしているとたった一行だけのはずなのに何故か動かなかったりすることがあるかと思います。時々そんな一行をやらかすのですが、本日この一行で1時間ほどハマってました。

  • 正常動作
[[Audio instance] play:[[AudioSource alloc] initWithPath:@"bgm.m4a"]];
  • 異常動作
AudioSource *source = [[AudioSource alloc] initWithPath:@"bgm.m4a"];
[[Audio instance] play:source];

コードだけ見ると要はAudioSourceのインスタンス確保と初期化、そしてAudioクラスによる再生を行っています。Audioクラスはsingletonにより管理されていて常に唯一のインスタンスを返し、サウンド(ソース)の再生・停止を行います。

が、このAudioクラスが自分で作ったにも関わらずハマったという情けない話で、Audioクラスが最初にインスタンス化される際にはinitメソッドをコールするようになっています。つまりは、singletonクラスメソッドであるinstanceをコールした際にはご丁寧にもOpenALの初期化まで行っています。

ここまで書くと答えは明白で、異常動作のほうはOpenALの初期化前にALBufferを確保しようとして失敗していたと。たった一行や二行でも本気でわからないものです。

ちなみにこのオチに気がついたのが隣の同僚に「なんでだろ~」と聞いていた最中でふと思いつきました。その同僚はObjective-Cを知らないというのに無茶振り質問したわけなんですが・・・。いざ話してみると見落としがちな部分も気がつくものですね。

# 話す前に自分で気がつけって?、うん、自分でもそうしたいのですけどね。巧く行かないものなんです orz

iOSの開発をしているとプログラム以外の割と煩わしいものにProvisioning(プロビジョニング)とCodeSigning(コードサイニング)なるものがあります。この2つは以下のことをコンパイルされたプログラムに情報提示するものです。

  • 自分がAppleより認定された正規のiOSディベロッパである
  • プロダクトの署名が有効期限内に限って正しいものである (開発時のみ)
  • コンパイルされたコードは改ざんされてはいないコードである
  • デプロイ(インストール)先のデバイスが開発者から正式に認定されたデバイスである (開発時のみ)
  • InAppPurchase, Push Notification, Game Centerに関して有効 or 無効である情報をアプリケーションに与える

多分、思いつく限りでこんな感じ~のはず・・・・なんですが、適当です。なので、InAppPurchaseを使うにもInAppPurchaseが有効とされたAppIDに対して、その際に設定した適切なBundleIDをplistに設定して、更に適切なProvisiningを発行して、更の更にiTunesConnectでInAppPurchase用のアイテムを登録してようやく課金が行える・・・・なんて手間が必要なわけで。

とりあえず、InAppPurchase云々はさておき、未だ現役である可能性が非常に大きいXcode3系の、プロビジョニング/コードサイニングでハマる部分を以下に示します。適切なプロビジョニングを設定しているのにCodeSigningでWarningが出た場合は以下を確認してみてください(一部先日コメントしたInAppPurchaseにも関連します、多分)。

1. InAppPurchase、GameCenterが有効であることを確認する

Provisioning1

iOS Provisioning Portal(iOS Dev Center内)のAppIDタブで一意なBundleID(ワイルドカードを含まないBundleID)ではない場合は図のようにInAppPurchase, GameCenterの部分にEnableが付きません。まずはここで適切なAppIDが登録されていることを確認しましょう。その上で、Provisioningタブからプロビジョニングを発行 or 再生成し、ダウンロードしてみましょう。

2. 適切なプロビジョニングを設定する

Xcode3系のバグなのか、プロジェクトツリーの[Project]→(右クリック)→[Get Info]からプロジェクトの設定を行っても適切に設定情報が反映されない場合があります。その場合は以下の手順で設定を行ってみましょう。

Provisioning2

[Get Info]からではなくて、[Project]メニューの[Edit Active Target]から設定画面を出すのがコツです。私がコレにはまったときは2時間ほど困って1時間ほどググった挙句にようやく海外のとあるBlogerのホームページで対処を見つけました(が、原文はいずこかに消えました)。

Provisioning3

書くまでもないですがこれは通常通りの操作、CodeSigningの際に使用するProvisiningを設定するだけです。

3. Xcode上でおまじないをする

今からほぼ2年前に私にiOSの開発を教えてくれたドイツ人のMさんが、非常に悩ましい顔をしながら「おまじないをするとコンパイルが通る場合があります」という事を言いました。もちろん、コンピュータ上で動くプログラムにそんな曖昧な要素などあるわけがないのですが、意外やこれが良く効くおまじないで。

  1. [Build]メニューから[Clean All Target]を選択する
  2. [Xcode]メニューから[Empty Caches]を選択する
  3. Xcodeを終了する (ランチャーにランプが付いている場合は右クリックで[Quit]選択)
  4. Finderからプロジェクト直下のBuildフォルダを削除する
  5. Xcodeを起動してコンパイル開始

CodeSigningに使用するProvisioning(2.の操作)を変更してもどこかにキャッシュが残っているのか、おまじないが本気で有効な場合があります。慌てずにおまじないまでキッチリやった後にコンパイルすることをお勧めいたします。

って、情報を1年前に出しておけば救われた人が多かったかもしれませんね。まだ3ヶ月に満たないBlogですが、ハッハハハー、アーハッハハ@オーマイオキー風。

※ Warningが出たときの詳細メッセージがただ今再現できないので、再現できた場合は検索にかかるように更新します。