iOSプラットフォーム(最近iPhone OSと呼ばれたもの)でのプログラミングは、実り多い仕事からの喜びと潮に逆らって泳ぐことの苦痛の奇妙な組み合わせです。 各開発者は、これらのコンポーネントのどちらが優先されるかについて独自の意見を持っています。 個人的には、このレッスンが好きなので、次のプロジェクトに取り組む
プロセスの印象を共有するのが適切だと思われました。
3月下旬、私はiPhone用の
Bookmateのモバイル版を書くように頼まれました。 ほとんどのアプリケーションの設計は、厚いPSDの形ですでに準備が整っていて、サーバー側では作業が本格的でしたが、Objective-Cでクライアント部分を「ただ」書く必要がありました。
この記事では、私たちを攻撃したレーキを備えた最初のコンテナについて説明します。 Starcraftをプレイする場合、通常は信じられないほどの量のすべてのクラックから突然上昇したzergとの類推がより適切です。
建築
一言で言えば、Bookmateは、一連のHTMLファイルで構成される本を保存するサーバーです。 Bookmateクライアント(一般の人々、「リーダー」)の主なタスクは、このHTMLを表示して処理することです。 HTMLを選ぶ理由 たとえば、PDFとは異なり、フォントサイズを変更する際に再フォーマットするのは非常に簡単です。これは、小さな画面のモバイルアプリケーションでは絶対に必要です。 一方、HTMLには通常のページという概念がないため、厳密に言えば、従来のページごとの形式で本を表示するのにはあまり適していません。 画面に収まらない行を次のページに正しく転送するには、ページの垂直インデントを知る必要があります。 これを行うには、HTMLエンジンは最初にドキュメント全体を処理し、スタイルシートに従ってドキュメント内のすべてのブロックのサイズと座標を計算する必要があります。 その後、Adobe InDesignやQuarkXPressなどの組版プログラムでできることを実行できます。
当時のブックメイトの同僚は、これをすべて美しく迅速に行うJavaScriptのライブラリをすでに作成していました。
こんにちはiPhone
ベースのテストプラットフォームとして、iPhone 3Gを選択しました。 ロシアで非常に人気があり、私が保存した最古のiPhoneモデルです。 テストブックの1つは、サイズが890 KBの単一ファイルのJulio Cortazarによる出版物「Models for Assembly」でした。
最初のプロトタイプは、UIWebView、JavaScriptライブラリ、およびObjective-Cでのそれらの間の(かなり重要ではあるが)量の中間層コードでした。
iPhone 3Gは非常にスマートなスマートフォンです。
場所で 。 正直なところ、このような重いサイトをMobile Safariで開くことはほとんどありませんでしたが、それらが自然界にあるかどうかはわかりません。 貧しいiPhone! 「アセンブリのモデル」を開くのに40秒間かかった彼は、メモリ不足のために他のすべてのアプリケーションを閉じて、メモリが完全になくなると叫び、困難な運命についてうめきました。そして、baskは何も変更できません。
しかし、その瞬間には冗談を言う時間はありませんでした。 前衛からの最初のザーグは足を握り、地球は大群の下で震え、まだ見えないが、すでに恐ろしい。
本質的に最適化するものはありませんでした。 これらの40秒間のかなりの時間、WebKitは解析、DOMの構築、ジオメトリの再計算、および(他の用語がないために)メモリの貪食に従事しました。 後者は、仮想メモリにディスクを使用できないため、iOS自体の深刻な問題です。また、この場合、バックグラウンドプログラム(メールや電話など)のためにメモリを緊急に解放する必要も生じました。 DOM全体でのJavaScriptの反復処理がWebKitを終了しました。
このようなパフォーマンスは、明らかに、誰にも適していない。 iPhoneのJavaScriptが私たちの目的にとって十分に速くなく、20 MBのメモリを買う余裕がないことは明らかでした。
そして、それについてどうすればいいですか?
災害の規模をよりよく理解するには、少し中断して、発生する状況を説明する必要があります。 WebKitは
オープンソースライブラリです。 しかし、iOSでは、
プライベートAPIを指し
ますが 、その使用はAppleによって禁止されています。 iOS用のWebKitを自分でビルドし、静的ライブラリとしてプログラムに含めることができたとしても、UIKitはすでにWebKitにリンクしており、必然的にキャラクターの衝突につながります。 WebKit全体の名前を変更して衝突を回避した場合でも、UIWebViewはそれが構築されているWebKitコンポーネントへのアクセスを提供しません。上記のプライベートAPIの使用を参照してください。 JavaScriptがなければ、Objective-Cから直接DOMに到達することはできません。 そしてそれは、片目盲目カメが最近言ったように、「さて、誰もが航海した」ということです。
残っているのは、WebKitを忘れることです。libxmlを使用してHTMLを解析し、DOMを手動で描画します。 つまり、HTMLエンジンを作成する必要があります。 これがパフォーマンスの観点から理にかなっているかどうかを理解するために、私は実験のためだけにこれに同意することができました。 最終的に、WebKitのソースコードを見て、それをはるかに高速にしながら、それだけを書き直そうとすることの無益さを認識しています。 一方、WebKitの機能は10%も使用していません。 高度に特化した独自のエンジンを作成すると、最新のブラウザよりも10倍軽くなります。 ああ、私たちのものは消えませんでした!
libxml
最悪のAPIのプログラマーのための地獄での年次コンテストで、libxmlは再び大賞を受賞しました。 私は間違いを見つけるかもしれませんが、ソースコードは
ドキュメントよりもはるかに明確です。 典型的な例:
Function: htmlCtxtReset
void htmlCtxtReset(htmlParserCtxtPtr ctxt)
Reset a parser context
ctxt: an HTML parser context
真剣に、そしてそれだけですか?! ここに書かれていることはすべて、関数と引数の名前から明らかです。 この関数は正確に何をしますか? なぜ必要なのですか? いつそれを使用しますか? 申し訳ありませんが、
劣等生はこれを書きましたか? 座って、libxml、deuce。
公平に言えば、ライブラリ自体はほとんどすべての人が使用できるほど十分に機能していると言う価値があります。 そして、生活の中でそれははるかに簡単です。 そして、すべてのiPhoneにあります。
最初の結果
行き止まりの開発でできるだけ時間を無駄にしないために、最も遅いセクションから始めました。 これは、テキストのブロックのサイズの計算であり、
グリフのサイズを合計することになり
ます 。 パフォーマンス測定の結果には、驚きがありました。
- UIWebView + JavaScript-38秒、20 MBのメモリ。 これは、すべての最適化の後の最初のオプションです。
- NSString、-sizeWithFont:-14秒、8 MBのメモリ。 同じフォントで同じグリフのサイズを繰り返し計算することは意味がないため、結果はキャッシュされます。 時間の35%は、メソッド呼び出し-[NSString sizeWithFont:]に費やされます。
- iPhone OS 3.1.3のコアテキスト-70秒以上。 Core Textは、バージョン3.2より前のiPhone OSのプライベートAPIです。その速度を比較したかったのは、 それは驚くほど使いやすく、ポピーツールで非常に高速です。
数字で武装して、私はそれらに手紙を書きました。 開発者向けのサポート(いわゆる
DTS 、一般に有料サービスである開発者テクニカルサポート)。 同時に、基になる関数CGFontGetGlyphsForUnichars()-[NSString sizeWithFont:]がプライベートAPIを参照する理由を尋ねました。
だからこそ私はAppleを尊敬しているのです。開発者がインターネット全体で泣き言を言うのではなく、「確立された形式に従って」質問するときの開発者に対する態度のためです。 2日後、質問に対する詳細な回答を受け取りました。
- WebKitへのアクセスは、パーティーポリシーによって提供されていません。
- バージョン3.2より前のiPhone OSのコアテキストは、遅すぎるためプライベートAPIです。 バージョン3.2以降、UIWebView + JavaScriptよりも大幅に高速になるはずです。
- CGFontGetGlyphsForUnichars()は、設計が不適切なためプライベートAPIです。 インターネット上で類似物を見つけて、これが正しいことを確認できます。
- 全体として、私は正しい方向に進んでいます。彼ら自身はもう一方を知らないからです。
- [NSString sizeWithFont:]とCore Graphicsの速度のいずれかを選択する必要があります。
原因に!
テキストをレイアウトするためのアルゴリズムは、詳細に入らなければ、非常に簡単に聞こえます。 最初に、テキストを同じスタイルのフラグメントに分割する必要があります。たとえば、テキストのブロックの途中に斜体のフレーズがある場合、そのようなブロックは3つのスタイルフラグメントに分割されます。 次に、テキスト
を新しい行に転送
できる場所を理解する必要があります。これらは、原則として、単語間の句読点または単語内のハイフネーションです。 私たちは彼らを移籍候補者と呼んでいます。 次に、ハイフネーション候補の間のテキストフラグメントを1つずつ取り出し、長さを考慮して、それらの合計長が行の長さを超えるまでそれらを行に配置します。 同様にページについても、次の行がページの高さを超えてクロールするまで行を追加します。
印象的なオブジェクトのセットが取得されます。テキストの各ブロック(たとえば、段落)は、いわゆるを含む行の配列で構成されます。
グリフの実行 -同じスタイルのテキストの断片。 これらの同じグリフランをそのまま全体として描画します。
この時点で、最適化の興味深い機会が現れ、コードはすぐにスパゲッティに変わります。 最下位レベルのAPIに進むと、言語教育が十分でないことが明らかになります。 新しいコード体系は、このコードが機能しない地平線上に出現しています。 たとえば、アラビア語やヘブライ語のように文字の方向のため、タイ語のように単語間にスペースがないためです。
その結果、最初の試みで広大さを把握することは不可能であると認識し、右から左へ書く方向の言語とタイ語のようなあまり知られていない言語のサポートを延期しなければなりませんでした。 同じ理由で、両側のテキストの位置合わせの実装は延期されています-効果的なハイフネーションシステムが必要です。そのためには、各単語の言語を知る必要があります。
ただし、パフォーマンスの点では、開始時と比較して大幅な進歩がありました。 これで、Cortazarの傑作が6秒(!)秒以内に画面に表示されます。 大変な月でした。 私は一時的に脳をリフラッシュしなければなりませんでした。 この間ずっと、妻は私の隅からうなり声と母親だけを聞きました。 クライアントにとっても甘くはありませんでした。第一に、誰も余分な月を頼りにしていませんでした。第二に、プロジェクトの作業の開始が落胆しました(「良いスタート、次に何が起こるでしょうか?」)、第三に、保証がありませんでした、この合板の束は離陸することさえできます。 しかし、危険を冒さない彼は徒歩で歩きます。 そして、私たちは空にとても欲しかった!
Bookmateアプリケーションはすでに
App Storeで利用可能です(リンクはiTunesで開きます。何らかの理由で動作しない場合は、ブラウザで開く
代替の方法があります)。
エピローグ
スティーブジョブズについてそのような
話をします。 1983年、彼はエンジニアに次の言葉でMacの起動時間を最適化するよう説得しました。 百万? もうありません。 数年のうちに、500万人が少なくとも1日に1回はMacの電源を入れると思います。 起動時間を10秒短縮できるとしましょう。 これに500万人のユーザーを掛けると、毎日5,000万秒が得られます。 1年間、これはおそらく数十人の命です。 ダウンロードを10秒速くすると、数十人の命が救われました。 さて、それだけの価値はありますか?」 そう思う。