2014年12月4日木曜日

オフスクリーン描画をライブラリ化(前書き編)

当ブログでは、よく使う機能を独自のライブラリ化している話が何度も出てきます。独自なのは本当ですが、たいしたことはやっていません。小型プリンターRoltoの場合は深く考えて設計したので、特別に取り上げました。でも、他の機能に関しては、ほとんど普通の作り方だと思います。

ただし、少しずつですが改良はしています。最初のうちは、iOSのAPIに詳しくないので、自分にとっての使いやすさを前面に出して設計していませんでした。でも、APIの特徴を理解し始めるとともに、ライブラリにも改良を加えたくなります。

最近、オフスクリーン描画のライブラリを改良しました。自分なりに考えて設計したので、もしかしたら誰かの参考になるかもしれません。また、iOSの描画機能を理解する手助けにもなると思います。これから数回に分けて、要点を紹介します。

 

中身に入る前に、2次元描画APIに関する私の理解レベルについて触れます。ハッキリ言ってしまうと、iOSの2次元描画機能は分かりづらいです。まず最初に、Quartz 2DとかCore Graphicsとか複数の名前があって、その範囲を正確には理解できていません。もしかしたら、今使っているAPIよりも良いAPIがあり、本来はそちらで作るべきかもしれないとの不安も少し持っています。もし間違って理解している箇所を見付けたら、正しい理解や使い方を、ぜひ教えてください。

一応、いろいろと調べてみた結果として、次のような内容が得られました。名前がUIGraphicsで始まるAPIを使って描画の開始や終了、出力先などを管理します。具体的な描画は、名前がCGContextで始まるAPIを使い、細かな設定が可能であることも知りました。これら2つの組み合わせが、オフスクリーン描画機能を作るのに必要なAPIの全体像となります。

 

もう1つ、大事な点があります。描画内容を作っている処理中は、まったく描画していないことです。単に描画する内容を構築しているだけで、それが終わったときに得られるのが、描画内容を記したコマンド集のようなものです。最終的にUIImageの形で作ったとしても、やはり描画コマンド集です。もしビットマップ画像を貼り付けた場合は、その画像へのリンクが含まれた描画コマンド集となります。

この描画コマンド集は、解像度に依存しません。ですから、すべての座標に浮動小数点のCGFloatを用いています。実際に描画されるのは、画面に表示されたり、プリンタに印刷されるときです。画面に表示する際には、ハードウェアの解像度に応じて、描画するビットマップ画像を生成するのでしょう。ディスプレイがレチナの場合は、縦横とも2倍の画素数(全体では4倍の画素数)を持ったビットマップ画像を生成するはずです。

このような仕組みなので、オフスクリーンで描画していると思っている処理は、まったく描画していなくて、描画するコマンド集を作っているだけです。ですから非常に高速で処理され、そこで長い時間が取られることはありません。

実際に処理時間がかかるのは、画面に描画するときです。当然GPUを使うので、描画内容が単純であれば一瞬で終わります。描画内容が複雑になったとき(つまり、描画コマンドの数が非常に多いとき)、遅いと感じるほどの時間を使うのでしょう。それを少しでも速くするには、重なる部分で透明度を使わないとか、描画計算時間が短くなるように工夫する必要があります。その工夫を、描画内容を作る際に行なうことは、言うまでもありません。

 

実際に作ってみて、分かってきたこともあります。先に結論を行ってしまうと、制限が意外に多いということです。何でも自由に制御できる形にはなっていません。制限を理解して、上手に使う必要があります。

たとえば、CGContextです。線の色とか太さとか、描画内容を指定するときにCGContextを使います。このCGContextは、自由に保存して、自在に切り替えるようにはなっていません。保存して切り替えようと思いましたが、保存するための機能がなく、深いコピーもできませんでした。swiftでの深いコピーにはcopyメソッドを使いますが、そんなメドッソはないとコンパイラーに怒られました。

swift上ではCGContextという型の名前ですが、Objective-C上ではCGContextRefという違う名前で、参照を意味するRefが付いています。通常のオブジェクトもインスタンスへの参照ですが、わざわざRefとは付けません。おそらく、何か特殊な参照なのでしょう。

この辺の制限の話は、ライブラリを使う段階で詳しく取り上げたいと思います。もちろん、私が知っている範囲でですが。

 

制限が多い理由も、作っているうちに理解してきました。もっとも大事なのが、UIGraphicsやCGContextで作る描画コマンド集は、実際に描画するための部品を作るのが役割だという点です。最終的な表示内容ではなく、あくまで描画に使う「部品」です。その部品を組み合わせて、最終的な表示内容を画面上に構築します。部品ごとに配置を変え、実際に表示する際に描画するわけです。

アニメーションで使う場合が、一番理解しやすいでしょう。動かす最小単位を部品として作り、部品ごとに異なる動きを与えます。同じ動きをするものは1つの部品内に集めて、一緒に作ります。作った中身は、前述のように描画コマンド集です。

このような役割なので、並行して複数を作る必要はありません。あくまで描画する前の準備段階であり、描画で使う部品を作るわけですから、1つ1つの部品を順番に作っていくわけです。そのため、常に1つの部品だけを描画していて、「どの部品に対して描画するのか指定する機能」は持っていません。必要な描画が終わったら部品を出力し、別な部品の描画を開始します。あくまで「1つ1つの部品を順番に作る」なのです。

 

以上の内容ですが、最初はまったく理解できていませんでした。最初に作りたいと思った形(CGContextを自在に切り替えて描画する形)ではライブラリが実現できず、少しずつ別な方法へと変更していった中で、だんだんと分かってきたことです。

作成したライブラリを使い、複数のインスタンスを生成して動かした結果を見て、制限があることと、その制限の理由が理解できました。この辺の詳しい話は、ライブラリを使って動かした結果を知ることが、もっとも理解しやすい方法だと思います。そのため、一番最後に取り上げるつもりです。

ここまでの話から分かるように、オフスクリーン描画機能を作り進む中で、大事な点に気付きました。描画しておらず、描画で使う部品を作っていることです。これって「オフスクリーン描画機能」と呼んで構わないの?という素朴な疑問が生じました。当然ですね。でも、機能としては「オフスクリーン描画機能」が一番しっくり来るというか、中身の機能をイメージしやすいので、その名前で作り進めることにしました。将来は変更するかもしれませんが。

話が長くなりましたので、一旦ここで切ります。ライブラリを作る話は、次回からですね。まずは、全体設計の話になると思います。実際のソースコードが登場するのは、その後でしょう。

2014年12月3日水曜日

Xcodeバージョンの切り替え時期で悩む

Xcode 6.1.1が正式に、App Storeに登録されましたね。少し前から、Appleの開発者向けサイトには何日も前からGMが登場していたので、正式版が出るのは時間の問題でした。

新しいバージョンの開発ツールが登場すると、開発しているマシンに入れる時期を毎回のように悩みます。皆さんは、どのように考えて判断しているのでしょうか。ちょっと知りたい気持ちです。私の場合の悩める思いを書いてみます。

 

Xcodeの現状は、開発環境として特別な状況です。swiftが登場したばかりですから、swift自体の安定性が良くなっているのか気になります。また、コンパイラーが使うswift用のAPI定義も、一部に間違っているものが見付かって、少しずつ修正されているでしょう。できるだけ新しいものを使いたい、との思いが強まります。

でも、新しいものだけに、大きく切り替わった時期というのは、大きなバグが出やすい時期でもあります。バグに遭遇して苦労したくない、つまり余計なことで開発を邪魔されたくない気持ちも大きいです。新しいものを使いたいけど、新しいものは危ないという、実に困った状況なのです。

 

私が現在使っているのは、Xcode 6.0.1です。まあまあ安定していて、大きなトラブルにも遭遇せず、開発を進められいています。開発が最近完了し、本番用としてビルドしたのも、現状のXcode 6.0.1でした。実機テストでも特に問題が発生しなかったので、安心してビルドすることができました。

並行して、Xcode 6.1も試しましたね。開発マシンとは別の実験用マシンにインストールして、開発マシンと同じプロジェクトをビルドしてみました。中身がかなり変わっているのでしょう。いろいろと問題が発生しました。

まず、ソースコードを編集中に、文法チェックしている処理が、何度も何度も異常終了しました。その度にメッセージが出て、文法チェックが止まります。こんな状態では、使っていても気分が悪いです。こんなに悪い状態なのに、よく公開したなと思いました。

さらに、6.0.1でコンパイルエラーが出なかったソースコードが、コンパイルエラーになります。Optionalの扱いが変わったというか、おそらくswift向けAPI定義が少し修正されたのでしょう。詳しく覚えていませんが、「!」を削除しなさいとか、「!」を追加しなさいとか、エラーと修正指示が何個も出ました。それは指示どおりに修正したら、問題なく動きました。

一番困ったのは、実機での原因不明な異常終了です。シミュレーター上では正常に動いているのですが、ビルド後に実機へ転送して動かすと、途中で異常終了します。その箇所のソースコードを調べても、問題がありそうには思えません。そもそも、Xcode 6.0.1では正常に動いているソースコードです。

動かない箇所をコメント化して、再び実機で動かすと、また別な箇所で異常終了します。3つめの異常終了が出た時点で、使うのをあきらめました。App Storeのコメントでも、さんざんの言われようでした。私も同感です。

 

私の場合、新しい開発ツールには真っ先に触りたいと思わないので、最新ツールには興味がありません。ただし、前述のようにXcodeの現状が特別な状態です。swift向けAPI定義の修正がされているバージョンだけに、使いたい気持ちは強くあります。だからこそ、悩ましいわけです。

通常の開発ツール選びでは、最新版を使いません。バージョンが大きく切り替わったとき、1つ前のバージョンの安定版を意識的に選びます。もしXcodeが相当前からswiftを採用していたら、おそらく5.xの最終版を使っていたと思います。でも、iOSの新バージョンに含まれる、ぜひとも使いたい機能が登場していた場合は、話が別です。仕方なく、最新版を使うでしょう。ただし、その場合でも、新しい機能を使うアプリ開発でだけ最新のXcodeを使い、他のアプリ開発では1つ前のバージョンのXcodeを使うでしょう。

 

で、Xcode 6.1.1です。通常のバージョンアップであれば、かなり安定しているのだろうと予測します。けれども、前の6.1が相当にひどかっただけに、試しに使うのも躊躇します。実機でだけトラブるのは、もう勘弁してほしいです。

とりあえずの判断としては、世間の評判を見定めて、評判が良いなら実験マシンにインストールしようと思います。既存の完成アプリをビルドしてみて、実機での動きが問題ないようなら、開発マシンにインストールする予定です。幸い、現在開発中のアプリはなく、切り替え時期としては最適ですから。

2014年12月1日月曜日

Titanium mobileからXcode+swiftへと移行

今ではXcode+swiftで普通に開発していますが、その前はTitanium mobileで開発していました。移行しようと判断した理由などを、簡単に書きます。

 

iPad用のアプリは、数年前に開発を始めました。まず最初に、開発ツール選びで迷いました。細かい部分まで作れて、そこそこ作りやすいツールを探したところ、当時はTitanium mobileがベストだと感じたからです。確か、Titanium Studioが出始めた頃だったと思います。Titanium mobileのバージョンが1.6とか1.7ぐらいでしたかね。

プログラミング言語は、お馴染みのJavaScriptです。クラスは使えないものの、オブジェクト指向っぽい書き方ができます。データ型の扱いが緩い感じで、個人的にはあまり好きになれない言語でした。ただ、関数中心という点だけは好きでしたね。

ツールの使い方や、Titanium mobileのAPIは、日本語の情報が出始めていたので、大まかな理解は素早くできました。あとは、TitaniumのウェブサイトにAPIの解説が載ってますから、それを参照しつつ、開発を進めました。

ただし、Titaniumのウェブサイトは最小限の情報しか載っていません。また、その場で見れるサンプルも非常に少ないです(この点は、Appleだとさらに悪いです)。細かな機能を使おうとすると、ウェブ上の資料だけでは目的の機能かどうか判断できませんでした。結局、自分でコーディングして動かしながら、期待したとおりの動作かどうか調べ、目的の機能を探していった感じです。余計な手間を多くかけたなと、今でも思い出します。

 

Titanium mobileを使った開発で、一番困った点はバグです。バージョン1.8ぐらいまでの時期は、描画関係に大きなバグがあり、それを回避しようと小技を多用しました。普通に描画すると大きく乱れて描かれるのが、透明度を設定すると乱れないことを発見して、それを使って回避するという技も見付けました。

画像を描画するアプリの開発では、描画関係のバグは致命的です。画面上での様々な演出を考えたのですが、バグを回避できなければ採用できません。回避方法を試行錯誤したおかげで、7割程度の描画演出が、何とか作れました。本当に苦労したのを覚えています。

Titanium mobileのバグで苦労している間、Xcode+Objective-Cでの開発も並行して勉強し始めました。Objective-Cでの書き方が、どうしても好きになれず、暇なときに勉強という感じでした。そのうちに、Titanium mobileを使ったアプリが完成し、Objective-Cの勉強も中断しました。

その後、iPadのアプリ開発はありませんでした。過去に作ったアプリの小さな改良はありましたが、Titanium mobileをそのまま使って、機能を少し追加しただけです。

私は今まで、色々な言語を経験してきました。もっともマシンに近い言語だと、大型汎用機やパソコンのマクロアセンブラです。他にも、フォートランやCなど古い言語から、Javaなどのオブジェクト指向言語も使いました。そうそう、若い頃はLISPも使ったことがあります。どれでも作ろうと思えば作れますが、今はモダンな言語を選んで使う時代でしょう。Objective-Cは、もう形式が古い言語だと思いますね。

 

こんな感じで時間が流れている間に、Appleからswiftが発表されました。いちおう言語仕様を見てみたら、かなり気になる存在に思えます。これは使ってみるしかないと判断しました。

本格的にマスターするには、ある程度の大きさのアプリを作るのが一番です。過去にTitanium mobileで作った中から業務アプリを選び、すべてswiftで書き直してみました。いろいろな要素が含まれるアプリだったので、これを作り終わったら、かなりのノウハウが蓄積できると考えたからです。

新言語といっても特殊な点は少ないので、マスターするのに苦労しません。もっとも大変なのは、数多くあるAPIの理解です。同じ機能を実現するにしても、Titanium mobileで使っていたAPIと、iOSのAPIでは名前すら違うため、一から勉強し直しとなります。どんなAPIがあるのか調べながら、1つ1つ機能を作っていきました。

ネットで検索すると、Objective-Cで作った例がいくつも見付かります。後で知ったのですが、Objective-Cとswiftでは、APIの定義値の名前などが微妙に違うのです。この微妙に違うという点が曲者で、swiftだと短めに区切ってある名前がいくつもありました。Objective-Cで使う名前が長いと批判されたのを、それを受けての変更だと思います。結果として、コンパイルエラーを消すためにAPI資料やサンプルを探しまくりました。

探しまくったのには、理由があります。Swift専用の短い名前は、Appleのサイト内のAPI資料にも載ってないことも結構あり(これは本当にひどすぎます)、誰かが使っているサンプルを頑張って見付けたりして、本当に余計な手間がかかりました。使っている人は、どこから探し出したのでしょうか。Appleのサンプルにでも載っているのでしょうか。

実際、今でもswift側だけ載っていない定義値の名前があります。もしかして、古いバージョンを見ているのでしょうか。クラス名やメソッド名をGoogleで検索して、トップ近くに出てくるページなので、古いバージョンとは考えられないのですが。

まあ、私の場合は、ソースコードを再利用しやすくライブラリ化するので、一度知ってしまえばOKです。作成した共通ライブラリを呼び出すだけなので、細かいコードを暗記してなくても大丈夫なのです。こうして共通ライブラリを1つずつ作りながら、業務アプリを完成させました。結果として、業務アプリが出来上がっただけではなく、いくつかの共通ライブラリも同時に得られました。

作成した共通ライブラリは、すべての機能を最初から作るのではなく、必要な機能だけを作ります。最初のうちは機能が少ないのですが、利用するアプリの数が増えることで、少しずつ機能が加わります。これからも、どんどんと成長していくでしょう。

 

Titanium mobileは、iOS用だけでなく、Android用のアプリを開発できる点も大きな特徴です。でも、私の周囲には昔からのMacユーザーがばかりで、Androidを持ってすらいない人ばかり。Android用のアプリを作ってくれと言われたことが、一度もありません。そのため、Titanium mobileを使わなくなっても、ほとんど困らないのです。

といった私なりの特殊状況のため、安心してXcode+swiftへと移行できたのでした。使ってみると、やはり純正ツールは一番良いなと感じました。アニメーションなどの機能が簡単に使えるし、珍しい機能を使っても、余計なバグに遭遇する可能性は相当減りました。当たり前ですが、開発ツールは純正が一番ですね。最新バージョンへのタイムラグもないですし。

試しに移植した業務アプリも無事に完成し、たまたま機能追加の依頼があったので、swift版に切り替えることにしました。本番での切り替えも順調に進み、今後はswift版だけをメンテします。

 

新言語のswiftは、使い始めから気に入りました。データの型を厳密にチェックするので、自分の間違いをコンパイル時に見付けやすくなります。厳しい型チェックを嫌う人もいますが、私は大好きです。

この型チェックを積極的に使うように、コーディングしています。具体的には、varやletで変数を宣言するとき、型やクラス名をあえて付けて、自分の理解が正しいかどうか、コンパイラーにチェックしてもらってます。値を入れられる側の変数で型宣言すると、宣言した型に必ずなりますから、以降のソースコードでエラーが出たときも、原因を追及しやすくなります。ソースコードの文字数は増えますが、今後も型宣言をどんどん入れて書こうと思います。

swiftの総評ですが、きっちりと書きたい派の私にとって、非常に良いプログラミング言語だと感じました。もちろん、小さな欠点はありますが、許容範囲に入ってます。いろいろ言われている細かな欠点は、さらに使いやすくバージョンアップしてほしいですね。全体としては、非常に魅力的なプログラミング言語だと思います。この言語が広く普及し、Apple以外の環境でも使えるようになってもらいたいです。