drawRectを描画するかどうか(drawRect / Core Graphics vs subviews / imagesを使用する必要があるのはなぜですか?)


142

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

また、事前に最適化することはあまり意味がなく、プロファイリングを実行する前に、より困難な方法を実行することも理解しています。私は両方の方法に慣れていると考えて、今は本当にもっと深く理解したいと思っています。

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

公平を期すために、これは3GSがかなり新しいブランドスパンキンであったときに書かれ、それ以来iDevicesはより速くなりました。それでも、この方法インターウェブや他の場所で定期的 に高パフォーマンスのテーブルとして推奨されています。実際、これはAppleのTable Sample Codeで推奨される方法であり、いくつかのWWDCビデオ(Practical Drawing for iOS Developers)、および多くのiOS プログラミングブックで推奨されています。

グラフィックをデザインし、それらのコアグラフィックスコードを生成するための素晴らしいツールもあります。

だから、最初は「Core Graphicsが存在する理由はある。それが速い!」と信じるようになった。

しかし、「可能な限りお気に入りのコアグラフィックス」というアイデアが浮かんだと思うとすぐに、drawRectがアプリの応答性の低下の原因であることが多く、メモリの消費が非常に高く、CPUに大きな負担がかかることがわかります。基本的に、「drawRectのオーバーライドを回避する」必要があります(WWDC 2012 iOSアプリのパフォーマンス:グラフィックとアニメーション)

だから、すべてのように、それは複雑だと思います。多分あなたは私と他の人がdrawRectを使用するときと理由を理解するのを助けることができますか?

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

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

Core Graphicsを回避する状況が見られます。

  1. ビューのプロパティは個別にアニメーション化する必要があります
  2. ビューの階層が比較的小さいので、CGを使用して知覚される余分な労力は利益に値しません
  3. すべてを再描画せずにビューの一部を更新したい
  4. サブビューのレイアウトは、親ビューのサイズが変更されたときに更新する必要があります

だからあなたの知識を授けなさい。drawRect / Core Graphicsにはどのような状況で到達しますか(これはサブビューでも実行できます)?その決定にあなたを導く要因は何ですか?どのように/なぜバターのようなスムーズなテーブルセルスクロールに推奨される1つのカスタムビューで描画するのに、Appleは一般にパフォーマンス上の理由からdrawRectを推奨していませんか?シンプルな背景画像はどうですか(サイズ変更可能なpng画像を使用する場合と、CGを使用して作成する場合)。

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

質問の更新

情報ありがとうございます。ここにいくつかの明確化する質問:

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

回答:


72

できる限りUIKitとサブビューを使用してください。あなたはより生産的になり、物事をより簡単に維持できるはずのすべてのオブジェクト指向メカニズムを利用することができます。UIKitから必要なパフォーマンスが得られない場合、またはUIKitで描画効果をハックしようとすると複雑になる場合は、Core Graphicsを使用してください。

一般的なワークフローは、サブビューを持つテーブルビューを構築することです。Instrumentsを使用して、アプリがサポートする最も古いハードウェアのフレームレートを測定します。60fpsを取得できない場合は、CoreGraphicsにドロップダウンしてください。しばらくこれを行ったとき、UIKitがおそらく時間の浪費であるときの感覚がわかります。

では、なぜCore Graphicsは高速なのでしょうか。

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

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

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

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


53

違いは、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番目のコピーをグラフィックカードにアップロードします。つまり、デバイスで2倍のGPUメモリを使用しています。

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

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

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


詳細な回答ありがとうございます。うまくいけば、より多くの人々が下にスクロールしてこれにも投票します。
Bob Spryn 2014

これを2回賛成できるといいのですが。素晴らしい記事をありがとう!
チベットの海岸

わずかな修正は:ほとんどの場合、何もありませんダウン GPUからの負荷が、それはVRAMが速くに遅く、システムRAMから大きなピクセルバッファを転送しなければならないので、アップロードは、基本的にはまだあなたが行うにはGPUを頼むことができる最も遅い操作です。OTOH画像が表示されたら、それを移動するには、新しい座標セット(数バイト)をGPUに送信するだけで、どこに描画するかがわかります。
公証人2016

@uliwitness私はあなたの答えを調べていましたが、オブジェクトを「その画像の背後にあるすべてのものを不透明にする」とマーク付けしたとあなたは述べました。その部分に光を当てていただけませんか?
リク

@Rikhレイヤーを不透明としてマークするということは、a)ほとんどの場合、GPUはそのレイヤーの下にある他のレイヤーを描画する手間をかけないことを意味します。少なくとも、不透明レイヤーの下にあるレイヤーの部分は描画されません。描画されていても、実際にはアルファブレンディングを実行せず、画面上の最上位レイヤーのコンテンツをコピーするだけです(通常、不透明レイヤーの完全に透明なピクセルは、最終的に黒になるか、少なくともその色の不透明バージョンになります)。
公証人2018

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の負荷が軽減されるためです。これは、特定のシナリオで問題を引き起こすボトルネックです。 。


1
注意してください。GPUは、フレームごとにレイヤーツリー全体を効果的に再合成します。したがって、レイヤーの移動は実質的にコストがかかりません。1つの大きなレイヤーを作成する場合、その1つのレイヤーを移動するだけの方が、複数のレイヤーを移動するよりも高速です。このレイヤー内で何か移動すると、突然CPUが必要になり、コストがかかります。セルの内容は通常はあまり変化しないため(比較的まれなユーザーアクションに応答する場合のみ)、ビューの数を減らすと速度が上がります。しかし、すべてのセルで1つの大きなビューを作成するのは、Bad Idea™です。
公証人2014年

6

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

UIViewを書き換えるだけの場合は、CoreGraphicsを使用しないでください。ただし、すべての描画を1つのビューで行う限り、CoreGraphicsを使用することである程度の速度を上げることができますが、それだけの価値があるのでしょうか。私が見つけた答えは通常ノーです。私が初めてゲームを始めたとき、私はiPhone 3Gで作業していました。ゲームが複雑になるにつれて、少し遅れが出始めましたが、新しいデバイスではまったく気付かれませんでした。今私はたくさんの行動を起こしており、唯一の遅れはiPhone 4で最も複雑なレベルでプレイしたときの1〜3 fpsの低下であるようです。

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

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


8
コアアニメーションとコアグラフィックスを混同していると思います。Core Graphicsは非常に低速なソフトウェアベースの描画であり、drawRectメソッドをオーバーライドしない限り、通常のUIViewの描画には使用されません。Core Animationはハードウェアで加速され、高速であり、画面に表示されるもののほとんどに責任があります。
Nick Lockwood 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.