143

この質問の目的を明確にするために、サブビューと drawRect の両方を使用して複雑なビューを作成する方法を知っています。いつ、なぜどちらを使用するのかを完全に理解しようとしています。

また、プロファイリングを行う前に、それほど前もって最適化し、より難しい方法で何かを行うことは意味がないことも理解しています。私は両方の方法に満足しており、今はもっと深く理解したいと思っています。

私の混乱の多くは、テーブル ビューのスクロール パフォーマンスを非常にスムーズかつ高速にする方法を学んだことから生じています。もちろん、このメソッドの元のソースは、iPhone 用の twitter (以前のトゥイーティー) の背後にある作者からのものです。基本的に、テーブルのスクロールをバターのようにスムーズにする秘訣は、サブビューを使用せず、すべての描画を 1 つのカスタム uiview で行うことです。基本的に、多くのサブビューを使用するとレンダリングが遅くなるようです。これは、多くのオーバーヘッドがあり、親ビューで常に再合成されるためです。

公平を期すために、これは 3GS がかなり新しい時代に書かれたものであり、iDevices はそれ以来ずっと高速になっています。それでも、この方法は、インターウェブや他の場所で高性能テーブルのために定期的に 提案されています. 実際、これはApple の Table Sample Codeで提案されている方法であり、いくつかの WWDC ビデオ ( Practical Drawing for iOS Developers ) や多くの iOSプログラミング ブックで提案されています。

グラフィックスを設計し、それらの Core Graphics コードを生成するための見栄えの良いツールさえあります。

そのため、最初は「Core Graphics には理由があります。それは速いのです!」と信じるようになりました。

しかし、「可能な場合は Core Graphics を優先する」という考えを思いつくとすぐに、drawRect がアプリの応答性の低下の原因であることが多く、メモリに関して非常に高価であり、実際に CPU に負担をかけることがわかります。基本的に、「drawRect のオーバーライドを避ける」必要があります(WWDC 2012 iOS App Performance: Graphics and Animations )

だから私は、すべてのように、それは複雑だと思います. drawRect を使用するタイミングと理由を、自分自身や他の人が理解できるように手助けできるかもしれません。

Core Graphics を使用する明白な状況がいくつかあります。

  1. 動的データがある (Apple の株価チャートの例)
  2. 単純なサイズ変更可能な画像では実行できない柔軟な UI 要素がある
  3. 一度レンダリングされると複数の場所で使用される動的グラフィックを作成しています

Core Graphics を避けるべき状況が見られます。

  1. ビューのプロパティを個別にアニメーション化する必要があります
  2. ビューの階層が比較的小さいため、CG を使用して追加の作業を行っても、得られる価値はありません。
  3. 全体を再描画せずにビューの一部を更新したい
  4. 親ビューのサイズが変更されたときに、サブビューのレイアウトを更新する必要があります

だからあなたの知識を授けてください。どのような状況で drawRect/Core Graphics に到達しますか (サブビューでも実現できます)? その決断に至った要因は何ですか?バターのように滑らかなテーブル セルのスクロールのために 1 つのカスタム ビューでの描画が推奨されるのはなぜですか? 単純な背景画像についてはどうですか (CG で作成するのではなく、サイズ変更可能な png 画像を使用するのはいつですか)?

価値のあるアプリを作成するために、このテーマを深く理解する必要はないかもしれませんが、理由を説明できずにテクニックを選択するのは好きではありません。私の脳は私に怒っています。

質問の更新

みんな情報ありがとう。ここにいくつかの明確な質問があります:

  1. コア グラフィックスを使用して何かを描画しているが、UIImageViews と事前にレンダリングされた png を使用して同じことを達成できる場合、常にその方法を使用する必要がありますか?
  2. 同様の質問: 特にこのような悪いツールでは、いつコア グラフィックスにインターフェイス要素を描画することを検討する必要がありますか? (おそらく、要素の表示が可変の場合。たとえば、20 種類のカラー バリエーションを持つボタン。その他の場合は?)
  3. 以下の私の回答での私の理解を考えると、複雑な UIView レンダリング自体の後にセルのスナップショット ビットマップを効果的にキャプチャし、複雑なビューをスクロールして非表示にしながらそれを表示することで、テーブル セルの同じパフォーマンスの向上が得られる可能性がありますか? 明らかに、いくつかの部分を解決する必要があります。私が持っていたただの興味深い考え。
4

4 に答える 4

75

できる限りUIKitとサブビューに固執してください。生産性が向上し、管理が容易になるすべての OO メカニズムを活用できます。UIKit から必要なパフォーマンスを得られない場合、または UIKit で描画効果を一緒にハックしようとすると、より複雑になることがわかっている場合は、Core Graphics を使用してください。

一般的なワークフローは、サブビューを含むテーブルビューを構築することです。Instruments を使用して、アプリがサポートする最も古いハードウェアのフレーム レートを測定します。60fps が得られない場合は、CoreGraphics にドロップダウンします。これをしばらくやってみると、UIKit がおそらく時間の無駄であるという感覚が得られます。

では、なぜ Core Graphics は速いのでしょうか?

CoreGraphics はそれほど高速ではありません。常に使用している場合は、速度が遅くなる可能性があります。GPU にオフロードされる多くの UIKit 作業とは対照的に、CPU で作業を行う必要がある豊富な描画 API です。画面上を移動するボールをアニメーション化する必要がある場合、ビューで setNeedsDisplay を 1 秒あたり 60 回呼び出すのはひどい考えです。そのため、個別にアニメーション化する必要があるビューのサブコンポーネントがある場合、各コンポーネントは個別のレイヤーにする必要があります。

もう 1 つの問題は、drawRect を使用してカスタム描画を行わない場合、UIKit がストック ビューを最適化できるため、drawRect は何もしないか、合成でショートカットを使用できることです。drawRect をオーバーライドすると、UIKit はユーザーが何をしているのかわからないため、低速なパスを使用する必要があります。

これら 2 つの問題は、テーブル ビュー セルの場合の利点によって補われます。ビューが最初に画面に表示されたときに drawRect が呼び出された後、コンテンツはキャッシュされ、スクロールは GPU によって実行される単純な変換です。複雑な階層ではなく単一のビューを扱っているため、UIKit の drawRect の最適化はそれほど重要ではなくなります。つまり、Core Graphics の描画をどれだけ最適化できるかがボトルネックになります。

できる限り UIKit を使用してください。機能する最も単純な実装を行います。プロフィール。インセンティブがある場合は、最適化します。

于 2013-02-02T18:32:34.717 に答える
57

違いは、UIView と CALayer は基本的に固定画像を扱うことです。これらの画像はグラフィックス カードにアップロードされます (OpenGL を知っている場合は、画像をテクスチャと考え、UIView/CALayer をそのようなテクスチャを示すポリゴンと考えてください)。画像が GPU に読み込まれると、非常に迅速に描画できます。数回描画することもできます。(わずかなパフォーマンスの低下はありますが) 他の画像の上にさまざまなレベルのアルファ透明度を適用しても描画できます。

CoreGraphics/Quartz は、画像を生成するための API です。ピクセル バッファ (OpenGL テクスチャを考えてください) を取り、その中の個々のピクセルを変更します。これはすべて RAM と CPU で発生し、Quartz が完了すると、イメージは GPU に「フラッシュ」されます。GPU から画像を取得し、変更してから、画像全体 (または少なくとも画像の比較的大きなチャンク) を GPU にアップロードするこのラウンドトリップは、かなり遅くなります。また、Quartz が行う実際の描画は、あなたがしていることに対しては非常に高速ですが、GPU が行うものよりもずっと遅いです。

GPU がほとんど変更されていないピクセルを大きなチャンクで移動していることを考えると、これは明らかです。Quartz はピクセルのランダム アクセスを行い、CPU をネットワーク、オーディオなどと共有します。また、Quartz を使用して同時に描画する複数の要素がある場合、1 つが変更されたときにそれらすべてを再描画してからアップロードする必要があります。一方、1 つの画像を変更してから、UIViews または CALayers で他の画像に貼り付けると、はるかに少量のデータを GPU にアップロードするだけで済みます。

-drawRect: を実装しない場合は、ほとんどのビューを最適化して取り除くことができます。それらにはピクセルが含まれていないため、何も描画できません。UIImageView などの他のビューは、UIImage のみを描画します (これも本質的にはテクスチャへの参照であり、GPU に既に読み込まれている可能性があります)。したがって、UIImageView を使用して同じ UIImage を 5 回描画すると、GPU に 1 回だけアップロードされ、5 つの異なる場所でディスプレイに描画されるため、時間と CPU が節約されます。

-drawRect: を実装すると、新しいイメージが作成されます。その後、Quartz を使用して CPU で描画します。drawRect で UIImage を描画すると、GPU から画像がダウンロードされ、描画先の画像にコピーされ、完了したら、画像の2 番目のコピーがグラフィックス カードにアップロードされます。したがって、デバイスの GPU メモリを 2 倍使用しています。

したがって、描画する最も速い方法は通常、静的コンテンツを変化するコンテンツから分離しておくことです (個別の UIViews/UIView サブクラス/CALayer で)。静的コンテンツを UIImage として読み込み、UIImageView を使用して描画し、実行時に動的に生成されたコンテンツを drawRect に配置します。繰り返し描画されるが、それ自体は変化しないコンテンツがある場合 (つまり、同じスロットに表示されてステータスを示す 3 つのアイコン)、UIImageView も使用します。

1 つの注意点: UIView が多すぎる場合があります。特に透明な領域は、表示時にその背後にある他のピクセルと混合する必要があるため、GPU の描画に大きな負荷がかかります。これが、UIView を「不透明」としてマークして、その画像の背後にあるすべてのものを消去できることを GPU に示すことができる理由です。

実行時に動的に生成されるが、アプリケーションの存続期間中は同じままであるコンテンツ (たとえば、ユーザー名を含むラベル) がある場合、Quartz を使用して全体を 1 回描画するだけで、テキスト、背景の一部として、ボタンの境界線など。しかし、Instruments アプリが別の指示をしない限り、通常は必要のない最適化です。

于 2014-04-10T10:23:09.323 に答える
26

ここで他の人の回答から推定していることの要約を維持し、元の質問の更新で明確な質問をします。しかし、私は他の人が回答を続けて、良い情報を提供してくれた人に投票することをお勧めします.

一般的方法

Ben Sandofsky が彼の回答で述べたように、一般的なアプローチは、 「できるときはいつでも UIKit を使用してください。機能する最も単純な実装を行います。プロファイルを作成します。インセンティブがある場合は、最適化してください」であることは明らかです。

なぜ

  1. iDevice には、CPU と GPUという 2 つの主なボトルネックが考えられます。
  2. CPU は、ビューの最初の描画/レンダリングを担当します
  3. GPU は、アニメーション (コア アニメーション)、レイヤー効果、合成などの大部分を担当します。
  4. UIView には、複雑なビュー階層を処理するために組み込まれた多くの最適化、キャッシュなどが含まれています。
  5. drawRect をオーバーライドすると、UIView が提供する多くの利点が失われ、UIView にレンダリングを処理させるよりも一般的に遅くなります。

1 つのフラットな UIView でセルの内容を描画すると、スクロール テーブルでの FPS を大幅に改善できます。

上で述べたように、CPU と GPU が 2 つのボトルネックになる可能性があります。これらは一般的に異なる処理を行うため、どのボトルネックに直面しているかに注意を払う必要があります。テーブルをスクロールする場合、Core Graphics の描画が高速であるというわけではなく、それが FPS を大幅に改善できる理由です。

実際、Core Graphics は、最初のレンダリングでネストされた UIView 階層よりも非常に遅くなる可能性があります。ただし、途切れ途切れのスクロールの一般的な理由は、GPU のボトルネックであるように思われるため、それに対処する必要があります。

drawRect のオーバーライド (コア グラフィックスを使用) がテーブルのスクロールに役立つ理由:

私が理解していることから、GPU はビューの最初のレンダリングを担当しませんが、代わりにレンダリングされた後、いくつかのレイヤー プロパティを含むテクスチャまたはビットマップを渡します。次に、ビットマップの合成、これらすべてのレイヤー効果のレンダリング、およびアニメーションの大部分 (コア アニメーション) を担当します。

テーブル ビュー セルの場合、GPU は複雑なビュー階層でボトルネックになる可能性があります。これは、1 つのビットマップをアニメーション化する代わりに、親ビューをアニメーション化し、サブビュー レイアウト計算を実行し、レイヤー効果をレンダリングし、すべてのサブビューを合成するためです。そのため、1 つのビットマップをアニメーション化する代わりに、同じピクセル領域に対する一連のビットマップの関係と、それらがどのように相互作用するかを担当します。

要約すると、コア グラフィックスを使用して 1 つのビューでセルを描画すると、テーブルのスクロールが高速化される理由は、描画が高速化されるからではなく、特定のシナリオで問題を引き起こすボトルネックである GPU の負荷が軽減されるためです。 .

于 2013-02-03T00:43:44.937 に答える
6

私はゲーム開発者です。友人から、UIImageViewベースのビュー階層によってゲームの速度が低下し、ひどいものになると言われたときも、同じ質問をしていました。次に、UIViews、CoreGraphics、OpenGL、またはCocos2Dなどのサードパーティを使用するかどうかについて見つけたすべての調査に進みました。WWDCの友人、教師、Appleエンジニアから得た一貫した答えは、あるレベルではすべて同じことをしているので、最終的には大きな違いはないというものでした。UIViewsのような高レベルのオプションは、CoreGraphicsやOpenGLのような低レベルのオプションに依存していますが、使いやすくするためにコードでラップされているだけです。

UIViewを書き直すだけの場合は、CoreGraphicsを使用しないでください。ただし、すべての描画を1つのビューで行う限り、CoreGraphicsを使用することである程度の速度を得ることができますが、それは本当に価値がありますか?私が見つけた答えは通常ノーです。私が最初にゲームを始めたとき、私はiPhone3Gで作業していました。ゲームが複雑になるにつれて、多少の遅れが見られ始めましたが、新しいデバイスではまったく目立たなくなりました。今、私はたくさんのアクションを実行していますが、iPhone 4で最も複雑なレベルでプレイする場合、唯一の遅れは1〜3fpsの低下のようです。

それでも、Instrumentsを使用して、最も時間がかかっている関数を見つけることにしました。問題はUIViewの使用に関連していないことがわかりました。代わりに、特定の衝突検知計算のためにCGRectMakeを繰り返し呼び出し、同じ画像を使用する特定のクラスに対して、1つの中央ストレージクラスから描画させるのではなく、画像と音声ファイルを別々にロードしていました。

したがって、最終的には、CoreGraphicsを使用することでわずかな利益を得ることができるかもしれませんが、通常はそれだけの価値がないか、まったく効果がない可能性があります。CoreGraphicsを使用するのは、テキストや画像ではなく、幾何学的形状を描画するときだけです。

于 2013-02-02T07:52:06.260 に答える