web-dev-qa-db-ja.com

drawRectを使用するかどうかを指定します(drawRect / Core Graphicsとサブビュー/イメージを使用するタイミングとその理由は?)

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

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

私の混乱の多くは、テーブルビューのスクロールパフォーマンスを本当にスムーズかつ高速にする方法を学ぶことから来ています。もちろん、このメソッドの元のソースは Twitterの背後にある著者 for iPhone(以前のtweetie)からのものです。 基本的に、テーブルスクロールバタリーをスムーズにするために、秘密はサブビューを使用せず、代わりに1つのカスタムUIビューですべての描画を行うことであると言います。本質的には、多くのサブビュー多くのオーバーヘッドがあるため、レンダリングが遅くなり、親ビュー上で常に再合成されます。

公平に言うと、これは3GSがまったく新しいスパンキンであったときに書かれたものであり、それ以来iDevicesはずっと速くなっています。それでも この方法定期的に推奨interwebs およびその他の高性能テーブル用です。実際、これは Appleのテーブルサンプルコード で推奨される方法であり、いくつかのWWDCビデオで提案されています( iOS開発者向けの実用的な描画 )、および多くのiOS プログラミングブック

グラフィックスを設計し、それらのCore Graphicsコードを生成するための 素晴らしい外観のツール もあります。

だから、最初は「Core Graphicsが存在する理由があるのだ。信じられないほど速い!」

しかし、「可能な場合は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を推奨しないのですか?シンプルな背景画像はどうですか(CGで作成する場合とサイズ変更可能なpng画像を使用する場合)

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

質問の更新

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

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

可能な限り、UIKitとサブビューに固執します。生産性を高め、すべてのOOメカニズムを維持しやすくする必要があります。UIKitから必要なパフォーマンスが得られない場合、または知っているコアグラフィックスを使用してください。 UIKitで描画効果を一緒にハックしようとすると、より複雑になります。

一般的なワークフローは、サブビューを使用してテーブルビューを構築することです。 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を使用してください。動作する最も単純な実装を行います。プロフィール。インセンティブがある場合は、最適化します。

71
Ben Sandofsky

違いは、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から画像がダウンロードされ、描画先の画像にコピーされます。完了したら、これをアップロードしますsecond copy画像のグラフィックカードに戻ります。そのため、デバイスで2倍のGPUメモリを使用しています。

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

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

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

51
uliwitness

私はここで他の人の答えから外挿しようとしているものの要約を試み、保持し、元の質問の更新で明確な質問をします。しかし、私は他の人に答えを寄せ続け、良い情報を提供してくれた人に投票することを勧めます。

一般的方法

Ben Sandofskyが his answer で述べたように、一般的なアプローチはであるべきであることは明らかです。可能な限りUIKitを使用してください。 。インセンティブがある場合、最適化します。」

なぜ

  1. IDeviceには2つの主なボトルネックがあります。CPUとGPU
  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はビューの最初のレンダリングに責任を負いませんが、代わりにレンダリングされた後、時にはいくつかのレイヤープロパティを持つテクスチャ、またはビットマップを渡されます。その後、ビットマップを合成し、それらのレイヤーの影響をすべてレンダリングし、アニメーションの大部分(コアアニメーション)をレンダリングします。

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

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

26
Bob Spryn

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

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

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

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

6
WolfLink