JD Financial アプリ クラッシュ ガバナンスの実践

I.はじめに

2020年初頭、JDフィナンシャルアプリのユーザー規模は数年前をはるかに超え、日々の活動量も2倍になり、同時にアプリのクラッシュ率も微増傾向にあることがわかりましたバージョンの繰り返しで。アプリのクラッシュがユーザーの日常体験に悪影響を及ぼしていることに気付いたとき、クラッシュ率は数千分の 1 に達していました。クラッシュ率は、アプリの品質を測定する重要な指標であり、アプリの安定性に影響を与えるだけでなく、ユーザー エクスペリエンスとビジネスの成長にも直接影響します。起動時にクラッシュが発生すると、アプリが直接アンインストールされる可能性があり、さらに悪い口コミやブランド価値の低下につながります。したがって、この金融アプリは急速に発展していますが、質の高い構築にもより注意を払っています。

JD Financial アプリのクラッシュ率の上昇は、アプリ ビジネスの急速な発展と切り離すことはできません。ますます複雑化するビジネス シナリオ、複数のビジネス間の論理結合、およびアプリ機能の拡張により、プログラムはエラーを起こしやすくなります。一部の古いコードは、複数のビジネス イテレーションの後でゆっくりと影響を受けており、一部の特別なシナリオでのエラーには長い時間がかかり、多数のユーザーがそれらを使用した場合にのみ表示されるため、エラーの修復がタイムリーではありません。グレースケールで現れたクラッシュの問題は、検索に失敗した後に観測待ちの状態になり、オンラインになってからユーザー数が大幅に増加したときに顕著になりました。クラッシュの蓄積が遅いため、特定のバージョンではクラッシュ率が非常に目立つ数値になりました。このような状況を踏まえ、チーム内で当時の状況を徹底的に管理し、維持する方法を模索することを決定しました。

JD Financial アプリのクラッシュ管理はいくつかのバージョンで継続され、上位 20 件のクラッシュは基本的に修復されました。ただし、クラッシュの修復は順風満帆ではなく、再現が難しい問題でも、修復、観察、修復を行うことで解決します。元の問題を修復する期間中、アプリ事業も継続的に更新され、いくつかの新しい問題が発生します.R&Dチームは、発生した問題に特に注意を払い、グレースケールのリリース段階を使用して、芽の問題を排除します. 最終的に、金融アプリはクラッシュ率を 10,000 分の 1 未満に安定させました。

2020 年のモバイル業界パフォーマンス エクスペリエンス レポートによると、アプリ業界の平均クラッシュ率は 0.29%、Android 側の業界の平均クラッシュ率は 0.32%、iOS 側のアプリケーション業界の平均クラッシュ率は 0.10% です。 %。

ここに画像の説明を挿入
JD Finance アプリは高品質の継続的な修復を受けており、クラッシュ率は業界平均よりも 2 桁低く、0.007% で長期間安定しています。

ユーザーのクラッシュ率のデータは、APM パフォーマンス監視システムから取得されます。
ここに画像の説明を挿入
JD Financial アプリのクラッシュ率は業界レベルをはるかに上回っており、R&D チームの詳細な技術的調査と切り離すことはできません.クラッシュに関する基本的な知識は、技術的調査の前提条件です. この記事では、浅いクラッシュから深いクラッシュまでの基本的な知識を説明し、典型的なクラッシュ ケースの解決プロセスを共有します。

2. クラッシュの定義

1.墜落の理由

クラッシュは例外に対する CPU の明示的な応答であり、CPU の例外処理は割り込みに基づいています。中断とは、CPU が実行中のプログラムを中断し、シーンを保存してから、対応する処理プログラムを実行することを意味し、イベントの処理後、ブレークポイントに戻り、「中断された」プログラムの実行を継続します。

オペレーティング システムの関連情報で紹介されています。割り込み (割り込み) と例外 (例外) は、異なる CPU アーキテクチャでは異なる意味を持ちます。

  • たとえば、Intel アーキテクチャでは、割り込み処理エントリは、オペレーティング システム カーネルの割り込みディスパッチ テーブル (IDT) によって定義されます. IDT には 255 個の割り込みベクトルがあり、そのうち最初の 20 個が例外処理エントリとして定義されています.つまり、割り込みに例外が含まれています。
  • ARM アーキテクチャでは、割り込み処理のエントリは例外ベクトル (例外ベクトル) にあり、8 つの例外ベクトルのうち 3 つが割り込みに関連しています。つまり、例外には割り込みが含まれます。

割り込みや例外の定義方法にかかわらず、CPU で例外が発生すると、例外前のプログラムから例外ハンドラに制御が移され、CPU は下位の実行権を取得せずに、カーネル モードに切り替えて実行します。対応する例外ハンドラ。従来の CPU の 5 ステージ パイプラインにおける命令のライフ サイクルは [フェッチ、デコード、実行、メモリへのアクセス、ライト バック] であり、ARM アーキテクチャなどの各ステージで CPU 例外が発生する可能性があります。

  • 「実行」フェーズで生成される「データ アボート」例外: プロセッサ データ アクセス命令のアドレスが存在しない場合、またはアドレスが現在の命令のアクセスを許可しない場合、データ アボート例外が生成されます。
  • 「命令フェッチ」フェーズで発生する「プリフェッチ アボート」異常:プロセッサがプリフェッチした命令のアドレスが存在しない場合、または現在の命令がアクセスできないアドレスの場合、メモリはプロセッサにアボート信号を送信しますが、プリフェッチされた命令が実行されると、命令プリフェッチ アボート例外が発生します。

2 つの例外に対応するハンドラーは、Mach カーネルの exception_triage() 関数を直接的または間接的に呼び出し、EXC_BAD_ACCESS を入力パラメーターとして渡し、exception_triage() は Mach メッセージ パッシング メカニズムを使用して例外を配信します。

2. iOS システムでクラッシュが発生する仕組み

iOS システムのカーネル (Mach) では、例外はカーネル内の基本設定「メッセージ パッシング メカニズム」を通じて処理されます. 例外はメッセージよりも複雑ではありません. msg_send() を通じて間違ったスレッドとタスクによって例外がスローされます.プログラムは msg_recv() 経由でキャプチャします。ハンドラーは例外を処理し、例外を消去し、アプリケーションを終了することを決定できます。

アプリの場合、CPU が特定のコードを実行できない (無効なメモリへのアクセス、読み取り専用ストレージ領域の変更など)、またはオペレーティング システムの特定のポリシーをトリガーする (メモリ使用量が多い、アプリの起動時間が長すぎるなど)、オペレーティング システムはアプリを終了してユーザー エクスペリエンスを保護します。

一部の開発言語では、一部のプログラミング オブジェクトは、エラーが発生したときにプログラムの実行を停止し、クラッシュします。たとえば、Object-C/Swift で範囲外の配列にアクセスすると、NSArray/Array はクラッシュを引き起こし、プログラムの実行を停止します。

2.いくつかのタイプの一般的なクラッシュ

1.ワイルドポインター

ワイルド ポインタは不確実なメモリ アドレスを指し、ワイルド ポインタを介してこのメ​​モリ アドレスにアクセスすると、さまざまな不確実な状況が発生する可能性があります。このメモリアドレスがカバーされていなくても、必ずしも問題があるわけではありません.カバーされているか、アクセスできない領域として割り当てられている場合、プログラムは直接クラッシュします. クラッシュの原因がワイルド ポインターであると判断された場合、現在のクラッシュ コードはクラッシュの原因ではない可能性が高く、呼び出し関係を分析してクラッシュの本当の原因を見つける必要があります。

C言語では、変数宣言後に初期値(ランダムアドレス)が代入されていない場合や、解放後にポインタが空になっていない場合にワイルドポインタが発生することが多いが、Object-Cでは複数のスレッドでワイルドポインタが発生し、変数にアクセスする場合が多い現在のスレッドによって、別のスレッドによってアクセスされます。Object-C のポインターのデフォルト値は nil です。これは、C 言語の NULL と同じです。これは、ポインターがメモリ空間を指していないことを意味します。ワイルド ポインター エラーの結果は通常、変数またはメモリ アクセスの例外であり、一般的なクラッシュ タイプは EXC_BAD_ACCESS メモリ エラーです。

2.デッドロック

iOS システムでは、dispatch_sync を使用してメイン スレッドで同期タスクを実行すると、デッドロックが発生します。いくつかの複雑なビジネス ロジック シナリオのためにタスクがメイン スレッド (以下のサンプル コード) で実行されると、アプリケーションがクラッシュします。

-(void)sceneAnalysis { 
  dispatch_sync(dispatch_get_main_queue(), ^{ 
    NSLog(@"Sync Task Result"); 
  }); 
  NSLog(@"Do Other Tasks"); 
}

プログラムは関数本体の最初の行でスタックし、次のようなエラー メッセージが表示されます。 Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

上記のサンプルコードは、メインスレッドの最も典型的なデッドロックです.同期タスク「NSLog(@"Sync Task Result")」がメインキューに追加されるため、メインスレッドは現在のコードを中断してブロックコードブロックを実行します.そして、dispatch_sync 関数を待ちます。 戻った後、実行を継続します。ただし、メイン キュー (メイン キュー) は、先入れ先出しの原則に従うシリアル キューです. 現在、メイン スレッドは sceneAnalysis 関数を実行しており、dispatch_sync は sceneAnalysis 関数が完了するのを待つ必要があります。sceneAnalysis と dispatch_sync がお互いに待機するため、デッドロックが発生します。

3・ウォッチドッグ

アプリが特定のタスク (開始、終了、またはシステム イベントへの応答) を実行するのに時間がかかる場合、オペレーティング システムは現在のプロセスを終了します。ウォッチドッグ メカニズムによってトリガーされたクラッシュの最も明白な兆候は、エラー コード: 0x8badf00d です。通常、クラッシュ ログは次のようになります。

ここに画像の説明を挿入
アプリの起動時間が最大許容値 (通常は 20 秒) を超えると、iOS システムでウォッチドッグ メカニズムがトリガーされ、プロセスが直ちに終了します。なお、ウォッチドッグによって引き起こされたクラッシュは、独自に開発したエラー監視では収集されず、クラッシュ ログはクラッシュ デバイスで取得できます。Apple はシミュレーターの使用時にウォッチドッグ メカニズムをオフにし、ウォッチドッグ メカニズムはデバッグ モードではトリガーされません。したがって、開発プロセスでは、アプリの起動プロセスを簡潔にし、オンデマンドでロードする必要があります。

3. 金融アプリのクラッシュ管理の実践事例

1. マルチスレッドによるワイルドポインタ問題

金融 APP では、ロング コネクション テクノロジーを使用して市場インデックス情報を更新します。次の凡例:

ここに画像の説明を挿入
機能のグレースケールに注目しており、この期間中にクラッシュは見られませんでしたが、新しいバージョンのオープンとアクティブ ユーザー数の継続的な増加に伴い、APM パフォーマンス モニタリング プラットフォームは時折見つけ始めました。クラッシュします。クラッシュ ログから、MQTTClient オープン ソース ライブラリでクラッシュが発生しました。コミュニケーションを通じて、他のビジネス部門が MQTTClient オープン ソース ライブラリを使用して同じ問題を抱えていることがわかりました。

ここに画像の説明を挿入
オンライン クラッシュが原因で、APM パフォーマンス モニタリング プラットフォームでのクラッシュ数とスタック情報によるリスク評価により、これは本質的ではない散発的な問題であると判断されたため、R&D チームは問題の特定と原因の特定を開始しました。APMパフォーマンス監視プラットフォームでクラッシュを継続的に追跡することで、データサンプルを収集した後、アプリがクラッシュする前、つまりアプリの切り替え前後でユーザーの一般的な操作を分析できます。非常に怪しい操作経路情報です。

ここに画像の説明を挿入
Jingdong Finance アプリの長いリンクは、オープン ソースの MQTT プロトコルを使用しています. R&D チームは、プロジェクトのオープン ソース コミュニティで関連する問題と解決策について問い合わせました. コミュニティには同様の問題がありますが, ライブラリは更新されておらず、維持されていないためです. 2年以上、解決策が見つからない. .

そのため、ビジネスでの使用シナリオと MQTTClient のソース コードに再び注意を向けました。ソースコードでクラッシュの原因となった関数は以下の通りで、MQTT でメッセージを送信する NSStream ストリーム オブジェクトのコールバック タイミングは次のとおりです。

​- (void)stream:(NSStream *)sender handleEvent:(NSStreamEvent)eventCode

実際のビジネス シナリオでは、MQTTClient は、フォアグラウンドで実行されているときに接続し、バックグラウンドに戻るときに積極的に切断することで機能します。そのため、R&D チームは表と裏を切り替えることでこの問題を再現したいと考え、アプリが表と裏のシーンに入るのをコードの高頻度シミュレーションでシミュレートし、最終的にデバッグ モードでこの問題を再現しました。

ここに画像の説明を挿入
MQTT の内部スレッドでクラッシュが発生した バックグラウンドの切り替え時に金融アプリが切断された 外部スレッド (オブジェクトが作成されたスレッド) で MQTTCFSocketEncoder オブジェクトが解放された MQTTCFSocketEncoder オブジェクトの解放と現在のストリーム処理キューに一貫性がなく、現在のスレッドが同期できませんでした.このアドレスに引き続きアクセスすると、「ワイルド ポインター」がクラッシュします。

解決策: 自己オブジェクトを「予約」することにより、オブジェクトが属するヒープ メモリの参照カウントが増加し、コールバック関数の実行中にシステムによってヒープ メモリが再利用されるのを防ぎます。その後、再度高頻度の通話でシーンをシミュレートしましたが、再現されませんでした。したがって、問題は解決されます。

- (void)stream:(NSStream *)sender handleEvent:(NSStreamEvent)eventCode { 
  MQTTCFSocketDecoder *strongDecoder = self; 
  (void)strongDecoder; 
  //其他代码。。。
}

問題を解決した後、管理された更新を通じて、財務アプリと他のビジネス チームがチーム内で均一に更新されます。また、コミュニティで PR を提出しました。将来的には、長い接続の使用中に、研究開発チームは発見された問題に引き続き注意を払います。

開発中のワイルド ポインターのもう 1 つの明白な例は、通知センターの NSNotificationCenter です。通知を登録するとき、通知センターは受信オブジェクトのメモリ アドレスを保存しますが、受信オブジェクトの参照カウントに 1 を追加しません (unsafe_unretained)。オブジェクトが解放されると、元のメモリ アドレスが再利用されている可能性があります。通知センターが通知を送信すると、メッセージは引き続き保存されたメモリ アドレスに送信されますが、保存されたメモリ アドレスは元のオブジェクトではなくなり、受信したメッセージを処理できず、プログラムでクラッシュ エラーが発生します。

ワイルド ポインターによるクラッシュが頻繁に発生し、Apple は iOS9 で通知センターの使用を最適化しました。iOS9 以降のバージョンでは、オブジェクトがリリースされると、すべての通知が自動的に削除されます。オブジェクトが正常にリリースできることが前提です。同様に、tabview のデリゲートと dataSource は、iOS9 より前に unsafe_unretained で変更され、iOS9 以降でアンチワイルド ポインターを変更するために weak を使用するように変更されました (weak で変更されたポインターは、オブジェクトの後に自動的に nil に設定されます)。解放されます)。アプリでサポートされている最小バージョンが 8.0 の場合、デリゲートのワイルド ポインターの問題に特に注意する必要があります。

2. マルチスレッド共有リソースによる過度の解放

開発中に発生する別のエラーも、マルチスレッドが原因で発生します。これは、比較的まれな set メソッド割り当てエラーです。このアプリは、GIF 画像の代わりにオープン ソースの lottie フレームワークを使用して、複雑な雰囲気のアニメーションを実行し、メモリ消費を削減します。ローカルの lottie ファイルを取得する場合、時間がかかるため、サブスレッドを作成してローカルで取得および解凍し、完了後にメイン スレッドに戻ってレンダリングします。さらに、アプリが起動すると、最新の宝くじファイルをプルするネットワーク要求を送信します.ネットワーク要求がスムーズで高速な場合、インターフェースは最新の宝くじファイルを最初に表示します.

クラッシュ統計ログとビジネス コードを確認したところ、マルチスレッドが原因であることがすぐにわかりました。ローカルの lottie ファイルの読み取りはスレッド 1 で、ネットワーク リクエストはスレッド 7 です。ファイル パスを取得した後、スレッド 1 とスレッド 7 の両方が handleCacheFilePath 関数を呼び出して、lottie ファイルを取得します。handleCacheFilePath 関数のコードは次のとおりです。

-(void)handleCacheFilePath:(NSString *)filePath {
​
    if (!filePath) {
        return;;
    }
​
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
​
        NSData *zipData = [NSData dataWithContentsOfFile:filePath];
​
        /*省略zip解压等其他操作 ... */
​
        manager.lottieData = zipData;
​
        dispatch_async(dispatch_get_main_queue(), ^{
            //回主线程
        });
    });
}

解凍後、ロッティのjsonデータをself.lottieDataに代入し、次のインターフェースでロッティのアニメーションが表示されるのを待ちます。実際の運用ではクラッシュが発生する場合がありますが、アプリのビジネスは複雑であり、トリガーのシナリオが非常に過酷なため、宝くじの取得および表示モジュールを新しい Domo に移動して再現します。

解析後のクラッシュ ログの内容は次のとおりです。
ここに画像の説明を挿入
クラッシュ タイプ EXC_BAD_ACCESS (SIGSEGV) を見ると、メモリ エラーが示されています。アプリケーションのマルチスレッド エラーは、通常、何らかのメモリの問題を引き起こします。クラッシュ ログは、メモリ エラーと非常によく似ています。通常、エラーの種類は EXC_BAD_ACCESS (SIGSEGV) です。分析の結果、manager.lottieData = zipData の割り当てでクラッシュが発生しました. 引き続きクラッシュ情報を確認すると、クラッシュの原因は変数の過度の解放であることがわかります。

ここに画像の説明を挿入
Object-C で変数に値を代入することはセッター メソッドを呼び出すことであることがわかっているのに、なぜ過剰にリリースされるのでしょうか? OC の基になるソース コードがどのように処理されるかを見てみましょう (ソース コード リンク: https://opensource. apple.com/source/objc4/objc4-723/runtime/objc-accessors.mm.auto.html)。アセンブリ コードを介して、set メソッドが OC ランタイムの objc_setProperty_nonatomic 関数を呼び出すことがわかり、Apple のランタイムのソース コードの一部がオープン ソースであるため、ソース コードを直接確認します。

void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}

reallySetProperty 関数は、実際には objc_setProperty_nonatomic で呼び出されます。

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }
​
    id oldValue;
    id *slot = (id*) ((char*)self + offset);//计算偏移量获取指针地址
​
    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);//retain新值newValue
    }
​
    if (!atomic) {//非原子属性
        oldValue = *slot;//第一步
        *slot = newValue;//第二步
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }
​
    objc_release(oldValue);//释放旧值 引用计数-1
}

問題の鍵は、reallySetProperty 関数の 20 ~ 31 行目にあります.非アトミック操作の場合は、スロットを直接oldValue オブジェクトに割り当て、新しい値をスロットに支払い、最後に oldValue の解放参照カウントを次のようにデクリメントします。一。If it is a atomic operation (atomic modified), a spinlock will be added before the variable is read. スピンロック スピンロックが現在のスレッドによって取得された後、別のスレッドはスピンロックを取得できず、その場で待機することしかできません。

lottieData は共有変数であり、nonatomic nonatomic で装飾されているため、スレッド 1 が oldValue = *slot の割り当ての実行を終了すると、タイム スライスが使い果たされると想定して、スレッド 1 の割り当ては非アトミック状態になります。このときCPUのスケジューリングが7番スレッドの実行を開始し、7番スレッドも同じ操作を行ってoldValueに値を代入します。割り当てが完了したら、objc_release(oldValue) コマンドを実行して、oldValue が指すメモリ空間を解放します。この時点で、oldValue が指すメモリ空間は解放されています。7 番スレッドの実行後、CPU は 1 番スレッドの実行に回っていますが、1 番スレッドが再度 objc_release(oldValue) メソッドを実行するとクラッシュします。その理由は、すでに解放されているメモリに対して解放操作を実行するためです。これは、クラッシュ スタックの overrelease_error エラーにも対応します。

解決策: 原理を深く理解すれば、問題の解決は当然のことです。lottieData はマネージャーが保持する共有変数であり、マルチスレッドの競合を防ぐためにアトミック デコレーションを使用するように変更できます。アトミックに改変された変数の set メソッドと get メソッドはスピンロックを追加するため、読み込みが頻繁に行われるシーンの場合、スピンロックはより多くの CPU リソースを消費します。幸いなことに、lottieData をビジネス分析と組み合わせて使用​​するシナリオは多くなく、CPU リソースを過度に浪費することはありません。さらに、コード ロジックを変更して、lottieData を一時変数として宣言し、一時変数を関数の戻り値として使用して、マルチスレッド競合の問題を解決できます。

3. メソッド呼び出し例外

万華鏡のようなさまざまなクラッシュの場合、メソッド呼び出しの例外は、修正が最も簡単な問題の 1 つです。アプリでは、過失により実装されていないメソッドや、メモリ解放のために存在しないオブジェクト メソッドがいくつかありますが、開発者はすでに知っており、何度も解決しているため、解決策については説明しません。ここでは主に、「インスタンスに送信された認識されないセレクター」例外スローのプロセスを調べます。

OC でオブジェクトのメソッドを呼び出すことの本質は、オブジェクトにメッセージを送信することであることは誰もが知っています。メソッド呼び出しは、コンパイル中に objc_msgSend 関数に変換されます。最初の必須パラメーターはメッセージ受信者で、2 番目の必須パラメーターはメソッド名で、その後に渡されたパラメーターが続きます。
objc_msgSend(id 自己、SEL op、…)

メッセージを送信する手順は次のとおりです。

(1) セレクターを無視する必要があるかどうかを検出する Mac OX システムにガベージ コレクション機構がある場合、retain/release 関数は無視されます。
(2) 応答オブジェクトが nil かどうかを確認します。nil オブジェクトへのメッセージの送信は、ランタイム システムによって無視されます。
(3) キャッシュから IMP 関数ポインタ検索メソッドで実現し、存在する場合はそのメソッドを実行する。
(4) キャッシュに見つからない場合は、Class のメソッド リストから検索し、親クラスのメソッド リストを再帰的に検索します。
(5) 何も見つからない場合は、動的メソッド解決とメッセージ転送プロセスに入ります。

ここに画像の説明を挿入
objc_msgSend 関数を使用してオブジェクトにメッセージを送信します。複数のプロセスの後でオブジェクトを処理できない場合は、例外がスローされます。クラッシュする前に、OC のランタイム システムは次の 2 つの手順を実行します。

  • DynamicMethod Resolution (動的メソッド解決) は
    、オブジェクト メソッドを例に取ります. システムは resolveInstanceMethod: を呼び出してオブジェクトのメソッドを動的に追加します. 戻り値が yes の場合, インスタンス メソッドを再度検索します. 戻り値がいいえ、メッセージ転送処理に入ります。
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(handleOpenPage:)) {
        IMP imp = class_getMethodImplementation([self class], @selector(openNewPage:));
        class_addMethod([self class], sel, imp, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

上記の例では、実装 (openNewPage:) をインスタンス オブジェクトの handleOpenPage: メソッドに動的に追加しています。ここで、「v@:」は戻り値とパラメーターを表します。各文字の意味は、Type Encodings で確認できます。(タイプ エンコーディングのリンク: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)

  • Message Forwarding (メッセージ転送)
    メッセージ転送は、forwardingTargetForSelector メソッドを呼び出して新しいターゲットを受信者として取得し、セレクターを再実行します。オブジェクト メソッドの場合は、オーバーライドする必要があります ((id)forwardingTargetForSelector:(SEL)aSelector メソッド)。クラス メソッドの場合は、+ (id)forwardingTargetForSelector:(SEL)aSelector メソッドをオーバーライドします。
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if(aSelector == @selector(handleOpenPage:)){
        return _otherObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

返されたオブジェクトが無効な場合 (nil または古いレシーバーと同じ)、forwardInvocation プロセスに入ります。このメソッドは、転送ロジックを定義するためにプログラムでオーバーライドできます。anInvocation パラメータは、ランタイム システムが methodSignatureForSelector: メソッドを呼び出してメソッド シグネチャを取得するときに生成されるオブジェクトです。forwardInvocation: を書き換えるときは、methodSignatureForSelector: メソッドも書き換える必要があります。そうしないと、例外がスローされます。

- (void)forwardInvocation:(NSInvocation *)anInvocation {
​
    if ([_otherObject respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:_otherObject];
    } else {
        [super forwardInvocation:anInvocation];
    }

メッセージ転送のプロセス全体を次の図に示します。

ここに画像の説明を挿入
オブジェクトが対応するメソッドを実装していない場合、ランタイム システムは forwardInvocation メッセージを通じてオブジェクトに通知します。すべてのオブジェクトは NSObject クラスから forwardInvocation: メソッドを継承し、NSObject のメソッド実装は単に doesNotRecognizeSelector: を呼び出します。独自の forwardInvocation: メソッドを実装することで、メソッド実装内の他のオブジェクトにメッセージを転送できます。これらのプロセスで処理されない場合、例外がスローされ、クラッシュが発生します。

上記は、クラッシュ管理のプロセスにおける金融アプリの典型的なケース、ソース コード、および原理分析です。上記のケース管理プロセスにおいて、R&D チームは非常に有用な問題発見の経験を蓄積してきました。
4. 実戦経験の蓄積

1. ユーザーの操作パスは、問題を再現するのに最適なプロンプトです. クラッシュ ログのスタック情報は、ユーザーがどのページにアクセスしたか、どのような状態にあるか、フォアグラウンドで実行されているかバックグラウンドで実行されているかを追跡できます. APM パフォーマンス監視プラットフォームと組み合わせることで、アプリの現在の状態をより明確に分析できます。起動 ID に従って、クラッシュ前のアプリのネットワーク ステータスと、どのネットワーク リクエストが送信されたかを確認できます。これらは、開発者が問題をより迅速に再現するのに役立ちます。

2. クラッシュはアプリ自体のビジネス コードで発生するため、多くの場合簡単に解決できますが、使用されているサードパーティのオープン ソース ライブラリで発生した場合は、まずオープン ソース コミュニティにアクセスして同じ問題を見つけることができます。頻繁に維持されるオープン ソース ライブラリは、多くの実際のアプリ シナリオ テストを経ており、同様の問題と解決策があります。R&D チームによって解決された場合は、関連するプル リクエストをオープン ソース コミュニティに送信することもできます。そして、それを解決した経験を、同じ問題に遭遇した開発者と共有してください。

3. クラッシュには特定の発生条件があります. 再現が本当に不可能な場合, 別の角度から解決策を見つける必要があります. ソースコードを読むことは, 問題の性質を理解するための非常に, 非常に良い方法です. Apple のシステムはソース コードはオープン ソースです。オンラインで読むか、関連するソース コードをダウンロードして、詳細を理解することができます。

4. クラッシュログは、クラッシュの問題を解決するための直接的な情報です. クラッシュログには、アプリがクラッシュしたときのスタック情報とクラッシュの原因が含まれています. クラッシュ ログを読むことは、開発において非常に重要です。したがって、次のセクションでクラッシュログの内容を簡単に紹介する必要があります。

五、クラッシュログ分析

1.クラッシュログの内容

クラッシュが発生した後、最初に考えるのは、クラッシュが発生したコード行、スタックの内容、実行中のスレッドです。これらはすべてクラッシュ レポートに含まれています。WWDC のデモを例にとると、ChocolateChip はエミュレータ上で動作します。クラッシュ ログの上部には、アプリ名、バージョン番号、オペレーティング システム、クラッシュの日時などの概要情報が含まれています。

ここに画像の説明を挿入
次の部分がクラッシュの原因です.メインスレッドでエラーが発生します.クラッシュタイプはSIGILL、つまりCPUが存在しないか無効な命令を実行しています. Fatal error に表示されるクラッシュの具体的な理由は、オプションの値が nil の変数を強制的にアンパックすることです。

ここに画像の説明を挿入
次の部分はクラッシュのスタック情報で、現在クラッシュしているスレッド、クラッシュ時のスタック情報などを確認できます。

クラッシュの元のスタック情報を次の図に示します。

ここに画像の説明を挿入
クラッシュの元のスタック情報は、クラッシュの問題を直接特定するのに不便であり、元のスタック情報を記号化する必要があります ()。メモリ アドレスをメソッド名、ファイル名、および行番号に変換するプロセスは、記号化と呼ばれます。クラッシュ ログの記号化には 3 つの要素が必要です。

(1) クラッシュ ログ、クラッシュ ログは、Xcode の [ウィンドウ] オプションで [オーガナイザー] ウィンドウを開いて [クラッシュ] パネルから取得するか、アプリの送信のバックグラウンドからダウンロードできます。完全な監視機能を備えたアプリは、アプリを介してストレージ用にサーバーに収集および報告し、サーバーからクラッシュ ログをダウンロードできます。

(2) 記号表。dSYM (デバッグ SYMbols) は、デバッグ シンボル テーブルとも呼ばれます。Xcode を介してコンパイルおよびアップロードされたすべてのアプリケーションは、自動的にアーカイブされます.Xcode の [ウィンドウ] オプションで [オーガナイザー] ウィンドウを開くと、コンパイルされたアプリケーション ファイルが [アーカイブ] メニューに表示されます. ファイルを選択して、show in finder → display package content でアプリケーションの dsym ファイルを表示します。

(3) /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash パスは、Xcode に付属のシンボリック ツールを取得できます。

上記の 3 つのファイルを同じファイルにコピーし、3 つのファイルの UUID が一致していることを確認してから、ターミナルを使用して現在のディレクトリに入り、シンボリック コマンド ./symbolicatecrash-vxxxx.crash xxxx.app.dSYM を実行します。完了後に xxxx.crash ファイルを開くと、シンボル化されたスタックが表示され、メソッド名、ファイル名、行番号などの情報が明確に表示されます。記号化された情報を下の図に示します。

ここに画像の説明を挿入
もちろん、ログの下部には、クラッシュしたスレッドのレジスタ ステータスや、プロセスに読み込まれたバイナリ データ イメージ (アプリの実行可能ファイル データ) など、いくつかの低レベルの情報が表示されます。Xcode は、シンボル化を通じてシンボル、ファイル、および行番号情報を検索し、それらをスタックに表示します。

登録情報:
ここに画像の説明を挿入
実行ファイルのイメージ:
ここに画像の説明を挿入
以上がクラッシュログの内容であり、内容の中で以下の有用な情報に注意する必要があります。

まず、クラッシュの種類から始めます.例では、例外の種類は EXC_BAD_INSTRUCTION 例外であり、CPU は不正な命令を実行しています。クラッシュ メッセージには、クラッシュの原因がオプション オブジェクトの強制的なアンパックであることが示されています。

次に、クラッシュはメイン スレッドで発生し、スタックにはクラッシュ時に実行されていた関数スタックが含まれています。FatalErrorMessage 関数がスタックに表示されます. これはシステム関数であり、コード内の関数がそれを呼び出します.

スタック トレース (RecipeImage.swift:26) からわかるように、呼び出しは RecipeImage.swift ファイルの 26 行目で発生します。コードには、画像関数が呼び出される Recipe クラスがあり、その関数は何らかのエラーのために fatalErrorMessage 関数を呼び出します。イメージを取得するときに、コードがオプションのパスを強制的にアンパックし、クラッシュを引き起こします。下記参照:

ここに画像の説明を挿入

2.クラッシュログを表示する場所

クラッシュ ログの内容を解釈した後、どこでクラッシュ ログを表示できますか

(1) AppleID を使用して Xcode にログインし、メニュー バーのオーガナイザーでクラッシュ アイテムを表示します。
(2) クラッシュしたマシンを取得できれば、デバイス内のログ情報を直接取得し、アプリに関連するログ情報を除外できます。
(3) アプリ側からクラッシュ情報を収集し、バックグラウンドで分類・分析するアプリ監視プラットフォーム。問題の開発と特定を支援する方が便利です。

6.クラッシュ特別ガバナンス戦略

1. 特別なクラッシュ プロジェクトを設定し、段階的な目標を設定する

JD Financial App の特別なガバナンスがクラッシュする前は、アプリのクラッシュ率は不安定で、バージョンのリリースやビジネスの反復によって変動していました。オンライン統計には、さまざまな種類のクラッシュの問題があります。範囲外の単純な配列のクラッシュ、nil 値を挿入するときのクラッシュ、およびワイルド ポインターとマルチスレッド例外を引き起こすメモリの問題があります。これらの状況に対応して、チームはクラッシュ管理のための特別なチームを設置し、クラッシュの数と緊急度に応じて分類し、クラッシュ リストの上位 10 個の問題を循環的に解決しました。

2. クラッシュ モジュールの場所と配布

現在のアプリは、もはや特定のビジネスに限定されたものではなく、すでに複数のビジネス機能の集合体です。アプリ全体がコンポーネントに分割された後、各ビジネス機能のコードが .a、.framework などの形式でアプリに統合されます。クラッシュが発生すると、クラッシュ コードがどのビジネス モジュールに存在するかを見つけるのが難しくなり、クラッシュの配布と解決が非常に困難になります。この問題に基づいて、クラッシュ タスク フォースは linkmap を介してファイル名の照合を実行するか、grep コマンドを使用して、バイナリにクラッシュ コードを含む .a および .framework ファイルを見つけます。検索が成功すると、タイムリーな処理のために便利な方法ですべてのビジネス関係者に送信されます。

3. アプリ監視システムの確立

以前のクラッシュ解決プロセスでは、R&D が率先して Apple 開発者のバックグラウンドまたはサードパーティのクラッシュ監視バックグラウンド (bugly、Youmeng など) にアクセスして、現在のクラッシュの傾向などを確認していました。たとえば、バックグラウンドで構成を変更するときに突然発生する多数のクラッシュにタイムリーに対応することは困難です。金融アプリは、APM パフォーマンス監視システムを通じてクラッシュの傾向を監視し、クラッシュのしきい値を設定し、一定期間内に指定された数のクラッシュ内でアラームを自動的にトリガーし、クラッシュ率がしきい値を超えた場合にアラームをトリガーします。メールや社内コミュニケーションツールなどの手段を用いて担当者に速やかに対処するよう通知します。同時に、毎週のパフォーマンス レポートが自動的に送信され、パフォーマンス システムが評価されます。

4. 既存の問題に引き続き注意を払い、新たな問題を抑制する

日々の開発には、永続的で再現が難しい問題がいくつかあるはずです。この種の問題は、日常の活動が安定している場合はクラッシュの数が少ないですが、複数のバージョンを経て実行され続けます. 平時は他のクラッシュに沈む可能性がありますが、618 や Double Eleven などの日常の活動が大幅に増加すると. 、クラッシュの数が増加します。問題発見の特別な時期でもあります。また、当初の問題を解決しながら、オンライン化後の新しいビジネスの監視に注力します。グレースケールに表示されない問題の場合、より多くのユーザーが使用するとクラッシュして爆発します。

5.コーディング仕様

適切なコーディング プラクティスは、コーディング エラーを減らすのに役立ちます。プログラム内の複雑なアルゴリズムとロジックはプログラムのほんの一部にすぎず、ほとんどのクラッシュはコード レビューによって回避できます。従来の辞書配列はnull値を挿入する、メソッドが見つからないなど、開発・テスト・グレースケール化を経て基本的に解消されてきました。

7. まとめ

この記事では、クラッシュの発生、クラッシュの典型的なシナリオ、クラッシュ ログの解釈方法など、クラッシュに関する基本的な知識に焦点を当てます。同時に、金融アプリの開発で遭遇した実際のクラッシュ事例と合わせて、問題の原因、クラッシュ場所の特定方法、再現方法、修復方法から詳細に分析します。開発中に、クラッシュのタイプとクラッシュ ログに従って問題を特定し、典型的なクラッシュの解決策を提供できます。アプリのパフォーマンスとユーザー エクスペリエンスは、長期的な最適化プロセスです。クラッシュは最適化にとどまりません。継続的な注意と最適化のみが、コード量が爆発的に増加した今日のアプリを着実に前進させることができます。

この記事の第 6 部では、APM パフォーマンス監視システムについて説明します.APM パフォーマンス監視システムは、JD Technology のモバイル チームと運用保守チームによって構築されたパフォーマンス監視プラットフォームです.起動に時間がかかり、ネットワーク リクエストが変動し、 、ユーザートラック、ネイティブページ監視、クラッシュフリーズ、カスタム監視などの機能が揃っており、起動からサーバー接続、終了までフルリンク監視を実現。現在、JD Technology の複数のアプリが APM パフォーマンス監視システムに接続されており、各アプリおよびビジネス チームにより高い品質保証を提供しています。

この記事の著者: Jingdong Technology Wu Xinyu
より多くの技術的なベスト プラクティスとイノベーションについては、「Jingdong Technology Technology Talk」の WeChat 公式アカウントに注目してください。

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/JDDTechTalk/article/details/119238048