iOS画像とパフォーマンス:画像表示、イベント配信、パフォーマンス最適化、オフスクリーンレンダリング

1.概要

この記事は、画像表示の原理から始まり、パフォーマンス最適化の一般的な問題にまで及び、いくつかの一般的な最適化方法を提供します。面接でよく尋ねられる関連スキルや詳細もあります。

 

2.画像​​表示、イベント配信、パフォーマンス最適化、オフスクリーンレンダリング

1. UIViewとCALayer

  • 要約:

(1)UIViewにはCALayerタイプの属性レイヤーがあります。

(2)UIViewはCALayerのデリゲートです。

(3)UIViewはUIResponderを継承し、イベントに応答できます。

(4)CALayerはNSObjectを継承し、表示されるコンテンツと、背景色、フレーム、境界などのコントロールのサイズとスタイルを含みます。

(5)CALayerのコンテンツはバッキングストアであり、実際にはビットマップタイプのビットマップです。

(6)どちらも階層構造になっています。

(7)CALayer的AnchorPoint、Position

    • CALayerのAnchorPoint(アンカーポイント):独自のレイヤーに対して、iOS座標系の(0,0)、(1,1)はそれぞれ左上隅と右下隅を表します。AnchorPointは支点に相当します。支点は、回転の変更、変換、およびスケーリングに使用できます。
    • CALayerの位置は、superLayerのAnchorPointの位置座標です。位置ポイントは比較的スーパーレイヤーです。
    • PositionおよびAnchorPointアイコンについて(anchorPointが変更された場合、レイヤーのフレームは変更され、位置は変更されません。positionおよびanchorPointの属性を変更しても、他の属性には影響しません) 

    

 

  • 最大の違い:

  <1> UIViewは、CALayer表示の基礎を提供します。UIViewは、タッチおよびその他のイベントの処理と、イベント応答チェーンへの参加を担当します。

  <2> CALayerは、コンテンツのレンダリングと表示のみを担当します。

   (8)補足:境界のxとyに関する質問 

<1>サンプルコード1:親ビューの境界のxとyのみを変更する

  

<2>効果1:サブビューによって参照される座標系の原点が、左および上に20だけシフトされます。

  

<3>サンプルコード2:ビュー境界のwおよびhをフレームのwおよびhよりも大きくなるように変更します。

  

  <4>効果2:ビューフレームのx、y、w、hが50に変更された(拡大)

  

  境界のwとhは、フレームのwとhに影響するだけでなく(2つの幅と高さは同じまま)、フレームのxとyにも影響します。この効果は、境界wおよびhが増加または減少するときに、周囲の領域を増加または減少させることです。

  <5>まとめ
    • 独自の座標系の原点位置を変更できます。これは、「サブビュー」の表示位置に影響します(サブビューの参照原点を変更します)。
    • 幅と高さを変更することにより、境界はそれ自体のフレームを変更できます。これは、親ビューでの表示位置とサイズに影響します。
 
 
 

 

2.イベントの配信と応答チェーンの表示:

(1)レスポンダーチェーン:再帰によって形成されたUIResponderオブジェクトのグループのチェーンシーケンスです。

(2)イベントの生成、送信、処理:

<1>生成:指が画面上の特定のビューに触れると、タッチイベントが発生し、システムはそのイベントをUIApplicationが管理するイベントキューに追加します。

<2>配信:UIApplicationはイベントキューから最初のイベントをフェッチし、イベントを配布します。

  • 最初に、アプリケーションのメインウィンドウ(keyWindow)にイベントを送信します。
  • メインウィンドウは、タッチイベント(ヒットテストループトラバーサル)を処理するために、ビュー階層で最も適切なビューを見つけます。
  • 最適なサブコントロールが見つからない場合は、最適です

<3>処理:レスポンダーチェーンに従って、見つかった最も適切なサブコントロールからイベントの処理を開始します。

  • ビューのtouchesメソッドを呼び出してイベントを処理します
  • UIViewが処理されない場合は、次のレスポンダーに渡します(現在のビューがコントローラーのビューの場合、コントローラーは最後のレスポンダーです。現在のビューがコントローラーのビューでない場合、親コントロールが最後のレスポンダーです)。
  • コントローラが処理しない場合は、引き続きUIWindowに渡してください。
  • UIWindowが処理しない場合は、引き続きUIApplicationに渡します。
  • UIApplicationが処理されない場合は、破棄します

<4>全体プロセス例

  

 

 

(3)主要なキーポイントであるhit-test:およびpointInside:メソッドシミュレーションを与える

  

 

 

 

3.画像表示の原理

(1)全体原理

    

 

<1> CPU:出力ビットマップ

<2> GPU:レイヤーレンダリング、テクスチャ合成

<3>結果をフレームバッファー(フレームバッファー)に入れます。これらのバッファーは、ハードウェア(ビデオメモリ)に応じて、メモリ領域にあるか、個別に分離されている場合があります。

<4>次に、vsync信号(垂直同期信号)に従って、ビデオコントローラーが指定時間前のフレームバッファーの画面表示内容を抽出します。

<5>可能なデジタル/アナログ変換をモニターに渡し、画面に表示します

 

(2)CPUとGPUの分割

    

 

 

  • CPU作業:UIレイアウト、テキスト計算、描画、画像デコード、ビットマップ送信
  • GPUレンダリングパイプライン(OpenGL):頂点シェーディング、プリミティブアセンブリ、ラスター化、フラグメントシェーディング、フラグメント処理

(3)UI描画原理

  • まとめ

UIViewのsetNeedsDisplayメソッドが呼び出されると、同じ名前のCALayerのsetNeedsDisplayメソッドが呼び出されます。このとき、描画はすぐには行われませんが、現在のレイヤーをマークすることと同じであり、Runloopが終了しそうになると[CALayer表示]が呼び出されます。このメソッドの内部は、displayLayerメソッドが実装されているかどうかを決定します。実装されていない場合は、システムコールが実行されます。実装されている場合は、非同期描画のエントリポイントを提供します。

    

 

  • システムレンダリング

    

 

 

<1> CALayerはバッキングストア(CGContextRefコンテキスト)を作成します

<2>レイヤーにデリゲートがあるかどうかを確認します。

(1)デリゲートがある場合は、[layer.delegate drawLayer:inContext](システムで実行)を実行し、このメソッドでビューのdrawRect:メソッドを呼び出します(ビューのdrawRect:メソッドを書き換えて、 )。

(2)デリゲートがない場合は、レイヤーのdrawInContextメソッドが呼び出されますが、レイヤーのこのメソッドをオーバーライドすると、この時点で呼び出されます。

<3>最後に、描画されたバッキングストア(ビットマップ)をGPUに送信します。

 

  • 非同期描画:

 

  

 

 

<1>レイヤーのプロキシメソッドdisplayLayerを実装する

<2>エージェントは対応するビットマップを生成する責任があります

<3>ビットマップをlayer.contentsプロパティの値として設定します

(CPU最適化に関しては、非同期描画が再度含まれます)

 

(4)4つのメソッド:layoutSubViews、setNeedsLayout、layoutIfNeeded、setNeedsDisplay

  • layoutSubViews:

1.子要素の位置とサイズを再定義する

2.以下の場合に呼び出されます

(1)初期化の初期化では、layoutSubviewsはトリガーされませんが、initWithFrameを初期化に使用すると、rectの値がCGRectZeroでない場合にトリガーされます。

(2)addSubviewは、layoutSubviewsをトリガーします

(3)フレームの値が設定の前後で変化した

(4)UIScrollViewをスクロールすると、layoutSubviewsがトリガーされます

(5)画面を回転すると、親UIViewでlayoutSubviewsイベントがトリガーされます

(6)サブビューを削除、追加、または変更すると、親ビューは

(7)setLayoutSubviewsを呼び出し、LayoutIfNeedsを呼び出します

  • setNeedsLayout

1.将来の特定の時間にレイアウトを更新します。すぐには更新されません。layoutIfNeededを呼び出す必要があります

2. layoutSubViewsを呼び出します

 

  • setNeedsDisplay

1.再描画します(現在の実行ループが終了する直前に描画を開始します)。

2. drawRectメソッドが呼び出されます。

 

  • layoutIfNeeded

1.レイアウトをすぐに更新し、setNeedsLayoutで使用します。

 

 

 

 

 

4. UIスタックフレームドロップの理由

 (1)理由

  

 

<1> iOSデバイスのハードウェアクロックはVsync(垂直同期信号)を発信します。

<2> VSync信号が到着すると、システムグラフィックサービスはCADisplayLinkおよびその他のメカニズムを通じてアプリに通知します。アプリのメインスレッドは、ビューの作成、レイアウトの計算、画像のデコード、テキストの描画など、CPUでの表示コンテンツの計算を開始します。

<3> CPUは計算されたコンテンツをGPUに送信し、GPUは変換、合成、およびレンダリングを実行します。

<4>次に、GPUはレンダリング結果をフレームバッファーに送信し、次のVSync信号が画面に表示されるのを待ちます。垂直同期メカニズムにより、CPUまたはGPUがVSync時間内にコンテンツ送信を完了しない場合、そのフレームは破棄され、次の表示機会を待って、ディスプレイは前のコンテンツを変更せずに保持します。これが、インターフェースが動かなくなっている理由です。

<5>一般的に、スムーズなページスライドは60 fpsです。つまり、1秒で60フレームが更新されます。つまり、フレームは16.7ミリ秒ごとに生成されます。CPUとGPUの処理時間が16.7ミリ秒を超えると、フレームが落ちたり、フリーズしたりします。

6 CPUとGPUのどちらが表示処理を妨げても、フレーム落ち現象が発生します。したがって、開発時には、CPUとGPUの圧力を個別に評価および最適化する必要があります

 

 

 

5. CPUリソース消費ソリューション

  • 要約:

1.軽量オブジェクトを使用する

2.オブジェクトの属性調整を減らす

3.事前構成を行う:レイアウト計算

4.コントロールの削減:テキストコントロールの描画、drawInRectメソッド

5.サブスレッドを開く:オブジェクトの作成など、時間のかかるタスクを実行します

6.非同期画像のデコード:バックグラウンドスレッドは最初に画像をCGBitmapContextに描画し、次にビットマップから直接画像を作成します。

7.非同期描画(上記で説明):ドライバースレッド、CoreGraphicを呼び出します

 

  • 詳細:

1.オブジェクトの作成:

(1)イベント処理が利用できない場合など、軽量オブジェクトを使用してみてください。UIViewの代わりにCAlayerを使用することを検討できます。

(2)基本的なデータ型を使用できる場合は、NSNumber型を使用しないでください。

(3)UIを使用せずに、バックグラウンドスレッドに配置してみてください。

(4)ビューオブジェクトを作成するストーリーボードは、はるかに多くのリソースを消費します。

(5)オブジェクトの作成時間を可能な限り延期します。

(6)再利用してください。

 

2.オブジェクトの調整と破壊:

(1)不要な属性調整を減らす:たとえば、UIViewの表示に関連する属性(フレーム、境界、変換など)は実際にはCALayer属性によってマッピングされるため、UIViewのこれらの属性を調整すると、消費されるリソースがはるかに多くなります。一般的なプロパティ用。

(2)ビューレベルの変更を少なくする:レベルの調整、追加、削除など。

(3)オブジェクトを背景に投げて解放する

 

3.事前構成、レイアウト計算:

(1)データ処理:JSONがモデルに変換されるときに、レイアウト関連のデータが事前に計算され、オブジェクトがカプセル化されて、計算されたすべてのレイアウトデータ(リッチテキスト情報を含む)が格納されます。(子スレッドで計算できます)

(2)Autolayout lessを使用します。ビューの数が増えると、Autolayoutによって引き起こされるCPU消費量は指数関数的に増加します。

(3)テキストコントロールを減らす:UILabelなどの「ペイント」してバックグラウンドスレッドに配置できるテキストコントロールを減らします。([str drawInRect:withFont:]と同様)

(4)ライン高キャッシュ:たとえば、FDTemplateLayoutCell、アイデアはデータ処理と同じです。

(5)テキストレンダリング:カスタムコントロール、CoreTextによる非同期テキスト描画。(テキストコントロールはタイプセットされ、下部のCoreTextを介してメインスレッドにビットマップ表示として描画されます)

 

4.画像のデコード:

(1)デコードの概念:JPEGとPNGはどちらも圧縮されたビットマップグラフィックスの「データ」です(PNGはロスレス圧縮であり、アルファチャネルをサポートします。JPEGはロッシー圧縮です。0〜100%の圧縮率を指定できます)。ディスクから画面に画像をレンダリングする前に、後続の描画操作を実行する前に、まず画像の元のピクセルデータ(ビットマップデータにデコード)を取得する必要があります。

(2)UIImageまたはCGImageSourceのメソッドで画像を作成する場合、画像データはすぐにはデコードされません。画像はUIImageViewまたはCALayer.contentsに設定され、CGImageのデータはCALayerがGPUに送信される前にデコードされます。この手順はメインスレッドで行われ、避けられません。このメカニズムをバイパスする場合、一般的な方法は、バックグラウンドスレッドのCGBitmapContextに画像を描画し、ビットマップから直接画像を作成することです。現在、一般的なネットワークイメージライブラリにはこの関数が付属しています(AFN、SDWebImage)。

    

 

 

5.絵を描く

(1)スレッドセーフ:CoreGraphicメソッドは通常スレッドセーフです

(2)非同期描画(前述):CGの最初のメソッドは、イメージをキャンバスに描画し、キャンバスから画像を作成して表示します。この最も一般的な場所は、drawRect内です。上記で紹介して、レイヤープロキシのdisplayLayerメソッドを実装することもできます。

    

 

 

6.その他:

(1)時間のかかる操作を子スレッドに入れます。

(2)同時スレッドの最大数を制御し、スレッドの開発操作もリソースを消費します。

 

 

6. GPUリソ​​ース消費の原因と解決策

  • 要約:

1.ビューレイヤーコントロール:ビューの数とレベルを最小化

2.画像​​サイズを制御します。GPUが処理できる最大テクスチャサイズは4096x4096です。このサイズを超えると、CPUリソースが処理に使用されます。

3.画像の数を減らす:一定期間内に大量の画像が表示されないようにし、できるだけ多くの画像を組み合わせて画像を表示します

4.透明なビューを減らす:透明度の低いイメージ(アルファ<1)を使用し、不透明なイメージの場合は不透明をyesに設定します

5.オフスクリーンレンダリングを避けてください:ラスタライズ、コーナーカット、シャドウ、マスク

6.サードパーティの非同期フレームワークAsyncDisplayKit(Texture)を使用します。テキストとレイアウトの計算、レンダリング、デコード、描画、その他のタスクを非同期で処理します(同時にCPUとGPUリソ​​ースの破棄を最適化します)。

 

  • 詳細:

  GPUの機能(OpenGLテクスチャレンダリング):

  1.送信されたテクスチャ(テクスチャ)と頂点の説明(三角形)を受け取り、変換を適用し、ブレンドしてレンダリングし、画面に出力します。

  2.通常表示されるコンテンツは、主に2種類のテクスチャ(画像)形状(三角形のアナログベクトルグラフィックス)です。

  • 最適化されたポイント:テクスチャレンダリング(画像の数とサイズの削減)、ビューブレンディング(レイヤーの削減、透明度の制御)、グラフィックス生成(オフスクリーンレンダリングの回避)

1.テクスチャのレンダリング

    • 短時間で大量の画像の表示を減らします。すべてのビットマップ(画像、テキスト、ラスタライズ)をメモリからビデオメモリに送信し、GPUテクスチャにバインドする必要があります。ビデオメモリへの送信プロセスであっても、GPU調整およびテクスチャのレンダリングプロセスであっても、多くのGPUリソ​​ースが消費されます。多数の画像を短時間で表示する場合、CPU使用率は非常に低く、GPU使用率は非常に高く、インターフェイスはフレームをドロップします。これを回避する唯一の方法は、短期間に大量の画像の表示を最小限に抑え、できるだけ多くの画像を組み合わせて表示することです。
    • 画像(テクスチャ)のサイズを縮小します。画像が大きすぎてGPUの最大テクスチャサイズを超える場合、最初にCPUで画像を前処理する必要があります。これにより、CPUとGPUの両方で追加のリソース消費が発生します。現在、iPhone 4S以降の場合、最大テクスチャサイズは4096x4096です。したがって、画像とビューのサイズがこの値を超えないようにしてください。

 

2.ビューの混合

    • ビューの数とレイヤーの数を減らす:複数のビュー(またはCALayer)が一緒に表示される場合、GPUは最初にそれらを混合します。ビューの構造が複雑すぎる場合、ミキシングプロセスも多くのGPUリソ​​ースを消費します。この状況でのGPU消費を削減するために、アプリケーションはビューの数とレベルを最小限に抑える必要があります。
    • 最適化された構成の透過性:無駄なアルファチャネル合成を回避するために、不透明なビューで不透明属性をマークします。透明なビューを縮小し(alpha <1)、不透明なビューの場合はopaqueをyesに設定します。
    • 画像を組み合わせる:事前に複数のビューを1つの画像にレンダリングして表示します。

3.グラフィックの生成。

    • OpenGLでは、GPUには2つのレンダリングメソッドがあります。

(1)オンスクリーンレンダリング:現在の画面レンダリング。レンダリング操作は、現在表示に使用されている画面バッファで実行されます。

(2)オフスクリーンレンダリングオフスクリーンレンダリング、レンダリング操作のために現在のスクリーンバッファーの外側に新しいバッファーが開かれます。

 

    • オフスクリーンレンダリングのパフォーマンス消費の理由:

(1)新しいバッファーを作成する必要がある。

(2)コンテキストを複数回切り替える必要がある。まず、現在の画面からオフスクリーンに切り替えます。オフスクリーンレンダリングが終了すると、オフスクリーンバッファーのレンダリング結果が画面に表示され、コンテキストをオフスクリーンから現在の画面に切り替える必要があります。

 

    • オフスクリーンレンダリングをトリガーする操作に注意してください。

(1)ラスタライズ、layer.shouldRasterize = YES(レイヤーをビットマップとキャッシュに変換)

(2)マスク、layer.mask

(3)角が丸い、layer.maskToBoundsを設定する=はい、layer.cornerRadisが0より大きい、CoreGraphicsを使用して切り取り角を描画することを検討する、または角丸画像を提供

(4)シャドウ、layer.shadowXXX。layer.shadowPathが設定されている場合、オフスクリーンレンダリングは発生しません

 

    • バックグラウンドスレッドによる画像としての描画:

最も完全な解決策は、バックグラウンドスレッドで画像として表示する必要のあるグラフィックを描画し、角の丸い、影、マスクなどの属性の使用を回避することです。

 

    • ASDK:AsyncDisplayKit(テクスチャ)

(1)AsyncDisplayKitは、iOSインターフェースを流暢に保つためにFacebookがオープンソース化したライブラリです。

(2)テキストとレイアウトの計算、レンダリング、デコード、および描画はすべて、さまざまな方法で非同期に実行されることを期待していますが、UIKitおよびCore Animation関連の操作はメインスレッドで実行する必要があります。ASDKの目的は、これらのタスクをメインスレッドからできるだけ削除することです。

(3)ASDKはUIKitをカプセル化し、ほぼすべてのコントロールとプロパティを提供します

(4)UIViewおよびCALayerとは異なり、ASDisplayNodeはスレッドセーフであり、バックグラウンドスレッドで作成および変更できます。

 

    • 特別なオフスクリーンレンダリング:特別な「オフスクリーンレンダリング」メソッド-CPUレンダリング。

(1)drawRectメソッドが書き直され、描画操作にCore Graphicsテクノロジが使用されている場合、CPUレンダリングが関係します。レンダリングプロセス全体は、アプリケーションのCPUによって「同期的に」完了し、レンダリングされたビットマップは最終的にGPUに渡されて表示されます。

(2)CoreGraphicは通常スレッドセーフであるため、非同期に描画して、表示時にメインスレッドに戻すことができます。

 

    • ラスタライズについて、shouldRasterizeの設定(理解するため)

1. ShouldRasterize = YES、他の属性がオフスクリーンレンダリングをトリガーすると、ラスタライズされたコンテンツがキャッシュされます。対応するレイヤーとそのサブレイヤーが変更されていない場合は、次のフレームで直接再利用できます。shouldRasterize = YES、これは暗黙的にビットマップを作成し、さまざまなシャドウマスクやその他の効果もビットマップに保存されてキャッシュされるため、レンダリングの頻度が低下します(ベクターグラフィックではありません)。

2.ラスタライズされたレイヤーを更新すると、多くのオフスクリーンレンダリングが発生します。したがって、CALayerのラスタライズオプションを開くには、使用シナリオを慎重に測定する必要があります。画像のコンテンツが変更されていない場合にのみ使用できます。

(1)前述のUIBlurEffect(曇りガラス)など、静的コンテンツの複雑な特殊効果の再描画を回避するために使用されます

(2)複数のビューによってネストされた複雑なビューの再描画を回避するために使用されます。

(3)頻繁に変更されるコンテンツは、現時点では開かないでください。開くと、パフォーマンスが低下します。たとえば、TableViewCellは、セルの再描画が非常に頻繁に行われるため(再利用)、セルのコンテンツが絶えず変化している場合、セルを継続的に再描画する必要があります。このときにcell.layerがラスタライズされるように設定されている場合、多くの分離が発生します。画面のレンダリングはグラフィックのパフォーマンスを低下させます。

(4)shouldRasterizeを使用した後、レイヤーはビットマップビットマップとしてキャッシュされ、パフォーマンスを向上させるために、shawdowなどのエフェクトが追加されたリソースを大量に消費する静的コンテンツがキャッシュされます。

 

3.使いすぎないでください。キャッシュのサイズは2.5 x画面サイズに制限されます。過度に使用すると、キャッシュを超えた後、画面外のレンダリングが大量に発生します。

4.ラスタライズされた画像が100ミリ秒以上使用されない場合、削除されます。したがって、継続的に使用される画像のみをキャッシュする必要があります。一般的に使用されていない画像キャッシュを使用しても意味がなく、リソースを消費します。

5.オフスクリーンレンダリングを監視する

InstrumentsのCore Animationツールには、オフスクリーンレンダリングに関連するいくつかの検査オプションがあります:

(1)カラーオフスクリーンレンダリングイエロー

開くと、画面外で黄色にレンダリングする必要のあるレイヤーが強調表示されます。つまり、黄色のレイヤーにパフォーマンスの問題がある可能性があります。

(2)カラーがグリーンにヒットし、レッドがミスする

レイヤーが緑色の場合、これらのキャッシュが再利用されていることを意味し、赤色の場合、キャッシュが繰り返し作成されることを意味します。つまり、そこにパフォーマンスの問題があります。たとえば、UIView.layer.shouldRasterize = YESの場合、生成されたビットマップはバッファーに入れられます。TabelViewがスライドして(UITableViewCell多重化)キャッシュを使用して直接ヒットする場合は緑色で表示され、そうでない場合は赤色で表示されます。赤が多いほど、パフォーマンスは低下します。

補足:

(1)iOS 9.0より前のバージョンでは、UIimageViewとUIButtonの角の丸みがオフスクリーンレンダリングをトリガーします

(2)iOS 9.0以降では、UIButtonの角を丸く設定するとオフスクリーンレンダリングがトリガーされますが、UIImageViewでpng画像の角を丸く設定するとオフスクリーンレンダリングはトリガーされません。他のシャドウ効果などを設定しても、オフスクリーンレンダリングはトリガーされます。

 

 

おすすめ

転載: www.cnblogs.com/cleven/p/12750545.html