2015年12月1日火曜日

pyspa Advent Calendar 2015: 2015 GOTY for Programmers

この記事は pyspa Advent Calendar 2015: 12/1 分の記事です。先鋒を担当いたしますakisuteこと小野と申します。よろしくお願いします。なんか3年前にも先鋒をやったような・・・

さて2015年も終わりに近づいてきたということで、本来はGame of the Year for akisuteというタイトルで私的に今年最高だったゲームについて延々と無駄に語ろうかと思っていたのですけれども、Qiitaでは利用規約には一切明記がありませんがプログラマーに役立たない記事は投稿できないという制約がございまして、大変遺憾ながらタイトルを無理やりプログラマーに役立ちますように変更させていただいた次第であります。

しかしながらご安心ください。今年2015年はプログラマー向けゲームの大豊作当たり年でございます。プログラマーにこれからなりたい人向け、プログラマーに育成したいお子さん向け、ガチのプログラマー勢向けのゲームを多数ご用意させていただきましたので最後までお楽しみいただければと思います。

それではまえがきはこの程度にいたしまして・・・いよいよ各賞の発表です!

■お子さんのプログラマー教育向け賞: CodeSpells


あなたのお子さんを将来プログラマーに育てたいと考えているのであればこのようなゲームはいかがでしょうか?というわけで自分で魔法を自由にプログラミングして使えるゲーム、CodeSpellsがノミネートです。こちらのゲーム自体はかなり昔から発表されていたのですが、Steam Early Accessが始まったのが今年のようなので今年のノミネートとさせていただきました。

ゲーム自体はオープンワールドのゲームで目的等はまだ特になく開発段階という感じですが、このゲームのキモである魔法を自由にプログラミングして行使する点は現在でもバッチリ遊べます。各魔法にはイベント・アクション・それから分岐とループの制御構造が用意されていて、例えば
  • 光の玉を正面に発射する
  • if 光の玉が何かに接触する
  • 自分自身を接触地点にワープ
とか、
  • for 100回ループ
  • 炎をi*3.6度方向に発射
とかいろいろ工夫して魔法が作れるようになっています。大人が遊ぶにはちょっとパズル要素がないとか目的がないとか現段階では問題点が多いのですが、子供が想像力を働かせて遊ぶという当初の目的上であれば自由に地面を隆起させたり石を吹き飛ばしたり津波を起こしたりして遊べるのではないかなと思います!

■プログラマー初心者向け賞: Human Resource Machine


World of GooやLittle Infernoなどで有名なTomorrow Corporationの新作、Human Resource Machineが今年のプログラマー初心者向け賞にノミネートしました!このゲームはどんな命令でもただ言われたとおりに働くただのサラリーマンのおっさんに簡単な命令セットを与えて目的を達成する、つまり
おっさんをプログラミングするゲーム
です!新しいな。

Tomorrow Corporationらしい独特の世界観がただただ言われたとおりに動くおっさんを彩り、一風変わった雰囲気を味わうことができます。この世界観だけでもこのゲームをやる価値があります。

さてこのおっさんを操るプログラミング言語(仮におっさん言語と呼ぶことにする)ですが、一般的なアセンブリ言語の命令セットと同じような感じになっています。またレジスタ兼RAMの代わりとして床のカーペット上の区画を自由に使うことができるようになっていて、ちょっと工夫すればこんなふうにおっさんにバブルソートをさせる事ができます!

おっさん言語のコーディングはビジュアライズされていて、各命令ラベルをドラッグアンドドロップするだけで快適にコーディングが楽しめます。コメントラベルも手書きで好きなように書けるようになっていて、プログラミングをやったことがないカジュアル層の人でも無理なく遊べます。反面ガチ勢の人には簡単すぎるかもしれません。一応命令セット数とステップ数の最適化チャレンジが用意されているので、ガチ勢の人はそちらで楽しんでみてはいかがでしょうか?

■自動化大賞: Factorio


皆様
プログラマーにとって
最も邪悪なことは何でしょうか?

手作業です!

皆様
プログラマーにとって
最も素晴らしいことは何でしょうか?

自動化です!

自動化こそ正義!
自動化こそ未来!
今こそゲームも自動化されるべき!!

ということで今年最も自動化が進んだゲームに送られる自動化大賞には、スーパーマリオメーカーを差し置いてFactorioがノミネートされました!

Factorioについて解説する前に少し前置きをさせてください。
皆様はMinecraftというゲームをご存知でしょうか?
今やスーパーマリオに肩を並べ、Minecraft系という一大ジャンルを作り上げた超有名作だと思います。

しかしながら私このMinecraftというゲームが大嫌いでして。

例えば鉄の武器が欲しいじゃないですか。
なので一生懸命地面を掘って鉄を探して、鉄が見つかったら今度は石炭を探して、持って帰ってきて自分でかまどにぶち込んで鉄にして、今度は鉄を自分で並べて装備を作って、また鉄が足りなくなったら自分で地下に潜って掘ってって繰り返して。

めんどくせえんだよ!!!!!!!
人力手作業で作業するからスケールしねえし!!!!!
自動で掘らせろや!!!!!!!!!

って思っちゃうんですね(´・_・`)

Factorioも基本的にはMinecraftのようなゲームです。
ある宇宙飛行士がとある惑星に不時着してしまって帰れなくなっちゃうんですね。
そこで現地の素材を集めて加工してサバイバルして、最終的にはロケットを作って打ち上げて星から脱出するのが目的のゲームです。
というわけで最初は木を斧で切り倒したり、鉄鉱石をツルハシで掘ったりするんですね。

違うのはここからです。
手作業で鉄を掘るとかめんどくさいじゃないですか。そもそもロケットを作るには膨大な料の鉄が必要だというのに手で掘るとか現実的じゃないですよね。
そこで主人公は鉄を掘るためのツルハシ・・・じゃなくて、なんと自動砕石機を作るんですね。
それから自動で材料を運ぶためのベルトコンベアと、自動で材料を加工してものを作成する機械と、自動で機械に燃料や材料を投下するロボットアームを作るんですね。
あとは砕石機からベルトコンベアで材料を運んで、ロボットアームで機械に投下すれば、自分が寝てても別の所を探検しててもエイリアンと銃撃戦を繰り広げてても勝手に新しい製品がドンドン製造されていくんですね。

なんて賢いんだこの主人公は!!!!!!!!!!

って思っちゃいますよね\(^o^)/

そう、このゲームは
自動化こそが正義
スケールする生産こそが正義
生産効率の最適化のために機械の配置を試行錯誤するのが正義

まさにプログラマーのプログラマーによるプログラマーのためのゲームなのです!

こうして最初は1秒間に1個しかつくれなかった鉄が、工場生産効率の改善とスケールメリットのおかげで最後には1秒に1万個以上生産できるようになります。ヤバい。
こうなってくるともう最適化欲求がドンドン高まってきて、そのうちロケットを打ち上げることが目標だったのが、1秒間に何発ロケットを打ち上げられるかみたいなゲームになってきて、資源を惑星の片っ端から鉄道で運搬し、邪魔するエイリアンを片っ端から銃弾爆薬戦闘ロボットの波で蹂躙し森林を伐採して燃やし・・・どっちが悪役かわからなくなってきましたね(´・_・`)

さらにこのゲームはゲーム内のオブジェクトがすべてコンソールからluaスクリプトで操作ができ、luaスクリプトを書くことで自由にMODをプログラミングすることができます。そのため究極的にはパソコンの前で座って眺めているだけで自動的に主人公が工場を作ってロケットを打ち上げてゲームをクリアするMODをプログラミングすることも理論上可能なわけです!

自動化万歳!!

■GOTY for Programmers: TIS-100


しかしながらそのFactorioを差し置いて、今年のGOTY for ProgrammersはこのTIS-100で決まりです!名前からしてプログラマーっぽいこのゲーム、簡単に説明しますと
  • 主人公(=あなた)の叔父に当たる人物が最近、原因不明の死を遂げた
  • 叔父は生前プログラマーで、遺品整理していたらTIS-100という1980年代製の謎のコンピュータが出てきた
  • TIS-100の中には叔父の生前のメッセージや日記などが残っていた
  • 主人公であるあなたはTIS-100のプログラムを復活させてすべての謎を解き明かしていく
という設定になっております。地味、地味の極地ですね。プログラマー以外眼中にない、まさにプログラマーの・プログラマーによる・プログラマーのためのゲームとしか言いようがありません。

この作品の焦点となるTIS-100コンピュータですが、通常の1980年台のコンピュータとは全く異なるユニークな分散コンピューティングアーキテクチャを採用しています。
  • 各プログラムは横に4つx縦に3つ、最大12個の「ノード」と、複数の「入力ポート」「出力ポート」で構成される
  • 各ノードは最大上下左右4つの「ポート」で結ばれている
  • 各ノードにはアセンブリ言語を用いて命令を記述できる
  • 各ポート間はGo言語のチャンネルのような仕様を用いて入力・出力を行ったり、各ノード間の協調動作を行ったりすることができる。
  • 各ノードの命令はすべて並列で実行される
このアセンブリ言語とモダンな言語の特性を併せ持ったアーキテクチャがTIS-100でのプログラミングの最大の面白さです。うまくコーディングしてノード間を並列させれば実行ステップ数を劇的に減らしたり、複数ノード間でちょっとしたmap-reduceのような作りさえ実現することができます。

しかしながらですね、このTIS-100
異常なほど難しい
んです

開発者Zachtronicsの前作はSpaceChemというゲームでして、こちらのゲームも後半異常な難易度になってくるのですが、本作TIS-100も負けず劣らず難易度が凄まじいことになります。主に難易度が高くなる原因として、TIS-100コンピュータのアーキテクチャ上の絶妙な制約があげられます。例えばこんな感じです:
  • 1ノード内にたったの15行しか書けない。ちなみに空行、ラベル行、およびコメントも1行として数えられます。
  • 1行に確か20文字だか30文字前後しか書けない。ラベル名等は極端に短くする必要がある。
  • 1ノードにレジスタが2つしかなく、そのうち片方は計算レジスタとして使用できずただ値を一時保存するしかできない。さらにこの一時保存レジスタと値をやり取りするだけで追加の1命令が必要。
  • INCL/DECL命令がない。
  • 上記制約が合わさって1ノード内でループ構造を作るのが著しく難しく、汎用のどこでも通用するループ構造を構成するのが難しい。
というわけで普通の手続き的なコードを書くことができず、まさにTIS-100のためのまったく新しいコーディングの仕方を考えていく必要があってとにかく辛いです。実際私は最後までクリア出来ていません(´・_・`)

このただでさえ高い難易度に加え、さらに各問題には最適化チャレンジというものがありまして、使用したノード数・使用した行数・実行ステップ数の3つの点からプログラムの最適化を全世界のプログラマと競うことができるようになっています。おかげで世界中でドハマりするプログラマーが続出。ついにハッカーズマニュアルやらC言語製のシミュレータ、果てはWeb上で動作するTIS-100用コードエディタがまでが登場してしまいました。捗る!

ぜひ我こそはというガチ勢のプログラマの皆様、年末年始のお休みはこのTIS-100の謎を解き明かすのに使ってみてはいかがでしょうか?

それでは皆様、来年もHappy Gaming!

2015年11月29日日曜日

iOS でヒラギノフォントが明示的に指定された時に描画サイズの計算が正しくならない問題を修正する

タイトルからして出落ち感が少々ありますが・・・

iOSのフォントサイズ計算には長年修正されないバグというか仕様がございまして、「ヒラギノフォント(ヒラギノ角ゴシック、ヒラギノ明朝等)」が明示的に[UIFont fontWithName:size:]で指定されたとき、そのフォントを使ったUILabelやUITextViewなどの描画サイズの計算が正しくならない問題があります。iOS 6からiOS 9.1現在に至るまでずっとなので今後も直ることはないと思います。

詳細についてはこちらの記事が詳しいです。
http://qiita.com/yusuga/items/2be8c55ca561bba44702
一番下のリンク先の記事でも同様の問題が訴えられていまして、それぞれ対策が記載されていますので合わせてご参照ください。

でまぁ、対処法としてはいくつかあります。
  • UIControl.contentVerticalAlignmentFillにする
  • sizeThatFitsおよびintrinsicContentSizeの実装を差し替える
  • ヒラギノフォントを明示的に指定するのをやめる。システムフォントを使えばいいじゃない\(^o^)/
  • システムが提供するヒラギノフォントを使うのをやめて、モリサワさんなどからまともなヒラギノフォントを買ってきてそちらを使う

今回は私が実際に使っているsizeThatFitsの実装を差し替える方法を紹介したいと思います。といってもまぁ結構簡単です。以下の様な実装になっています。

見ての通り、フォントファミリーがヒラギノ系であったら、
  • widthはceilする
  • heightはもともとの高さにfontのdescenderをfabsしてから足したうえでceilする
だけです。簡単でしょう? Apple爆発しろ

2015年10月30日金曜日

iOS デバイス上で YouTube の動画を扱うときの制限事項などまとめ

iOSのデバイス上でYouTubeの動画を扱うときに、PC上のブラウザとは異なった制約がかかる箇所がいくつかあるのでまとめておきます。

■iOSアプリでYouTube動画を扱いたいとき

youtube/youtube-ios-player-helper を使うのがもっともよいです。こちらはYouTube公式のライブラリとなっています。具体的な実装としてはUIWebViewを使い、その上にYouTubeのiframe APIを用いてiframeのプレイヤーをロードして表示する実装になっています。その他一部のAPIについてはObjective-C経由でそのまま呼び出せるようになっていて便利です。
こちらに動画ロード時に使えるパラメータの一覧があります。
またこちらにプレイヤーのAPIの一覧があります。
以上に記載がある機能は基本的に使えますが、一部例外があります。以下で説明します。

■iOSアプリでYouTube動画を扱いたいとき(AVFoundationで)

諦めてください。
実際にはやろうと思えば実は可能ですし巷にはそういうアプリもございますが、基本的にYouTubeが許可していない動画そのもののURLを直接探してきて叩く必要がありまして権利的に大変問題がございますので、まっとうなアプリを作ろうとお考えの方は潔く諦めていただくことを強くおすすめいたします。ただリジェクトはされていないところを見るとApple的にはYouTubeの都合なんぞ知ったこっちゃないというスタンスのようです。

■YouTube動画をインラインで表示したい

普通に問題ありません。ロード時にplaysinlineパラメータを1に設定しましょう。

■YouTube動画のUIをカスタマイズしたい

諦めましょう。インライン再生でもフルスクリーン再生でも、最初から用意してある見た目を使うしかありません。ただしコントロールを表示するしない程度はカスタマイズ可能です。ロード時にcontrolsやfsパラメータを渡しましょう。

■YouTube動画の一部だけを再生したい

可能です。ロード時にstartパラメータとendパラメータを指定すればオッケーです。

■YouTube動画を複数同時再生したい

複数のYouTube動画を並べるぶんには問題ありませんが、同時に再生となるとあんまり自信がないです(実機で試せていません)。ちなみにAndroidの場合はできないそうです。

■YouTube動画をオートプレイしたい

アプリであれば可能ですが、iOSのSafariでは不可能です。実装上許可されていません。
アプリの場合はUIWebViewのallowsInlineMediaPlaybackをYESに設定すればautoplayオプションが有効になります。先ほどのyoutube-ios-player-helperはこちらのオプションが有効になっています。

■YouTube動画をプログラム的にフルスクリーンに切り替えたい

いわゆる標準のフルスクリーンプレイヤーを使うという意味では不可能です。諦めてください。
詳細はこちら。
ワークアラウンドとしては、

  • 最初からフルスクリーンで表示する(これは可能です)
  • ボタンを押したら一旦現在の動画をアンロードして、フルスクリーンとして再度一からロードする(可能ですが遅いです)
  • プレイヤー領域を司るHTMLを操作してフルスクリーンに見せかける

などがあります。

■YouTube動画をミュートしたい

不可能です。諦めてください。
詳細はこちら。
AVFoundationの機能などを使っても基本的にアプリ全体をミュートにすることはできません。AVAudioSessionのmodeをAVAudioSessionModeMeasurementにすることでアプリ全体のオーディオプレイバックを止めるという裏ワザもありますが、この方法を使うとビデオの再生まで止まってしまうので意味が無いです。
ただしデバイスのmuteスイッチの状態やデバイス全体の音量は普通に動画の音量に反映されますのでご安心ください。


2015年10月19日月曜日

AppBank GAMESを退職していました

表題の件、私akisuteは2013年10月末日を持ちましてAppBank GAMES株式会社を退職したことをご報告いたします。短い間ではございましたが関係者皆様大変ありがとうございました。今後の予定につきましてはとりあえずのところ問題なくやっていけそうで助かっております。

みなさま3年間お疲れ様でした\(^o^)/

※すみません、なんかTwitter経由なりLINE経由なりで皆様から消しといたほうがよくねというご指摘を多数頂いておりまして、別に意地張るものでもないので以前の内容は削除させていただきました(´・_・`) 現職の会社から何か言われたってことはないのでごあんしんください\(^o^)/

2015年9月7日月曜日

Apple Watch アプリの実機インストールが失敗する時のメモ

Apple Watchアプリの実機インストールが失敗する際にチェックする事項をまとめてみました。Apple Watchアプリが実機インストールに失敗する原因は多岐にわたり、ここにまとめてある内容だけではとても網羅しきれませんが、何かの参考になれば幸いです。

以下、すべてwatchOS 1での結果です。watchOS 2ではまた何かが変わっているかもしれませんが、基本的なところは同じかと思います。

最初にやること

いずれの場合でもまず最初に必ずやることとして、XcodeのDeviceウィンドウを開いて実機のログを確認するようにしましょう。このログにApple Watchアプリをインストールするときのログも全て残るようになっていますので、一番の手がかりになります。

症状: 「インストールに失敗しました」ダイアログ

このダイアログが表示されるときは、殆どの場合iPhone本体にインストールされるApple Watch App ExtensionとApple Watch上にインストールされるApple Watch Appの両方がインストールに失敗していることが多いです。

失敗の理由ですが私が遭遇したものの中だとダントツ多かったのがコードサインエラーです。どうしてもインストールが成功しない場合は、Xcodeのビルド設定などから使用するProvisioning Profileを明示的に指定してやるとうまくいくことが多かったです。

このとき指定するProvisioning Profileは、Debug向けビルドであれば標準のデバッグ用のiOSTeam Provisioning Profile: *を使うか、または専用に用意したcom.example.myapp.watchkitappないしcom.example.myapp.watchkitextensionのようなApp IDを使うプロビジョニングを選びましょう。Release向けビルドの場合は必ず専用に用意したApp IDを使うプロビジョニングを選択する必要があるので、面倒でも必ずApple Watchアプリを作る際には新しくApple Watch App Extension用のApp IDとApple Watch App用のApp IDを作るようにしましょう。

症状:「インストール中です」が終わらない

このようなケースの場合は、どうやらiPhone本体にインストールされるApple Watch App Extensionのインストールには成功しているのだけれども、Apple Watch上にインストールされるApple Watch Appのインストールが何らかの理由で失敗している事が多いようです。

私が遭遇した失敗の理由ですと、例えばApple Watch AppのInfo.plistに不要な値を記載してしまったためにインストールにコケたということがありました。具体的にはLSApplicationQueriesSchemesとNSAppTransportSecurity(いずれもiOS 9向けのキー)をApple Watch AppのInfo.plistに記載していたのですが、インストール時にこれらが
(Error) WatchKit: validateWatchKitApplicationInfoDictionary, invalid Info.plist key 'LSApplicationQueriesSchemes'
のように言われて失敗してしまっていました。

watchOS 1の地点ではインストール時にかなり厳密に値をチェックしているようなので、Apple Watch App向けのInfo.plistには余計なものを記載しないほうがよさそうです。watchOS 2ではまた変わるかもしれませんが・・・

症状: インストールは正常に完了するのにアプリが全く立ち上がらない

大変稀なケースですが、インストールは完了するのにアプリが全く立ち上がらず、最初のアプリ名が表示されてクルクルが回ったまま永遠に先に進まなくなってしまう事があります。私が遭遇した場合の原因ですが、Entitlementsの指定が間違っていたときにこのような問題が発生したことがありました。通常アプリのEntitlementsは自動的にXcodeが生成して付与するため問題にはならないのですが、何らかの理由で自分でApple Watchアプリ向けにEntitlementsを自作して設定した場合は大変問題になります。

具体的には、Apple Watchアプリ向けにEntitlementsを自作する場合には必ず以下のようにしてください。

  • Apple Watch App Extension向けのEntitlementsには、必ずapplication-identifierとkeychain-access-groupsを含める。
  • Apple Watch App向けのEntitlementsには、application-identifierとkeychain-access-groupsのみを含め、その他のキーは絶対に指定しない。

application-identifierは指定がないとそもそもApple Watchアプリのインストールに失敗します。

keychain-access-groupsはアプリ内でKeychainを一切使っていなくても必ず含めてください。どうやらiPhone本体とApple Watchアプリ間の通信の際にこのEntitlementsをこっそりシステムが使用しているようで、keychain-access-groupsが指定されていないと冒頭で紹介したような事態が発生するようです。エラーメッセージも何も一切表示されなかったため調査に大変苦労しました。

2015年7月31日金曜日

iOS 9 の split screen に対応するサンプルプロジェクトを作成してみました

WWDCでの発表でもありましたとおり、iOS 9からはiPadで画面を二分割して複数のアプリケーションを同時に実行することができるようになります。このSplit Screen機能に対応するためには、iOS 8で導入されたAdaptive User Interfaceを活用する必要があります。

参考:



Adaptive User Interfaceな実装をする上で最も簡単な対応方法はひとつのUIViewControllerで複数のSize Classeに対応することです。大体のケース、例えばApple標準のMapアプリやメールアプリなどはこれで問題がないのですが、例えば横方向がRegularサイズの場合(=iPadやiPhone 6 plusなど横に広いデバイスの場合)はUICollectionViewを使ってタイル上に広くアイテムを並べ、横がCompactなデバイス(縦向きのiPhoneなど)の場合はUITableViewを使って縦に多数のアイテムを並べたいという需要があったりします。この場合、ひとつのUIViewControllerでどちらにも対応するのがかなり困難になり、シンプルな実装になりません。

そこでSize Classに応じてdynamicに複数のUIViewControllerを差し替えて表示するサンプルを作ってみました。



特徴:

  • Size Class変更時にきちんとアニメーションします
  • 当然Split Screenにも対応しています


iOS 9は既存のコードをAdaptive User Interfaceに対応させる良い機会だと思いますので、これを機会に皆さんも自分のアプリを見なおしてみてはいかがでしょうか?

2015年4月10日金曜日

Apple Watch の実機を触ってわかった、アプリ開発者が抑えておくべきポイント


本日からついにApple Watchの実機がお目見えとなりました。私も早速Apple Storeに行って試着・試用してきたのですが、予想以上にアプリ開発に影響がありそうな点が多数見つかりましたので、思うところをブログ記事にまとめて公開しようかと思います。

■小さい、とにかく小さい

Apple Watchの実機を身につけてまず最初に感じるのがその圧倒的な小ささです。この小ささというのは

  • これまでのAndroid Wearデバイスのどれと比べても感じる相対的な小ささ
  • Apple Watch上で表示されているUIを見て感じる絶対的な小ささ

の2つの要素から感じられます。

試しに私が身につけているAndroid WearデバイスとApple Watch Standard 42mmを並べて写真をとってみたのですが、見ての通り42mmモデルですら表示領域がずいぶんと小さいのがわかります。

その上Apple WatchのUIは全体的にAndroid WearのUIと比べて密度が高い用に感じられます。こちらのブログに具体的な例があるのでぜひ参照していただきたいのですが、見ての通り同じアプリでもApple Watchのほうが詰まったUIになっています。ただでさえデバイス自体が小さめな上に密度の高いUI、具体的にはアプリ内で常時上にナビゲーション領域が表示されたりする、ということでなおさら小ささが際立っているわけです。

小ささをより体感するために、iPhone 6の画面でApple Watch 38mmの画面サイズを表現してみました。

アイコンが一つにラベルが一つプラスアルファ程度がせいぜいの大きさしか無い、というのがよく分かるかと思います。

したがって繰り返し繰り返し随所で述べられていると思いますが、画面上に表示する要素は徹底的に少なくする必要があります。私も十分に少ない要素だけを画面に表示するように心がけていたつもりでしたが、実際にデバイスに触れたあとに見返すとまだまだ要素が多すぎるぐらいです。少なすぎるのではと心配になるぐらいまで減らしてちょうどいいのではないでしょうか。

Appleの標準のアプリなどでかなり高密度なUIを採用しているものもありますが、そこは真似しないほうが良いと考えています。具体的には標準のマップアプリなどは38mmモデルの上では細かすぎて地図を読み取るのが極めて困難でした。

■Glanceこそがすべて

Apple Watchのインターフェースのナビゲーションは以下の図のようになっています。

基本は時計フェイスが表示されていて、そこから竜頭を押すとHomeに遷移してアプリを選択して起動することができます。時計フェイスを下にスワイプすると上からNotification Centerが表示され、上にスワイプすると下からGlanceが表示されます。Glanceは左右スワイプで次々に閲覧することができます。感覚的にはGlanceはiOSデバイスにおけるWidgetのようなもので、常時Widgetが時計フェイスの下に並んでいるようなイメージをするとわかりやすかったです。

このインターフェースの中でアプリができることで、最も重要になってくるのがGlanceです。操作してみてわかったのですが、Home画面からアプリを起動するのはただでさえ小さいWatchの画面上に無数の小さいアイコンが並ぶため困難苦痛を極めます。したがって必然的にアプリの状態を確認したりアプリを起動するのはGlanceを使うのが最も楽でスピーディで良いということになります。GlanceこそがApple Watchアプリにおけるすべての窓口と言えそうです。ここをどれだけ便利に使いやすく見せるかによってアプリの価値が変わってくるかもしれません。

■ネイティブアプリは速いが転送は遅い?

気になるApple Watchの動作速度ですが、まず通常の用途ですとかなりサクサクと動作しました。時計フェイスから通知センターに遷移したりGlanceを見たり、Glance間を切り替えたりするぶんには素晴らしい応答速度で、手元のAndroid Wearデバイスよりも機敏に感じました。

これがアプリとなってくるとだんだんと遅さが感じられる場面が出てきます。気になった点としては、

  • いくつかのGlanceについてロードが終わらない、ないしロードが遅すぎる。マップ・天気・株価が該当。
  • GlanceまたはHomeからのアプリの起動が遅いときがある。フィットネスで該当。
  • フィットネスで「開始」ボタンを選択してから実際に開始するまでに明らかに感じられる遅れがあった。
  • マップアプリについてはロードが遅く、地図が表示されるのも遅い。

これらから推測するに、おそらくネイティブでアプリが動作している部分に関しては素晴らしいパフォーマンスが得られているものの、本体側のiPhoneからデータを転送している箇所に関しては顕著にパフォーマンスが落ちているのではないか思われます。

今回体験する事ができた実機にはサードパーティ製のアプリが入っていなかったので、我々開発者が作ったアプリに関してどの程度のパフォーマンスが得られるのかは全く不明ですが、この調子ではあまり良い結果が得られないかもしれません。今後のSDKの拡張でApple Watchネイティブのアプリが作れるようになるまでは、本体からデータを転送する頻度および転送量を少しでも削減できるようなつくりを目指すしかなさそうです。

■妄想とか将来の話

その他現状はサードパーティ開発者からは使えないのですが、将来的に面白くなりそうだと思った点を挙げます。

まず竜頭コントロールについてですが、現状竜頭コントロール入力をアプリ側から取得することができないのはみなさんご存知のとおりです。触った感じ竜頭自体は非常に良く出来ていたのですが、画面のどの箇所が竜頭でコントロールできるのか出来ないのかがいまいちよくわからないという問題があるように思えます。最も顕著な例は時計フェイスのカスタマイズUIで、これはカスタマイズする箇所をタップしてから竜頭で項目を選択するという仕組みになっているのですが、直感的に非常にわかりづらかったです。きちんと考えて統一的に使われていれば便利かもしれませんが、画面のタッチとの併用がほぼ必須なため竜頭だけでコントロールできなかったりなど、課題が山積みのように感じます。

逆にForce Touch(強く力を込めて押しこむようにタップする)機能ですが、これは非常に優れているように感じました。ダブルタップと特性は似ていますが、精密動作を必要とせずTapticエンジンによるフィードバックによって入力成功が伝わる点を考えてもダブルタップより圧倒的に優れている入力方式と言えます。現状Force Touchは自由に使うことができずメニューの表示用途に限定されていますが、これはまずForce Touchという操作の存在を確実にユーザーに理解してもらうという意味で良いと思います。この操作が広まればゆくゆくはiPhoneやiPadにもForce Touch搭載されることが確実でしょうし、アプリは積極的にForce Touchを取り入れていくべきと思いました。

将来的にForce TouchがiPhoneに導入されるとなると、iOSのAndroidに対する現在の弱点であるメニューボタンの不在を補う重要な役割になってくるかもしれませんね。さらにゲームでも大変有効に使える入力方式に間違いありません!夢が広がります。

Force Touchといえばその対となるTapticエンジンも非常に素晴らしかったです。Force Touch時のフィードバック、通知時のフィードバック、友人へのハートビートの送信、すべてで全く異なる触覚が伝わってくるのがまさに見事でした。まったく画面を見なくても触覚の違いだけで何が起こっているのかを判別できるほどです。現状Tapticエンジンを自由に触ることはできませんが、もし開放されたらTapticからのフィードバックだけで画面を全く見ないでも十分に使えるアプリが作れるかもしれません。全くユーザーを煩わせることない究極のUIになりうるかもしれませんね。

最後にGlanceについてちょっと触れます。基本的に現在サードパーティのアプリがGlanceでできるのは情報を表示するだけで、Glanceがタップされた時の挙動もWatchアプリが起動するだけに固定されてしまっています。

ところがAppleのネイティブアプリである心拍数Glanceについては、なんとGlanceが表示された瞬間に心拍数が自動的に計測開始され、さらにボタンをタップするとダイアログが表示されるというつくりになっていました。すなわち機能が開放されていないだけでリッチなGlanceを作ることも可能なようです。先にGlanceこそがすべてだと述べましたが、このリッチなGlanceを作る機能が開放されたらApple Watchのサードパーティアプリの可能性は更に広がると思います。

2015年3月16日月曜日

WatchKit 向けの UIImage Animation を簡単に実装するためのライブラリ ParaMangar を作りました

いよいよApple Watch発売日まで1ヶ月ということで皆様精力的にWatch向けのアプリを作成されているのではないかと思いますが、現状のWatchKitで誰もが一度はマジギレ不満に思う点がアニメーションです。

こちらのブログにある通り、WatchKit向けのアニメーションを作成するのは現状極めて面倒と言わざるを得ません。

http://d.hatena.ne.jp/shu223/20150214/1423901142

そこでiOS側でUIViewを今までどおりレンダリングして、その結果をファイルにしたりUIImageにしてWatchKitに渡せばいいじゃない!というライブラリを書いてみたので公開いたします。

https://github.com/akisute/ParaMangar

このParaMangarを使うとこんな感じでアニメーションが作れます。


ライセンスはMITです。
あまりテスト出来ていないのでバグだらけかもしれませんが、ご意見issueなどgithubでお寄せいただければ幸いでございます!!

2015年2月2日月曜日

YouTube などのフルスクリーンで再生される UIWebView の動画をプログラムから終了させる方法

UIWebViewでyoutube.comやvine.coなどのサイトの動画を開いた場合、動画がフルスクリーンの専用動画プレイヤーViewControllerで再生されることがあります。このフルスクリーン動画を閉じる方法です。

方法1: 安全な方法

JavaScriptのFullscreen APIを使って安全に閉じることができます。iOS 6以上で動作確認済みです。

Fullscreen APIについては以下の資料が詳しいです。


方法2: animated, completionの制御もしたい場合

ここからがお待ちかねの黒魔法になります。
先ほどのJavaScriptを使った方法ではフルスクリーン動画を閉じる際のアニメーションを制御できません(必ずアニメーションが発生します)。またフルスクリーン動画を閉じ終わったタイミングのcompletion handlerが存在しません(JavaScriptでハンドルしようにも、videoタグのonendedイベントは通常の再生終了時にもイベントが飛ぶ上にアニメーション終了時ではなく再生終了時にイベントが飛ぶため使いづらい)。

通常はこれらが問題になることはまずありませんし、万一あったとしても仕様のほうを変えるほうが適切ですが、何らかの止むに止まれぬ理由によりなんとかしなければならなくなる場合が稀によくあります。

そこで画面上にUIWebView経由で表示されている動画プレイヤーViewControllerをビュー階層をたどって見つけ出し無理矢理dismissViewController:completion:で消すという方法を取ります。iOS 6以上にて動作確認済みです。ただしiOS 7以下の場合とiOS 8の場合で全く構造が異なり、将来にわたって動作するか非常に怪しいです。

Private APIの名前がバリバリ含まれるコードになっておりますので審査に出すアプリには使用しないことを強くおすすめさせていただきます。

2015年1月15日木曜日

ReactiveCocoa を Swift から使ってみた(2) KVO編

前回の記事から引き続き、ReactiveCocoaを触ったりしています。FRPの概念に慣れてくると通常のプログラミングスタイルでは得られない知見に遭遇出来てなかなか面白いです。

今回はまずSwiftでReactiveCocoaを学ぶときに参考にするドキュメントについてご紹介したいと思います。といってもSwiftに特化したドキュメントはほとんど存在しないため、Objective-CでReactiveCocoaを学ぶときのドキュメントを見て学ぶしか無いのが現状です。

私は個人的にはサンプルコードを直接触るほうが性に合っているようなので、以下のオープンソースのReactiveCocoaで作られたアプリのコードを見ています。
https://github.com/AshFurrow/C-41
https://github.com/jspahrsummers/GroceryList
GroceryListのほうがより本格的にFRPっぽい書き方をしているのでオススメです。実際にコードを動かしたい場合はC-41のほうが比較的簡単に動かしやすいと思います(それでも使われているライブラリが古いためビルドを通すのが大変だったりしますが・・・)

SwiftでReactiveCocoaを使っている例としてはCarthageが良いと思います。ただしMacのコマンドラインアプリなのでUIまわりがらみのサンプルとしては余り参考になりませんでした。
https://github.com/Carthage/Carthage

例えばReactiveCocoaを使う上でどうしても欠かせないのがRACObserveを使った特定プロパティに対するKVOのシグナル化です。これにより特定のプロパティが変化した時にシグナルを受け取ることができるようになります。通常Objective-CでReactiveCocoaを使う場合は標準のRACObserveというマクロを使用すれば良いのですが、Swiftではマクロが使用できないため他の方法を採用する必要があります。

調べてみたところ、幸いにしてRACObserveマクロは単なるNSObjectのrac_valuesForKeyPathメソッドのラッパですので、以下のようにしてrac_valuesForKeyPathを使用すれば解決できます。


ここで注意することとして、KVO対象となるプロパティにはdynamic修飾子を付ける必要があります。Objective-Cにもdynamic修飾子はありましたが、Swiftのdynamic修飾子はObjective-Cのものとは意味が異なりdynamic修飾子を付けたプロパティについてObjective-Cと同様に動的プロパティアクセスを行うようにする(具体的にはobjc_msgSendする?)という意味があります。詳細については以下の記事を参照してみてください。
http://stackoverflow.com/questions/24092285/is-key-value-observation-kvo-available-in-swift#comment39273366_24092370
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html

2015年1月1日木曜日

Swiftプログラマ格付けチェック (2015新年スペシャル)

Swiftプログラマ格付けチェック

今回一流のSwiftプログラマの皆さんに格付けチェックしていただくのはこちら!

JSONライブラリ

です!

ひとつは日本が誇る一流プログラマ、dankogai氏が作成されたJSONライブラリ、githubスター数312
ひとつは当ブログ管理人三流プログラマ、akisuteが適当にググって見つけたJSONライブラリ、スター数16
となっております。
皆様にはこの2つのうちからdankogai氏のライブラリを当てていただきます!

Aのライブラリ
Bのライブラリ
(コメントはすべて削除しています)

それでは正解だと思ったほうの部屋に入っていただきましょう!

Aが正解だと思った人の部屋
Bが正解だと思った人の部屋

2014年12月26日金曜日

Carthage で Code Sign Identity および Provisioning Proifleを直接指定してビルドする方法

誰得情報なのでメモだけ残しておきます。
手元のMacに複数のCode Sign Identityがあってビルドが失敗する人向けです。

akisute Test$ carthage update
*** Fetching SwiftState
*** Fetching SwiftTask
*** Checking out SwiftState at "1.1.1"
*** Checking out SwiftTask at "2.4.0"
*** xcodebuild output can be found in /var/folders/gk/205sh3lx1qdfrwtxcb_tj97m0000gp/T/carthage-xcodebuild.uzBMvq.log
*** Building scheme "SwiftState-iOS" in SwiftState.xcodeproj
*** Building scheme "SwiftState-OSX" in SwiftState.xcodeproj
*** Building scheme "SwiftTask-iOS" in SwiftTask.xcworkspace
*** Building scheme "SwiftTask-OSX" in SwiftTask.xcworkspace
** BUILD FAILED **


The following build commands failed:
Check dependencies

(1 failure)

akisute Test$ tail -n 10 /var/folders/gk/205sh3lx1qdfrwtxcb_tj97m0000gp/T/carthage-xcodebuild.uzBMvq.log
** BUILD SUCCEEDED **

Build settings from command line:
    SDKROOT = macosx10.10

=== BUILD TARGET SwiftState-iOS OF PROJECT SwiftState WITH CONFIGURATION Release ===

Check dependencies

Code Sign error: Multiple matching codesigning identities found: Multiple codesigning identities (i.e. certificate and private key pairs) matching “iPhone Developer” were found.



こういうときは環境変数を使って、
JP11688 kaa$ CODE_SIGN_IDENTITY="iPhone Developer: Masashi Ono" carthage update
*** Fetching SwiftState
*** Fetching SwiftTask
*** Checking out SwiftState at "1.1.1"
*** Checking out SwiftTask at "2.4.0"
*** xcodebuild output can be found in /var/folders/gk/205sh3lx1qdfrwtxcb_tj97m0000gp/T/carthage-xcodebuild.5z3CLW.log
*** Building scheme "SwiftState-iOS" in SwiftState.xcodeproj
*** Building scheme "SwiftState-OSX" in SwiftState.xcodeproj
*** Building scheme "SwiftTask-iOS" in SwiftTask.xcworkspace
*** Building scheme "SwiftTask-OSX" in SwiftTask.xcworkspace


または、
JP11688 kaa$ PROVISIONING_PROFILE="XXXX-XXXX-XXXX-XXXX" carthage update
*** Fetching SwiftState
*** Fetching SwiftTask
*** Checking out SwiftState at "1.1.1"
*** Checking out SwiftTask at "2.4.0"
*** xcodebuild output can be found in /var/folders/gk/205sh3lx1qdfrwtxcb_tj97m0000gp/T/carthage-xcodebuild.5z3CLW.log
*** Building scheme "SwiftState-iOS" in SwiftState.xcodeproj
*** Building scheme "SwiftState-OSX" in SwiftState.xcodeproj
*** Building scheme "SwiftTask-iOS" in SwiftTask.xcworkspace
*** Building scheme "SwiftTask-OSX" in SwiftTask.xcworkspace

参考: http://stackoverflow.com/questions/9264727/code-sign-identity-parameter-for-xcodebuild-xcode4
参考: https://github.com/Carthage/Carthage/issues/235

Container View Controllerを作ってみよう

今日は冬休みの工作ということで、iOSのContainer View Controllerを作ってみようと思います。

Container View Controllerとは

一言で言うと、他のUIViewControllerを包含して表示するUIViewControllerのことです。どのように包含して表示するかによって、たとえばUINavigationControllerやUITabBarController、UIPageViewControllerのような実装があります。

iOS 5からはこのContainer View Controllerを自作する事が可能になりましたが、実装が面倒なのと大体の場合においてUIKitが用意しているContainer View Controllerを使うかcocoapodsあたりからそれっぽいライブラリを拾ってくれば解決するためかあまり具体的な実装方法が話題になっていないようです。今回たまたま作る機会があったのでその時の内容をメモしておこうかと思います。

Container View ControllerへのView Controllerの追加

Container View Controllerを作るには、UIViewControllerを継承したクラスを作成して、そこに- (void)addViewController:(BOOL)animatedのような管理対象のView Controllerを追加するメソッドを作ってやればよいです。
ここで、Container View ControllerにView Controllerを追加する上で基本的にやらなくてはならないことは以下の5ステップにわかれます。
  1. addChildViewController:
    • このタイミングでContainer View Controllerに対象のView Controllerが格納されます。ただしViewはまだ表示されません。
  2. didMoveToParentViewController:
    • Container View Controllerに対象のView Controllerが格納されたことを通知します。
  3. beginAppearanceTransition:animated:
    • これからViewが表示されることをContainer View Controllerおよび対象のView Controllerに通知します。viewWillAppearに相当します。
  4. addSubview:
    • 実際にViewを表示します。必要に応じてアニメーションもつけます。
  5. endAppearanceTransition
    • Viewの表示が完了したことをContainer View Controllerおよび対象のView Controllerに通知します。viewDidAppearに相当します。

実際のサンプルコードはこちら。


Container View ControllerからのView Controllerの削除

View Controllerに追加した時に実施したことを逆順に実施してやればOKです。- (void)removeViewController:(BOOL)animatedのようなメソッドを作ってやって、そこに実装を書けばよいでしょう。
サンプルコードにするとこんな感じになります。


shouldAutomaticallyForwardAppearanceMethodsの設定

先に答えだけいうと、何もしないでいいです。このメソッドの存在そのものを忘れて構いません。

iOS 6から追加されたメソッドで、overrideして使用します。このメソッドがYESを返すときは、Container View Controller自身が他のContainer View Controllerに追加されるなどして表示されviewWillAppear/viewDidAppearが呼び出されるタイミングで自動的にchildViewControllersに対してもviewWillAppear/viewDidAppearを呼び出します。NOの場合は自動的に呼び出されないため手動でchildViewControllersの表示状態を管理し、適時beginAppearanceTransition:animated:を呼び出す必要があります。

デフォルトはYESで、基本的にはデフォルトのまま使えば問題ありません。NOを返したいケースは、たとえばContainer View Controllerが画面に表示されてから一瞬遅れてchildViewControllersをアニメーションしながら表示したいなどの要件がある場合に限られるでしょう。

2014年12月23日火曜日

ReactiveCocoa を Swift から使ってみた

FRP(Functional Reactive Programming)なるものが流行っているらしいので、私もたまには流行に乗っかってみることにしました。手始めにReactiveCocoaをSwiftで一日ほど使ってみました。

導入

こちらのブログにまとまっていますので、そちらを参照していただければ良いかと。基本的にはCocoaPodsで一発です。
http://tnakamura.hatenablog.com/entry/2014/11/15/how_to_use_reactivecocoa_in_swift

ドキュメント

基本的にはプロジェクトのGitHubにしっかりドキュメントがあるのでそれを見ればだいたい大丈夫かなと思います。
https://github.com/ReactiveCocoa/ReactiveCocoa
https://github.com/ReactiveCocoa/ReactiveCocoa/tree/master/Documentation
APIの使い方がわからなければヘッダファイルを見れば相当詳細にコメントがついているのでそれでほぼ問題無いです。それでもわからなければ結構ググればかなりヒットします。熱狂的なファンがいるようです。

概念

こちらのページに非常に詳細に書いてあるのでお勧めです。こちらを見るだけでなんとなく概念がつかめてReactiveCocoaは何をするフレームワークなのかがわかって良いと思います。以下主要なクラスに対して私が理解した内容です(間違ってたらゴメンナサイ)。

RACStream

連続した値を表すすべての既定概念。連続した値というのは今現在すぐに返せる値も通信や計算などによって将来的に返される値も含む。関数型言語で言うところのモナドらしいけどモナドはさっぱり。

RACSequence

RACStreamの実装の一つで、Pull-Baseなもの。すなわちユーザが値を要求して、それに対して値を返すオブジェクト。関数型言語でいうところのリストとかシーケンス。

RACSignal

RACStreamの実装の一つで、Push-Baseなもの。すなわちシステムが何らかのイベントやタイミングに応じて値を返すオブジェクト。もちろん値が返ってくるのは直後かもしれないし遠い未来かもしれない。

RACSubscription

RACSignalに対するコールバックみたいなもの。Promiseパターンのthenとかcatchとかfinallyみたいなもの。

RACCommand

UIBarButtonItemとかUIButtonなどのユーザーインタラクションを表すクラスみたいです。まぁ要するにIBActionみたいなもののようです。加えてRACSignalを使ってenabledの状態をコントロールしたりsenderをfilter/map/reduce/その他いろいろ加工可能。

RACTuple

引数とか返り値とかでよく使われるタプル。Swiftのtupleとは違うので注意。

だいたいこれぐらいわかっていたらコードが書けました。

実験

単純なメモアプリを作って実験しようと思いとりあえずこんなテーブルビューを作ってみました。

そしたら問題が出るわ出るわ。

マクロが使えない

Swiftからだと便利なマクロが使えないので非常に困ります。さらに次に挙げる一部の問題はマクロがないと解決できません。

配列の内容変化に対してRACSignalを取れない

オブジェクトの変化を監視するRACSignalがKVOを元に実装されていて、KVOがArrayに対して使用できないので当然なんですが、これのせいでいきなりReactiveCocoaの世界からいつものCocoaの世界に引き戻されます。
良い対処法はないようです。一応Objective-Cならマクロとか使って対応可能なように見えますがSwiftではどうしようもありませんでした。
https://github.com/ReactiveCocoa/ReactiveCocoa/issues/500
https://github.com/ReactiveCocoa/ReactiveCocoa/issues/1197

delegateパターンをRACSignalに変換するのが厄介

RACSubjectという自分で自由自在に状態を操作できるRACSignalのサブクラスを使ってシグナルをコントロールする方法がまずお手軽です。

またはこちらのブログで紹介されているrac_signalsForSelectorなどを使う方法もあります。
http://spin.atomicobject.com/2014/02/03/objective-c-delegate-pattern/

内部的に黒魔法が多い

先ほどのrac_signalsForSelectorもそうですが、内部で平然とmethod swizzlingを使い放題使いまくっていたりするのでちょっと怖いです。


まとめ

Objective-CでFRPの勉強をするにはちょうどいいんじゃないでしょうか。

2014年12月19日金曜日

SwiftからCやObjective-Cのライブラリを扱うときのテクニック数点

Objective-C Bridging Headerを利用することで、Swiftは既存のいかなるC/Objective-Cコードのシンボルでも呼び出すことが可能になっています。しかしながら場合によってはSwift単体では素直に書きづらいハマりどころがあります。C/Objective-Cのラッパーを作り、Objective-C Bridging Header経由でSwiftから呼び出せば全ての問題は解決できるのですが、面倒くさいですしやはりSwift単体で何とかしたいですよね。そこでここでは素直に書きづらいハマりどころと、それを何とかしてSwift単体で解決する方法をご紹介します。

※以下の情報は2014/12/19現在のものです。Swiftは言語仕様の変化が激しいので予期せず変更されている場合があります、ご了承ください。

1. ARCに管理されていないObjective-Cオブジェクトを扱う

例えばKeychainを扱うAPIなどで、ARCに管理されていないObjective-Cオブジェクトを扱うことがあります。このような場合にはUnmanaged型を使用します。

Unmanaged型はメソッド経由でretain, release, autoreleaseを行うことができるほか、takeUnretainedValue()またはtakeRetainedValue()メソッド経由でT型のSwiftオブジェクトを取り出すことができます。

2. C言語のポインタを扱う

生のC言語のポインタを扱う場合、たいていのケースではUnsafePointer型を使うようにCのAPIがSwiftのAPIに変換されます。このとき、Swift上ではUnsafePointer型を要求するのに、C言語のAPI的にはNULLを渡したい場合には、nilを渡すことができないので、代わりにUnsafePointer.null()を渡すことができます。

UnsafePointer以外にもCOpaquePointer型やCFunctionPointer型に変換されるAPIもありますが、この場合も同様にCOpaquePointer.null()やCFunctionPointer.null()をAPIに渡してやればうまくいきます。

3. cStringUsingEncoding()メソッドの罠に注意する

SwiftにはSwiftネイティブのString型とCocoaのNSString型が存在します。基本的にはこの2つは自動的にうまい具合にブリッジされるためプログラマは違いを意識する必要がありませんが、実はcStringUsingEncoding()メソッドを使う場合にはこれが重大な問題になってきます。String.cStringUsingEncoding()は[CChar]?を、NSString.cStringUsingEncoding()はUnsafePointerを返すのです。さらにコンパイラは文字列をStringとして解釈するのを優先するため、先ほど述べたC言語のAPIに渡す際に型が合わないという理由でエラーになりがちです。

対策として上記の通り明示的にNSStringにキャストすることをおすすめします。

4. enum値のOR結合を何とかする

すみません、なんともなりませんでした(´・_・`)
例えば以下のようなコードを書くこと自体は可能なのですが、適切なenum値を得ることができません。optionsはnilになってしまいます。

どうしてもOR結合が必要なenum値が存在する場合は、現状C/Objective-Cでラッパーを作りObjective-C Bridging Header経由でSwiftから呼び出すしかないようです。


2014年9月30日火曜日

GitHub:Enterprise 環境下では CocoaPods を 0.34.0 以上にアップデートしてはいけません

タイトルの地点で結構出落ちですが、本日少々厄介な問題にぶち当たってしまったので共有いたします。

発生条件

CocoaPods 0.34.0以上を使用し、GitHub:EnterpriseのgitリポジトリをPodSpecないしSourceリポジトリとして使用している場合に発生します。

発生する問題

CocoaPodsのgitを利用するアクションが全く成功しなくなります。具体的にはpod install, pod updateなどが全く通りません。

問題の原因

公式ブログにあるとおり、0.34.0以上からCocoaPodsは高速にgitリポジトリをcloneするため--depth 1 (shallow clone) オプションを採用するようになっています。しかしながらGitHub:Enterprise側の技術的問題なのか、GitHub:Enterprise内のgitリポジトリに対するhttpないしhttps経由でのshallow cloneが成功しないため永遠にpod installやpod updateが終わりません。

対処方法

根本的にはCocoaPodsかGitHub:Enterpriseかいずれかが修正されるのを待つしかありませんが、CocoaPods側としてはあまり積極的に対応する気がないようなので、GitHub:Enterprise側に対応を期待するしかないでしょうか。もちろん全てのGitHub:Enterprise環境下で発生するとは限りませんが、いずれにせよ注意する必要があります。

2014/10/01追記

GitHub:Enterpriseに問い合わせてみたところ、こちらは既知の問題であり現在修正中で、次のリリース時に修正されるそうです。次のリリースが社内環境にデプロイされるまでの辛抱ですね。

こちらからは以上です。

2014年9月8日月曜日

Swift から Core Data を操作するときはこの2点だけ気をつけよう (Xcode 6 beta 7編)

将来的にXcode側の対応が変わる可能性が極めて高いので暫定ですが、Xcode 6 beta 7でSwiftからCore Dataを触った時に注意するポイント2点まとめです。この2点にだけ気をつければSwiftでもCore Dataは案外あっさり動きますのでご安心ください!

1. プロパティの設定の仕方


  • @NSManagedを使うこと
  • Int, BoolではなくNSNumberを使うこと。StringはStringでOK
  • Many関連にはNSSetを指定すること



2. entityNameの与え方


  • コード上ではクラス名だけを与える
  • Model Editor上ではモジュール名.クラス名(完全修飾名)を与える



Model Editorではこのようにモジュール名.クラス名の形で設定する必要があります


あとは普段Objective-Cで使っている時と同じように使えばOKです。一応、簡単なSwiftからCore Dataを操作するラッパライブラリを書いてみたので、もし宜しければ見てみてください。
https://github.com/akisute/Dove

【ヤヴァい】リリース直前の Swift の仕様が早くも悲惨なことになってる

正式版リリースまで後一ヶ月と噂されるXcode 6と新言語Swiftですが、リリース一ヶ月前にも関わらずその仕様が早くも悲惨との声がごく一部から上がっているようです!!



public private(set)って結局どっちなの!?

beta 5から追加されたアクセス制限指定子ですが、その仕様に疑問の声が!


なるほど、public private(set) var numberはキモいですね〜。ちなみにこれはgetterがpublicでsetterがprivateな変数の宣言のようです。なんかもっといい文法はなかったんでしょうか・・・



一行closureを使うときに注意!思わぬ落とし穴が!




これはSwiftの言語仕様上、実行コードが一行しかないclosureは暗黙的にreturnするものとみなされてしまうからのようです。これはちょっとひどいですね〜〜!



中には良い一面も・・・?



Nil Coalescing Operatorというのは ?? 演算子のことです。これは A ?? B のように使い、AがNot NilならAを、そうでなければBを返すという演算子です。なかなか便利に使えますよ!ちょっとはSwiftも見なおしたかも!?



おわりに

某まとめっぽくしたらなんかイマイチな感じになりました(´・_・`)
あとAppleふざけんな

Apple Push Notification で送られてきた通知を通知センターから消すたった一つの方法

いや、それがたった一つしか方法ないのはさすがにどうかと思うのですが。

皆さんもiOSのアプリを使っていて、通知を受け取った後にアプリを開いても通知センターから通知が消えず地味にイライラする現象に見舞われたことがあるのではないかと思います。私もお恥ずかしながらつい最近知ったのですが、実はこれアプリ側で何も対応しないと通知センターから通知は消えません。それどころかなんと通知センターからプッシュ通知を消すためのAPIも一切提供されていません。つまり普通にアプリを作ると通知センターから通知が消えないのです。Appleふざけんな

※注記: ここで私が消せない通知と呼んでいるものはApple Push Notification経由でのRemote Notiifcationです。UILocalNotificationは自由自在にアプリ側から消せるので、適切なタイミングで消してないのは単なる実装者の怠慢となります。

しかしながらTwitterやFacebookアプリはきちんとアプリが開かれたタイミングで通知センターから通知が消えるようになっています。不思議です。そこでちょっと調べてみました。

解決策

以下のリンク先にあるように、一度applicationIconBadgeNumberを1以上の数字に設定してから、0に戻すと、全てのApple Push Notification経由での通知が消えるようです。

http://stackoverflow.com/questions/8682051/ios-application-how-to-clear-notifications
http://stackoverflow.com/questions/9925854/remove-single-remote-notification-from-notification-center

iOS 5~8のすべてのバージョンで動作することを確認しています。少々ダサいですがこの方法でしのいでいきましょう!

※注記: 個別に通知を消す方法はありません。なおiOS 8より、ユーザーが直接通知センターから通知をタップしてアプリが起動した場合のみ、タップされた通知が自動的に消えるように仕様が変更されています。相変わらずアプリから明示的に消すことはできません。Appleふざけんな

2014年8月25日月曜日

Silent Push / Background Fetch 時の fetchCompletionHandler に渡す引数ごとの挙動の違いを調べてみた

2014/09/10追記: 追加調査によって判明した事項を追記しています。


iOS 7から追加されたPush通知によるバックグラウンド処理機能 (Silent Push) および定期的なバックグラウンドフェッチ機能 (Background Fetch) ですが、これらの機能はバックグラウンド処理が完了したタイミングでいずれもfetchCompletionHandlerにUIBackgroundFetchResult型の値を渡すような設計になっています。

ちょっと調べれば、大体どこの解説サイトにも以下のように説明があります。


  • UIBackgroundFetchResultNewData
    • 新しいコンテンツの取得に成功し、新しいデータが存在した場合
  • UIBackgroundFetchResultNoData
    • 新しいコンテンツの取得には成功したが、新しいデータが用意されていなかった場合
  • UIBackgroundFetchResultFailed
    • 通信エラーなどの理由でコンテンツの取得そのものに失敗した場合


問題は、これらの値をfetchCompletionHandlerに渡したら「何が起きるか」を解説しているサイトや文献がほとんど存在しません。そこでこのたび独自に調査してみました。

UIBackgroundFetchResultの値が影響する項目

いろいろ調べてみた結果、以下の参考文献によりUIBackgroundFetchResultの値は2つの項目に影響することがわかりました。
http://www.objc.io/issue-5/multitasking.html
https://codeiq.jp/magazine/2013/12/3022/

ひとつは、以降のSilent PushおよびBackground Fetchの挙動に対する影響です。OSが返された値を元に、アプリが使用したプロセス時間・電力消費などを判断し、以降のSilent PushやBackground Fetchの頻度に反映させるというものです。ただしどのような判断が行われどのように頻度に対して影響するかは完全にブラックボックスとなっており文献がありません。

もう一つはApp Switcher(ホームボタンをダブルタップした際に表示されるアプリ切り替え画面)のサムネイル画像、スナップショットを更新するために使用しているようです。値が渡されたタイミングで必要に応じてアプリのスナップショットを再描画するために、なんとアプリがバックグラウンドにいるにも関わらずその場でUIの再描画が行われます。この際通常のフォアグラウンドにアプリが存在する場合とまったく同様に、UIViewやUIViewControllerのメソッドが呼ばれます。例えばviewWillAppear, viewDidAppear,  didMoveToSuperViewなどが呼び出されます。

従いましてUIBackgroundFetchResultを渡す際には必ずメインスレッドで行う必要があります。NSURLSessionのdelegateを受け取ったスレッドから直接値を渡すとバックグラウンドでひっそりクラッシュします。ひっそりクラッシュする程度ならまだしもですが、これらのUI処理の中にバックグラウンドで呼び出されることを考慮していない処理が入っていると数々の問題を引き起こします。例えばviewDidAppearのタイミングでユーザさんがその画面を見たものと判断してデータやフラグを更新するですとか、Google Analyticsに統計情報を送っているですとか、そういうことをやっていると一気に大問題になります。

長くなりましたが、以上を踏まえまして、各値ごとにどのような影響があるかを実際に実機でテストして調べました。

UIBackgroundFetchResultNewDataを渡した時

スナップショットの更新が発生します。
Silent Pushの受取可能頻度に対して影響はないようです。一日4回程度、3〜6時間程度の間隔が空いていれば、全く問題なく受取可能です。ただし10分〜15分間隔で受取を行うとレートリミットがかかりはじめ、1〜2日以内に受信不可能になります。一度受信不能になったデバイスが再度受信可能になるかどうかはわかりませんが、数日間程度置いていても効果がなかったので、再度受信ためには最悪デバイスの完全なワイプとリセットが必要になるかもしれません。
Background Fetchの発生頻度に対しても影響はないようです。

UIBackgroundFetchResultNoDataを渡した時


スナップショットの更新が発生します。
三ヶ月程度様子を見ているところ、Silent Pushの受取り頻度に対してNewDataの場合とほぼ同様に影響はなさそうです。したがってNewDataを返す場合と何が違うのか現在のところわかっていません。

以下、旧記述です。
スナップショットの更新は発生しません。スナップショットの更新に伴って大問題が発生する場合には一番簡単な解決策になります。
ただし毎回毎回UIBackgroundFetchResultNoDataばかりを返却していると、Silent Pushの受取可能頻度およびBackground Fetchの発生頻度に対してペナルティがかかる恐れがあります。何度試行してもデータが取れないので、システムが再試行頻度を下げるのではないかという推測ですが、実際に観測できたわけではありませんので、なにか情報が入り次第また詳しくお伝えします。

UIBackgroundFetchResultFailedを渡した時

スナップショットの更新は発生しません。
こちらもUIBackgroundFetchResultNoDataと同様、UIBackgroundFetchResultFailedばかりを返却していると、Silent Pushの受取可能頻度およびBackground Fetchの発生頻度に対してペナルティがかかる恐れがあります。何度試行してもデータが取れないので、システムが再試行頻度を下げるのではないかという推測ですが、実際に観測できたわけではありませんので、なにか情報が入り次第また詳しくお伝えします。


2014年8月12日火曜日

Apple Push Notification (APN) 使用時の delegate の挙動について、 iOS 7以降 / iOS 6以前の差をまとめた

iOS 7以降とiOS 6以前で、俗にいうリモートPush通知の受け取り方と受け取った際の挙動がまるで違っているので、最近リモートPush通知を実装した時につまづいた箇所をまとめてみました。

使用するdelegate methodの違い

iOS 7以降


  • いかなる種類のPush通知においてもapplication:didReceiveRemoteNotification:fetchCompletionHandler:を使用します。
  • application:didReceiveRemoteNotification:fetchCompletionHandler:とapplication:didReceiveRemoteNotification:が両方実装されている場合も、application:didReceiveRemoteNotification:fetchCompletionHandler:しか呼び出されません。application:didReceiveRemoteNotification:fetchCompletionHandler:のみ考えればOKです。
  • ただし、application:didReceiveRemoteNotification:が実装されており、application:didReceiveRemoteNotification:fetchCompletionHandler:が存在しない場合は、iOS 6以前同様の挙動になります。
  • 以下、長ったらしいのでapplication:didReceiveRemoteNotification:fetchCompletionHandler:を新メソッドと呼称します。

iOS 6以前


  • application:didReceiveRemoteNotification:を使用します。
  • 新メソッドが実装してあっても一切反応しません。
  • 以下、長ったらしいのでapplication:didReceiveRemoteNotification:を旧メソッドと呼称します。

挙動の違いまとめ

以下に表にしてまとめました。ここで、
iOS 7以降とは新メソッドを使用する場合、iOS 6以前とは旧メソッドを使用する場合のことを指します。


iOS 6以前iOS 7以降
初回起動application:didFinishLaunchingWithOptions:に
UIApplicationLaunchOptionsRemoteNotificationKeyが付いて呼び出される。
旧メソッドは呼び出されない。
application:didFinishLaunchingWithOptions:に
UIApplicationLaunchOptionsRemoteNotificationKeyが付いて呼び出される。
その後、UIApplicationStateActiveの状態で、新メソッドも呼び出される。
handlerについては、content-available指定が1の場合のみ付いてくる(未確認)。
起動中UIApplicationStateActiveの状態で、旧メソッドが呼び出される。UIApplicationStateActiveの状態で、新メソッドが呼び出される。
handlerについては、content-available指定が1の場合のみ付いてくる(未確認)。
未起動UIApplicationStateInactiveの状態で、旧メソッドが呼び出される。UIApplicationStateInactiveの状態で、新メソッドが呼び出される。
handlerについては、content-available指定が1の場合のみ付いてくる(未確認)。
データ取得不可能。UIApplicationStateBackgroundの状態で、新メソッドが呼び出される。
handlerは必ず付いてくる。
この通知がアラートや通知センターに表示された場合、ユーザーがそれらとインタラクションしたらさらにもう一回UIApplicationStateInactiveの状態で新メソッドが呼び出される。

バックグラウンドでの通知受け取りとデータ取得

iOS 7から、Push通知のペイロードのapsオブジェクトにcontent-availableというプロパティが新たに追加されました。content-availableプロパティの値を数字の1に設定すると、ユーザーのインタラクションを介さずにバックグラウンドでアプリケーションが起き上がることができ、サーバから最新のデータの取得を行ったりする事が可能です。詳しくは「Push通知 バックグラウンド iOS7 content-available」とかその辺の単語を適当に組み合わせてググってください。山ほど解説しているページがあります。

サイレント通知が届かないときの処方箋

さて、上記バックグラウンドでの通知受け取りとデータ取得について、余り解説が見当たらなかった点についてまとめました。

iOS 7以降のPush通知は、apsオブジェクトのalertプロパティの有無とcontent-availableプロパティの有無によって挙動が変わります。具体的には以下の表のようになります。

content-availableがあるcontent-availableがない
alertがある通常通知
同時にバックグラウンドデータ取得も可能
通常通知
alertがないサイレント通知
バックグラウンドデータ取得のみ可能
送信できない

content-availableがない場合はiOS 6以前のPush通知と全く同じなので説明を割愛します。次にcontent-availableとalert両方が指定されている場合ですが、これはiOS 6以前の通常通知と基本は同じで、異なる点は通知を受け取った瞬間にバックグラウンドでアプリが立ち上がってデータの取得ができるという点だけです。したがいましてちょっと上記に記載しましたが、通知を受け取った瞬間と、ユーザーがインタラクションした瞬間で2回新メソッドが呼び出されるので、必ず新メソッドの中でUIApplicationStateを見て処理を分岐するようにしてください。

問題になってくるのがcontent-availableだけが指定されている通知、サイレント通知についてです。ほかのサイトでもよく紹介されていますが、この通知を使ってバックグラウンドでアプリを起動させて最新のデータをダウンロードしたりすることができます。以下にサイレント通知のペイロードの例を示します。

上が通常のサイレント通知ですが、下のようにサイレント通知と同時にバッジの数を更新することもできます。

さてこのサイレント通知ですが、とにかく「届かない!」というトラブルを耳にします。実際私が試した際にもなかなか届かないで弱りました。そこで調査したところ、サイレント通知のペイロードを工夫することできちんと通知が到達するようになりました。以下に到達率を改善したサイレント通知のペイロードの例を示します。

ご覧のように、空のsound要素を追加することで到達率が劇的に向上します。また2つ目の例のように、priorityというプロパティを10に設定するとこれまた到達率が劇的に向上します。どうやらこのpriorityプロパティはalertプロパティかsoundプロパティが設定されている場合はデフォルト10に、設定されていないサイレント通知の場合はデフォルト5に設定されているようで、それが原因で到達しない事が多いということがわかりました。
参考記事: http://stackoverflow.com/questions/19239737/silent-push-notification-in-ios-7-does-not-work

それから、Xcode上からプロジェクトのCapabilitiesの設定をするとき、Background fetchとRemote notificationsの両方の指定がおそらく必須と思われます。中にはRemote notificationsだけ設定しておけば良いと解説しているところもありますが、どうやらRemote notificationsは通知を受け取る箇所までだけで、肝心のバックグラウンドでのデータの取得はBackground fetchがないと実行できないのではないかと思われます。

UINavigationController.interactivePopGestureRecognizer の挙動を魔改造して変えてみる

注意: 今回の記事は久しぶりの誰得記事です。こちらで紹介されている手法を用いるにはプライベートAPIを操作する必要があるため、おそらく審査に通りません。じこせきにんでお願いします。

iOS 7からUINavigationControllerにinteractivePopGestureRecognizerというプロパティが新たに追加されました。これは「ナビゲーションコントローラの左端から右にスワイプすると前の画面に戻る」機能を司るgesture recognizerで、同じくiOS 7から追加されたUIScreenEdgePanGestureRecognizer(画面端からのみ反応するPan Gesture Recognizer)とUIViewControllerInteractiveTransitioning(ViewController間の画面遷移時に、ユーザーのインタラクションを追加する。例えば画面半分までスワイプしたら50%画面遷移するとか)の仕組みで実装されています。

ここで、例えばこの「ナビゲーションコントローラの左端から右にスワイプすると前の画面に戻る」機能について、見た目はそのままがいいんだけど左端からしか反応しないのが気に入らないから画面の中央でも反応するようにしたいという要望があったとします。自前で実装してもよいのですが、やはりApple純正の動きが良いですよね。

そこで以下のページで紹介されている黒魔術を使ってみたいと思います。
http://blommegard.se/blog/2014/01/31/a-take-on-custom-transitions-with-uinavigationcontroller/
こちらのページにUINavigationController内部でどのようにinteractivePopGestureRecognizerが実装されているかという解説があります。

  • 先述の通り、UIScreenEdgePanGestureRecognizerのインスタンスが使用されている
  • targetは_UINavigationInteractiveTransition型のオブジェクト、調査の結果_cachedInteractionControllerという隠しプロパティがそれに相当することがわかった
  • actionは@selector(handleNavigationTransition:)

ここまでわかったので、あとはtarget/actionを自分で生成したUIGestureRecognizerにセットしてやれば、標準の動きをジャックすることができるというわけです。今回はUIPanGestureRecognizerを使用しました。



このコードを実行してみると確かに画面中央からでもいつものスワイプジェスチャで戻る挙動が可能になっています。やりましたね\(^o^)/

2014年6月25日水曜日

App Extension や Widgets に App Container を使わないで簡単にデータを渡す方法

iOS 8から利用できるApp Extensionはアプリ本体とは別のプロセスとして動作するため、そのままでは簡単にデータを渡すことができません。公式のドキュメントではApp Containerという仕組みを使う方法が推奨されていますが、この方法はiOS Dev Center上でApp IDの設定が必要になったりするなど面倒がつきまといます。そこでより簡単にApp Extensionにデータを渡す方法がないか調べてみました。

方法1: UIPasteboardを使う方法

一番おすすめなのがこちらです。UIPasteboardというのは要するにコピペ用のクリップボードみたいなものなのですが、こちらシステムが用意している共通のもの以外にも自分のTeam IDでコードサインされたアプリ間すべてで共用できるPasteboardを作成する機能があります。この機能を使用すると非常に簡単にデータをやりとりすることができます。面倒な設定も不要ですし、persistentプロパティを設定することでディスクへの永続化も面倒を見てくれます。

以下にサンプルコードを掲載します。本体アプリのUIApplicationDelegate内でクリップボードを作成してデータを突っ込み、App ExtensionのWidgetViewController内でその値を使用するサンプルです。

注意点として、UIPasteboardのnameはあなたの作成したアプリ全体(同じTeam IDでコードサインされたアプリ全体)で共有になります。ですのでWidgetですとかTestのような単純な名前ではなく、com.akisute.MyApp.Widgetのような一意になる名前をつけることをおすすめします。

UIPasteboardについての詳しい使い方については、以下の記事をご覧ください。

方法2: Keychainを使う方法(未確認)

未確認なので確定情報ではありませんが、Keychainには複数のアプリ間で値を共有する機能がありますので、そちらを使えばおそらくアプリ本体とApp Extensionの間で値を共有する事が可能になります。詳しくは以下の記事をご覧ください。

2014年6月9日月曜日

既存の Objective-C のメソッド引数の Swift 上での扱われ方を調べてみた

前置き

こちらの記事には2014/06/09現在、公式にはリリースされていないiOS8プレリリースドキュメントへのリンクが含まれます。iOS8にて新しく追加された内容には一切触れておらずAppleとのNDA規約にも違反するものではないという認識ですが、場合により予告なく削除する可能性があります。予めご了承ください。

本題

iOS8プレリリースドキュメントを眺めていて気になったのですが、ほとんどのCocoaのメソッドの引数に!がついています。例えばNSKeyValueObservingプロトコルのaddObserver:forKeyPath:options:context:メソッドのシグネチャは以下のようになっています。

func addObserver(_ anObserverNSObject!,
      forKeyPath keyPathString!,
         options optionsNSKeyValueObservingOptions,
         context contextCMutableVoidPointer)

第1引数と第2引数に!がついているのがわかると思います。
更にもうひとつ気になったのが、このメソッドの第3・第4引数です。これらは元々0やNULLポインタを渡すことができる引数だったのですが、見ての通り引数が?で宣言されておらず、そのままnilを渡してしまうとエラーになってしまうように見えます。

直感的に考えると、

  • !がついている引数は呼び出し時に強制的にunwrapされるので、nilを渡してはいけない、必須引数なのではないか?
  • !がついていない第3第4引数はnilが渡せないのではないか?

という風に思うのですが、実際には

  • !がついている引数にnilを渡すことができる。さらにnilを渡してもランタイムでクラッシュしない。
    • 今回の例のaddObserver:forKeyPath:の場合はクラッシュしますが、これは元々のAPIがそうだったからで、例えば他のCocoaのAPIで!がついているものでnilを渡しても問題ないものが多数存在します。
  • !がついていないNSKeyValueObservingOptions, CMutableVoidPointerにnilを渡してもコンパイルエラーにすらならず、何の問題もなくそのまま動作する。

このような私の理解とは正反対の挙動をします。大変気になったので調べてみることにしました。

!がついている引数の謎

これについてはそもそも私のSwiftに対する理解が完全に間違っていました。正しい理解は以下のとおりです。

  • 無印型 - nilを渡すことができない。nilになる瞬間が一瞬もない。これこそがnilを渡せない必須引数である。
  • ?型 - Optional型であり、nilを扱うことができる。これについては問題ない。
  • !型 - ImplicitlyUnwrappedOptionalという特殊なOptional型であり、Optional型なのでnilを扱うことができる。すなわち!が付いている引数については任意引数でありnilを渡すことができる。
    • !も?も演算子でも命令でもなく型であり、!や?をつけることで対象を型に包んでいる、と考えれば良いと思います。
    • !と?の違いはメンバにアクセスした際に暗黙的かつ強制的に元の型にアンラップされるか、そのままOptionalとして扱われるかの違いだけです。

ではなぜCocoaのAPIは引数で?ではなく!を使ってnilを受け取れるようにしているのでしょうか?これは引数を受け取った後に、その引数にアクセスした場合の挙動がおそらく影響しているのではないかと思われます。以下の例を見てください。

こちらのコードですが、safeSwim()メソッドが旧Objective-Cと完全に同じ挙動を示します。これは以下の様な理由によるものです。

  • self.name(!型)が?型によってラップされる。
  • self.name?の.descriptionにアクセスする。このとき?型によってラップされているので、self.nameがnilであれば何も起こらず、self.nameがnilでなければまず?型からアンラップされ、次に強制的に!型からアンラップされ、その結果通常通りdescriptionが呼び出される。
  • 通常だと?型の返り値は?型にラップされてしまうが、self.nameが!型なので強制的にアンラップされ?型ではなく素のString型を返すことができる
    • ここだけよく理解できてないです・・・

間違っていたらすみません(´・_・`)

とにかくこれこそが旧Objective-CのAPIについて!で型が表現されている理由ではないかと考えています。

!がついていないのにnilが渡せる引数の謎

こちらの謎はカラクリがわかってしまえば簡単です。前回の記事をご覧になった方はお気づきになったかもしれませんが、これらの型はAppleが__convertion()メソッドをNilTypeに対して追加しているため、nilを問題なく扱うことができます。

まとめ


  • !が付いている引数は?と同様にnilを渡しても良い引数である。nilを渡せない必須型は何もついていない引数のみである。
  • !はImplicitlyUnwrappedOptionalという特殊なOptional型であり、アクセス時にOptionalではなく強制的に元の型を返す点がOptionalと異なる、と考えれば理解しやすい。
  • 変数をすべて!で定義し、メンバアクセス時に常に?をつけるようにすると、どの変数にもnilを渡すことができ、実行時にnilがあればそこで実行がスキップされ、さらに返り値もOptionalではない通常の型にできるため、旧Objective-Cとほぼ同じ挙動になる。
    • ただしSwiftで新しく用意されているAPIを見る限りnilを扱う必要がある箇所については可能な限り!ではなく?を引数や返り値に使っているように見えるため、新しくSwiftで書く箇所については!を乱用するべきではないと思われる。
  • Cocoaの用意している型のうち、元々nilやNULLや0を渡すことができた型については、NilTypeに__conversion()メソッドが追加されているので、そのままnilを渡すことができる。

Swift で __conversion メソッドを使ってカスタムの型変換を定義する方法

2014/10/21追記:
Xcode 6.0 beta 6以降、__conversion()を使った暗黙的なas演算子を用いた型変換はサポートされていません。Xcode 6.1(Swift 1.1)現在、暗黙的な型変換を行う手段はないため、型変換を行いたい場合はイニシャライザを定義する方法を取るのが通例として良いと思います。

class 変換対象の型 {
  init(_ obj: 変換元の型:) -> 変換対象の型 {
    return 適当に変換対象の型を返す
  }
}



Swiftではas演算子を使ったり、型の定義されている変数・定数へ代入したり、メソッド呼び出しの引数にオブジェクトを渡す際に型変換が行われますが、デフォルトでは対応していない型変換があったりします。例えばStringはasを使ってもIntに変換することはできません。

また、SwiftではnilはNilTypeという型のシングルトンとして実装されており、nilを渡すとNilTypeから当該型への型変換が行われるようです。?(Optional)型や!(ImplicitlyUnwrappedOptional)型はNilTypeからの型変換に対応しているからnilが代入でき、それ以外の型は対応していないためコンパイルエラーになるというのがカラクリのようです。

この型変換ですが、__conversion()というメソッドを変換元の型に実装することで、任意の型変換を自分で実装する事ができます。以下のようになります。
class 変換元の型 {
  //@conversion属性は付けても付けなくても大丈夫みたいですが、一応つけます
  @conversion func __conversion() -> 変換対象の型 {
    return 適当に変換対象の型を返す
  }
}
以下にNilTypeを使ったnilからカスタムクラスへの型変換サンプルを示します。こうすればnilを引数にとってもエラーになりません。

ご覧のとおり、extensionとの組み合わせで既存のクラスも含むどのような型からどのような型への型変換にも対応可能になりますので色々はかどります。ぜひご活用ください。

2014年6月6日金曜日

Swift の enum型を for-in でイテレーションする方法

例えばJavaのEnum型などはそのまま以下のようにイテレーションすることが可能なのですが、

なぜかSwiftのenum型はそのままではイテレーションすることができません。対策としてGeneratorという仕組みが標準ライブラリに用意されてますので、それを使ってenumをイテレーションできるようにします。

具体的には、Generatorを継承したクラスを作成して next() -> Element? を実装してください。ElementはAnyObjectのtypealiasなので実際には好きな型を返していただければOKです。あとはSequenceOf<T>型でGeneratorをラップしてあげればOKです。next()メソッドがnilを返すまでSequenceOf<T>はイテレーションを続けてくれます。

以下にサンプルコードを示します。

Generator内部でyieldが使えれば便利なんですが、おそらくyield構文は無さそうです。Enum型を一覧したい場合以外にもGeneratorは便利に使えますのでぜひお試しください。

Swift を使ってみてがっかりした点まとめ



数日間iOS8/Xcode6/Swiftな環境で色々試してみて、Swiftを使っていて思ったよりがっかりした点が多かったのでちょっとまとめてみようと思います。

動的な処理がSwiftだけでは一切できない

[NSObject performSelector:]の類と、NSInvocationがSwiftからは一切呼び出せません。使おうとすると怒られます。objc/runtime.hは試していませんが、同様に直接Swift経由では呼び出せず間にObjective-Cをかます必要があるのではないかと思われます。

@optionalなprotocolが限定的にしか使用できない

具体的には@objc属性を付けないと使えません。しかしながらこのような後方互換性のためだけに存在する属性をいつまでもAppleがサポートするかは疑問が残るというのと、もう一つ以下の様な問題があります。

@objc属性のついたSwiftの型はただのObjective-Cクラスになる


こういう問題があるのであまり使いたいとは思えません。ちなみになのですがCocoaのクラスはほぼすべて@objc属性が付いているため、それを継承して使うことになるアプリでは事実上Swiftの本来の能力を出せないのではないかと思っていますが、実際のところはわかっていません・・・

メモリ管理が相変わらず必要

Swiftのメモリ管理はGCではなくARCでありただの参照カウント方式にすぎないため、Swiftでも循環参照が発生しないようにプログラマが明示的に参照の種類を指定しなければなりません。その上Objective-Cでも存在したstrong, weakに加えunownedという新しい種類のメモリ管理が追加されています。これはweakは参照が消滅するとnilにするという挙動であるためOptional型を使わなければならないのに対し、unownedは参照が消滅してもnilにならない代わりに通常の型がそのまま使えるというもののようです。

closureでselfをキャプチャするときの循環参照対策が相変わらず必要

いちばんがっかりしたのがこれです。Swiftはdelegateよりもclosureを使ったcallbackのほうが言語構造上向いているためclosureを大量に使うことになると思うのですが、このときselfがclosureを強参照し、closureがselfをキャプチャするようなコードを書いてしまうと、循環参照になるためメモリが開放されなくなるという問題がObjective-Cから引き続き発生します。対策としてclosure capture listと呼ばれる新たな構文が追加されています。closureの先頭、引数宣言の前に[unowned self]のような構文を追加することで、selfをunownedとしてキャプチャすることができます。

以下に使用前・使用後の例を示します。

Objective-CのweakSelfよりはマシに思えますが、とはいえこの辺りはコンパイラが自動的に対応してほしいところです(´・_・`)

2014年5月26日月曜日

Android で 画面の回転や状態の復元まで考えた Fragment の使い方のガイドライン(自分用メモ)

Fragment を使った画面を作る際に、どのように作ればうまい具合に画面の回転や状態の復元を扱えるかという自分用のメモです。

最初にまとめ

  • 基本方針として可能な限りすべての管理を当該ActivityのFragmentManagerに任せると楽
  • ActivityのフィールドとしてFragmentを保持するのはバッドノウハウな気がする
  • onCreateとonDestroyが呼び出されたからといってインスタンスが生成破棄されているとは限らない、これらはFragmentManagerのタイミング次第
最終的に実装したコードは以下のような感じになりました。

今回の発端

ActionBarのタブに2つのFragmentを格納し、片方はListView, もう片方はGoogle MapsのMapViewを突っ込むようなUIを作っていたのですが、Androidは素人なもので普通に作っていると画面回転とタブの切替時にうまいこと状態を復元するのがなかなか手こずってしまいました。というわけで良いプラクティスを考えてみることにしました。

Activity と Fragmentのライフサイクルを復習

Activityは画面回転時に一度破棄されてしまいます。このような場合はActivityのFragmentManagerが現在管理しているFragment(バックスタックに入っているものが含まれるかどうかは未検証)については、Activity破壊時にFragmentManager経由で自動的にonSaveInstanceが呼び出され、Activity復旧時に自動的にonCreateとonCreateView経由で復元が試みられます。

これとは別に、Activityは破棄されないがFragmentは破棄されるケース、例えばActionBarのタブを切り替えたりNavigationDrawerを選択するなどして同一のActivity上で別の画面Fragmentに遷移する場合もあります。

上記いずれの場合も、可能な限りテキストビューの入力内容やリストのスクロール位置、地図のカメラ位置などを保持することをユーザーから期待されるため、状態の復元が必要になります。画面を回したりタブを切り替えたらスクロール位置が先頭に戻ったらユーザーはイライラするでしょう。

状態の保存はBundleとonSaveInstanceStateを使い、復元はonCreateとonCreateViewを使うのが楽です。

解決策

画面回転などActivity自体の破棄と再生成が自動的に行われるケースであればFragmentManager管理下にあるFragmentについて自動的に再生成が試みられるため大して難しくはないと思います。タブを切り替えたりするケースについては、以下のいずれかが良さそうな気がしています。
  • 解決策1: すべてのFragmentをFragmentManagerにattachされた状態のままにし、タブが切り替えられたら見せないFragmentはhideする
  • 解決策2: 必要に応じてFragmentManagerにattach/detachを行い、そのかわり自分でBundleを作りonSaveInstanceStateを呼び出す
1のメリットはタブ切り替え時に復元がそもそも発生しないため管理が簡単です。確実に動作しますし、再生成も必要ないためパフォーマンスも良いです。デメリットはFragmentおよびFragmentが抱えるView構造をすべて保持し続けるためメモリを大量に消費します。

今回採用した解決策2のメリットはタブ切り替え時にFragmentのView構造をすべて捨てるためメモリが効率的です。MapViewはどうしてもメモリを大量に使うためいくらhide状態とはいえあまり他のタブの後ろにおいておきたくはなかったのでこうしました。デメリットはやはり複雑になります。今回はFragmentのインスタンスフィールドとして一時的にBundleを保持していますが、これは正直なぜFragmentがタブから外れてDetachされてDestroyされてるのにメモリ上に残ってるのかわかりづらい変な挙動になるので、Activity側かまたは何らかのマネージャクラスに任せてしまうべきではないかと思います。・・・ってそれがFragmentManagerなんですけど。もっとうまいやり方で出来そうな気がするんですが・・・


2014年4月30日水曜日

Android で Dagger DI を使いやすくするライブラリを書きました

Daggerというsquare社がオープンソースで提供しているAndroid向けDI (Dependency Injection)フレームワークがあります。


これを試しに自分のAndroidアプリで使ってみようと思い立ったのですが、幾つか問題が発生しました。

  • DI自体の概念が難しい
  • そもそもドキュメントを読んでもDaggerの使い方がよくわからない、公式のサンプルを真似してみても正直いまいちわからない
  • AndroidでDIを行うとなるとandroid.content.Contextの注入が必須になるのだが、Contextは動的なインスタンスであるためDIでの取り扱いが難しい

そこで四苦八苦しながら動くようになったものをライブラリとして公開し、少しでも簡単にDIのメリットだけを享受できればと思いまして DaggeredAndroid なるものを作ってみました。

使い方とかはREADMEを見てください。全部英語ですがすみません(´・_・`)

AndroidでDIを使う際のメリットは主に以下のとおりです。

  • オブジェクトをメンバに接続するだけのコードを無くせるので、コード量が減る。
  • シングルトンの取り扱いが楽になる。
  • Contextの取り扱いが楽になる。
  • Moduleを差し替えればインスタンスが安全に差し替わるので、テスト環境を作ったり、本番と開発環境を分離したりなど、環境の差し替えが楽になる。テスト時のみModuleを上書きすることもできる。

2014年4月20日日曜日

Android の TextView.setText() が遅い場合の原因と対処法

AndroidでTextViewを使っている時に、setText()に数百行単位のテキストを渡すとメインスレッドが1秒弱完全に固まってしまうという現象に見舞われてしまいました。昔の2.3端末ではともかく、手元の最新鋭機Nexus 5 (Android 4.4)でこんなに遅いのでは話になりません。しっかりと原因を調査し対処法を考えることにしました。

まずググってみると出るわ出るわ同じ問題。やはりみんな同じ場所で躓いているようです。
しかしながらいまいち具体的な原因がググっても見つかりません。そこでtraceviewを取ってみました。


すると原因が一発でわかりました。android.graphics.Paint.getTextRunAdvances()です。
Nexus 5では高速化のためJNI経由でネイティブ実装が呼び出されているようですが、それでもまだ間に合わないぐらい遅いようです。それもそのはず、このメソッドは与えられた文字の幅を計算するメソッドです。すなわち数百行のテキストのサイズを計算するため時間がかかっているようです。iOSで例えるならCore TextのCTGryphを計算するようなもの、UILabelのsizeThatFitsを呼び出すようなもので、非常に時間がかかってしまいます。

そこで対処法として、setText()でテキスト全体をセットし直すのではなく、TextViewが裏で保持しているテキストの一部だけを書き換えたり追記したりすることで一度に計算されるテキストのサイズの量を減らして高速化する事を考えました。iOSの場合はUITextViewにはsetText相当のプロパティしか用意されていないので、そのようなことをするのはdelegateを経由してみたりUIKeyInputプロトコルを自前で用意したりなどと困難がつきまとうのですが、Androidの場合は最初からTextViewの裏で保持しているテキストを自在に書きなおすための仕組みが用意されています。

そのためにはまずTextViewの裏で保持されているテキストを「編集モード」にしなければなりません。XMLでandroid:bufferTypeをeditableに指定するか、またはsetText()の第二引数にTextView.BufferType.EDITABLEを指定すると、テキストが編集モードで保持されるようになります。

そうするとgetEditableText()でTextViewが裏側で保持しているテキストが編集可能な状態で取得できます。あとはこのEditableオブジェクトに対して好きなように加工を行うだけです。単にテキストを追加するだけならTextView.append()を実行しても同じ結果が得られます。

こうすると数百行程度であればそれほど遅くなくテキストの追加ができるようになりました。しかしながら1000行を超えてくるとこれでも速度が足りなくなるので、自前でTextViewをサブクラス化して作っていくか、またはListViewにして一度に表示するテキスト量を減らすのが良いと思います。


2014年4月1日火曜日

Objective-🍣


全く新しい、真にユニバーサルな言語へ。


皆さんiOS開発の際にお世話になっているObjective-Cですが、一部の開発者の方々から以下の様な否定的な意見をいただくことがあります。

  • とっつきにくい
  • 文法がキモい
  • @や[]がキモい
  • シグネチャが無駄に長い
  • Apple製品でしか使えない

確かにObjective-Cは習熟すればこれらの欠点を補って余りある素晴らしい言語ですが、これからの更なるモバイルアプリの世界の拡充のためにはより一層多くの開発者に愛される言語になる必要があると私は考えました。

そこでこの度ご紹介するのがObjective-Cをさらに使いやすく、さらに親しまれるように、全く新しく一から作りなおした新言語

Objective-🍣

です!

Objective-🍣とは

以下の様な特徴を持つ言語です!

  • 驚異的に短く、真にユニバーサルな、洗練された文法を持ちます。これまでのプログラミング言語は基本的に英語による記述を強いるものでした。Objective-🍣は真に全人類にとってユニバーサルな絵文字をサポートすることで、この問題をすべて解決しました。Objective-🍣の親しみやすい文法にはすべてのプログラマがシンパシーを感じることができます。
  • Objective-Cと100%のランタイム互換性を持ちます。新しいiOSが登場してもその瞬間からあなたはObjective-🍣の圧倒的なパワーを手にすることができます。

さっそくObjective-🍣で書かれたサンプルコードを見てみましょう!拙作確率計算機のコードをObjective-🍣に書き換えてみました!

before

after

なんというこれまでにない全く新しいソースコード!あのキモかった@や[]、長ったらしいメソッド名がその姿を消しています!そのあまりの美しさには全プログラマが歓喜の涙を流すこと間違いありません!

Objective-🍣の導入方法

こちらのobjsushi.hをあなたのプロジェクト上でincludeするだけで使用できます!簡単ですね!


Objective-🍣を使ってみる

それでは早速ビルドしてみましょう!

あれ

えっちょ

\(^o^)/

ちなみに日本語セレクタ自体はXcode 5以降で普通に使えますよ。

お詫び

こちらの記事にはiOSならびに最新のMacでのみご覧になれる文字(具体的には🍣)を多数含んでおりますことをお詫び申し上げます(´・_・`)

2014年3月20日木曜日

iOSアプリ「確率計算機」をリリースしました


iOSアプリ「確率計算機 (YourLuck)」をリリースしました。
ガチャのドロップ率と試行回数からドロップ期待値を計算するアプリです。
無料です。
https://itunes.apple.com/jp/app/que-lu-ji-suan-ji-gachano/id838156105?mt=8 (JP store)
https://itunes.apple.com/app/id838156105?mt=8 (Universal)

機能

シンプルイズベストなので、以下の2つだけです。

  • 確率計算・・・何回ガチャを回したら、最低でも1個はドロップするか、確率を計算します。
  • 期待値計算(v1.1.0以降)・・・ガチャを指定された回数だけ回すと、一体何個ぐらいはドロップするのか、ドロップする数を計算します。


使い方

特別なことはなにもないのですが、以下のように操作します。



期待値計算モード(v1.1.0以降)

画面左下のこのアイコンを押すと、期待値計算モードに切り替わります。通常の確率計算モードでは「指定された試行回数分だけガチャを回したら最低でも1個はドロップする確率」を表示しますが、期待値計算モードでは「指定された試行回数分だけガチャを回したらだいたい何個ぐらいドロップするか」を瞬時に計算する事ができます。同じモンスターを複数体狙っている時などに便利です。


FAQ

Q: 用語が難しくてよくわからんのだが
A: めっちゃバッサリ言うと以下のとおりです。
  • ドロップ率・・・ガチャのドロップ率です。そのまんまですね。
  • 試行回数・・・何回ガチャを回すかです。
  • 中央にでっかく出る%・・・指定したドロップ率で、指定した回数だけガチャを回したら、100人いるうちここに出ている数字の人が最低1回は指定したドロップ率の何かが出る、という意味です。30%だったら100人のうち30人が最低1回出ます。運がいい人であれば2回以上出るかもしれません。

Q: 絶対確実って表示されてるのに表示されてる試行回数分だけ回しても出なかった!詐欺だ!!!
A: 申し訳ありません(´・_・`) これには幾つか理由があります。
  • 確率は確率なので、例えば98.7%で絶対確実と表示されても1.3%の人、1000人に13人の人は外れてしまいます。これは年末ジャンボ宝くじを1枚だけ買ったら5等3000円が当たった、ぐらいの確率です。すごいのかすごくないのかイマイチわからんですね。
  • 試行回数が多いと誤差が出る可能性があります。具体的には試行回数100回以上はより高速な計算を使っているのですが、そのため精度1~2%程度ですが落ちている可能性があります。

Q: 期待値モードで「最低でも」とか「ほとんどの人は」とか出るけどあれ具体的には何%なの?
A: 以下のとおりです。
  • 最低でも・・・100人中98人がこの数字以上の個数を獲得できます。
  • ほとんどの人は・・・100人中85人がこの数字以上の個数を獲得できます。
  • 半数の人は・・・100人中50人がこの数字以上の個数を獲得できます。

Q: ガチャの確率ってどこで調べればええの?
A: 以下の様な方法をオススメします。
  • ゲーム内に表示されている場合があります。最近のゲームでは特に確率を表示しているものが増えているので、ガチャの画面で注意して探してみてください。
  • グーグルとかヤフーで「ゲーム名 ガチャ 確率」と入力して検索する。
  • 知ってそうな人に聞く。ただしゲームを作ってる中の人とか関係者にTwitterで聞いても絶対に教えてくれないのでそういう人以外に聞いてください。

サポートとか

もし何かございましたら、以下の私の連絡先にまでご連絡ください。
mail: akisutesamaあっとまーくgmail.com
twitter: @akisutesama

2014年3月12日水曜日

iOS SDK 7.1 / Xcode 5.1にアップグレードした時に踏んだ地雷まとめ

本日iOS SDK 7.1 / Xcode 5.1にアップグレードを行った際にぶつかった所々の問題とTipsをまとめておきたいと思います。

arm64対応とCocoaPods

Xcode 5.1からデフォルトのビルド設定$(ARCHS_STANDARD_32_BIT)がarm64、要するに64bit対応を含むようになり、arm64 armv7 armv7sの3つのアーキテクチャに対してビルドを行うようになりました。ソースコードからビルドを行っている場合は大抵問題ないと思うのですが、以下の様なケースでarm64対応を切りたい場合があります。

  • プロジェクト内にarm64アーキテクチャに対応していないstaticライブラリが含まれている場合。
  • 64bitになると危険なバグが発生するおそれがあるコードが含まれている場合、例えばCGFloatやNSIntegerのサイズが変化したり、各種ポインタのサイズが4byteから8byteに増えていたりすると面倒な事が起きるような計算をしている場合。

このような場合、一番簡単な対応策はBuild SettingsのArchitectures(ARCH)をarmv7 armv7sに書き換えることなのですが、こうすると実はCocoaPodsを使用しているプロジェクトの場合リンカエラーが発生してビルドができなくなるという問題があります。
https://github.com/CocoaPods/CocoaPods/issues/1787

そこでBuild SettingsのArchitectures(ARCH)を$(ARCHS_STANDARD_32_BIT)という値に設定すると内部的にarmv7 armv7sとして扱ってくれ、かつCocoaPodsが問題を起こさないようです。この方法をオススメします。この件については以下のページが詳しかったです。
http://stackoverflow.com/questions/8323343/archs-standard-32-bit-vs-armv6-armv7-armv7s-vs-i386

JSONKit対応とCocoaPods

Xcode 5.1からisaポインタへの直接アクセスが完全に禁止になりました。これまでは警告を出さないようにする事が可能でしたが、Xcode 5.1からはそのオプションもなくなり問答無用でビルドエラーにされてしまいます。この問題が最も深刻に出るのはJSONKitライブラリです。
https://github.com/johnezang/JSONKit/issues/79#issuecomment-6322919

対策として一番簡単で確実な方法はJSONKitを使うのを今すぐやめてNSJSONSerializationに乗り換えることです。NSJSONSerializationはiOS 5から使用できるので、あなたが余程のパフォーマンス厨か、またはあなたのプロジェクトが数千万人以上のユーザを抱える巨大プロジェクトで、万一iOS 4系のサポートを止めると何故かあなたの会社ではなく携帯電話会社に「使えないんでなんとかしてください」と苦情が寄せられるような社会的インフラとなったアプリでもなければこの方法を取ることを強くオススメします。

それでも何らかの理由か大人の事情で万一JSONKitをサポートしなければならない場合は、もはやソースコードを書き換えるしか手がありません。幸いにしてXcodeがエラー箇所の修正提案を自動的に全て行ってくれるため、それに従ってコードをホイホイ書き換えれば簡単にビルドが通って使えるようになります。また、すでにそのようなパッチやプルリクエストが山ほどgithub上に用意されています。例えば以下の様なプロジェクトをオススメします。
https://github.com/ignazioc/JSONKit-NoWarning (JSONKit 1.5.2pre, 要するに最新版相当)
http://cocoapods.org/?q=jsonkit-nowarning (CocoaPodsでも使用可能です)

まとめ

CocoaPods爆発しろ