iOS Hotfix、古い考え方の新しい枠組み

8372 ワード

背景
現在、hotfixフレームワークの多くはruntime+他の言語エンジンによって実現されています.もちろん、手Qのようにバイトコードを自分でコンパイルし、解析する会社もあります.現在、私が知っている会社の中にはruby、lua、javascriptがあります.基本的な原理は、これらの言語エンジンを利用してOCと通信し、runtimeを通じてメソッド呼び出しを完了することです.これでOCのコードを他の言語で書くことができます.ここのエンジンがどんな役割を果たしているかよく考えてみましょう.コード実行の環境にほかならないが、簡単に言えばスタック+基本構文サポートであり、ここでのスタックはメソッド実行によって生成された変数を記録するために用いられる.また、OCでは、ほとんどの実装はOCメソッドを呼び出しif-else、ループに協力することによって実現されるので、以下に紹介する熱修復フレームワークの基本思想は、メソッドを修復する際に、この方法のために環境プールを生産し、方法内部で発生した変数を格納する.メソッドの実装は、N個のメソッド呼び出しに相当するN個のメッセージからなり、これらの呼び出しによって生成された変数とパラメータは環境プールを介してアクセスされる.さらにif-else、whileのサポートにより、簡易版の熱修復フレームワークを実現しました.
InstructionPatch
InstructionPatchは、他の言語エンジンに依存しないホット修復フレームワークであり、jsonファイルをダウンロードし、runtimeを再利用してホット修復を完了します.その基本原理は修正forwardInvocation:自分の実現を指し示し、ある方法を修復しようとすると、自分が実現したforwardInvocation:に転送させることである.メソッドの実装は一連のメッセージからなり、メッセージ間のパラメータ、変数は、メソッドの終了時に自動的にクリアされる環境プール(Map)を介して伝達される.
他の言語エンジンに依存しないメリットは、次のとおりです.
  • 余計なエンジン導入不要
  • サポートされているシステムバージョンの詳細
  • 制御性が高く、対象転換や引用管理にも関わらず当然やっていることが多い
  • デメリット:
  • 基本文法はサポートされておらず、if-else、whileなども自己実現が必要
  • 熱修復コードの読みやすさが悪く、当然スクリプトによるjsonの自動生成による最適化が可能
  • 修復されたメソッド内部実装はOCメソッドの呼び出しのみ
  • @implementation IPViewController
      
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    + (NSString *)returnClassMethod {
        return NSStringFromSelector(_cmd);
    }
    
    - (void)logObject:(NSString *)obj {
        NSLog(@"%@", obj);
    }
    
    @end
    

    viewDidLoadでreturnClassMethodで返される文字列を印刷するには、次のコマンドのみが必要です.
    {
        //       
        "instructions": [
            {
                //      
                "cls": "IPViewController",
                //      
                "methodList": [
                    {
                        //      selector
                        "method": "viewDidLoad",
                        "isStatic": false,
                        //         
                        "messages": [
                            {
                                // [super viewDidLoad]
                                "receiver": "super",
                                "message": "viewDidLoad"
                            },
                            {
                                // NSString *logStr = [IPViewController returnClassMethod];
                                // logStr         
                                "returnType": "NSString",
                                "returnObj": "logStr",
                                "receiver": "IPViewController",
                                "isStatic":true,
                                "message": "returnClassMethod"
                            },
                            {
                                // [self logObject:logStr];
                                "receiver": "self",
                                "message": "logObject:",
                                "args": [
                                    {
                                        //       logStr    
                                        "valueKey": "logStr"
                                    }
                                ]
                            }
                        ]
                    }
                ]
            }
        ]
    }
    

    詳細については、ドキュメントの使用を参照してください.
    テクノロジー実装
    EnvironmentPool
    環境プールは実際にはグローバルな静的Mapであり、key-valueの形式でアクセスし、1つの方法に入ると、自動的にこの方法のために発生した変数を格納するために空間を開き、方法を終了するとこの空間を空にします.しかし、実際には、メソッド実装には非同期のblockがよくあります.この場合、メソッドが終了してもすぐに空にすることはできません.そうしないと、blockは本当に実行中に対応する変数を取りに行く場所がありません.この問題を解決するために,OCの参照カウントを参考にして,メソッド開始時に+1を参照し,blockがある場合にも+1を参照し,メソッド実行終了,block実行終了参照-1を発見することで,環境プールの早期解放の問題を回避できる.しかし、これはもう一つの問題を引き起こし、1つのネットワーク要求のようにsuccessとfailureの2つのblockがあるが、最終的には1つしか実行できないため、参照カウントが常に0より大きくなり、コード上1つのblockが実行されるかどうかは判断できない.したがって、jsonのmessageで参照の回数を手動で指定できるのは、environmentPoolRefCountのみです.
    environmentPoolがMapである以上、idタイプの変数しかアクセスできないので、いくつかの基本タイプの変数に対してパッケージを作り、使用するときにパッケージを解除します.変数タイプは主にNSMethodSignatureの情報によりType Encodingsにより判断される.
    Blockの実装
    具体的なビジネスでは、blockが修復されたメソッドのパラメータとして呼び出されるか、呼び出されたメソッドのパラメータとして構築されるか、という問題が発生します.
  • パラメータの呼び出し方、戻り値が不確定なblock変数
  • パラメータタイプの構築方法、個数不確定block変数
  • 1つのメソッドのパラメータ、戻り値情報を決定するには、まずこのメソッドの署名を知る必要があります.blockも同じですから、最初の問題の核心はblock変数の署名を得ることです.システムは提供されていませんが、githubの多くのライブラリとruntimeソースコードには関連する実装があります.具体的なコードは以下の通りです.
    struct IPBlockLayout {
        void *isa; 
        int flags;
        int reserved;
        void (*invoke)(void *, ...);
        struct IPBlockDescriptor *descriptor;
    };
    
    struct IPBlockDescriptor {
        unsigned long int reserved;
        unsigned long int size;
        void (*copy)(void *dst, void *src);
        void (*dispose)(void *src);
        const char *signature;
    };
    
    enum {
      IP_BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
      IP_BLOCK_HAS_SIGNATURE  =    (1 << 30)
    };
    
    static NSMethodSignature * _IPBlockSignature(id block) {
        struct IPBlockLayout *bp = (__bridge struct IPBlockLayout *)block;
        if (bp && (bp->flags & IP_BLOCK_HAS_SIGNATURE)) {
            void *signatureLocation = bp->descriptor;
            signatureLocation += sizeof(unsigned long int);
            signatureLocation += sizeof(unsigned long int);
            
            if (bp->flags & IP_BLOCK_HAS_COPY_DISPOSE) {
                signatureLocation += sizeof(void(*)(void *dst, void *src));
                signatureLocation += sizeof(void (*)(void *src));
            }
            
            const char *signature = (*(const char **)signatureLocation);
            NSMethodSignature *blockSignature = [NSMethodSignature signatureWithObjCTypes:signature];
            return blockSignature;
        }
        return nil;
    }
    

    サインをもらうと、すべてが簡単になるので、直接利用NSInvocationで呼び出せばいいです.ただし、ここでは、一般的な方法のパラメータはindex=2から設定されており、最初の2つはselfとselectorですが、blcokはselectorがないため(ここでは何の資料も見つかりませんが、私の推測の原因だけです)、index=1から設定します.
    2つ目の問題は面倒で、署名もなく、id、int、double、floatなどを表す汎用的なタイプもなく、後退して求めるしかありません.次に、void *を使用し、最大4つのパラメータをサポートする点はJSPachが直面した問題と同じです.これでblockには大きな制限がありますが、十分なように見えます.
    データ構造
    @protocol IPIntructionArgumentModelProtocol
    @property (nonatomic, copy) NSString *type;
    @property (nonatomic, copy) NSString *valueKey;
    @property (nonatomic, copy) NSString *stringValue;
    @property (nonatomic, assign) double digital;
    @property (nonatomic, copy) NSString *digitalType;
    @property (nonatomic, copy) NSArray *blockParameterTypes;
    @property (nonatomic, copy) NSString *blockParameterPrefix;
    @property (nonatomic, copy) NSArray> *innerMessage;
    @end
    
    @protocol IPIntructionMessageModelProtocol
    @property (nonatomic, copy) NSString *returnType;
    @property (nonatomic, copy) NSString *returnObj;
    @property (nonatomic, copy) NSString *receiver;
    @property (nonatomic, copy) NSString *message;
    @property (nonatomic, assign) BOOL isStatic;
    @property (nonatomic, assign) BOOL isBlock;
    @property (nonatomic, assign) BOOL isIfSnippet;
    @property (nonatomic, assign) BOOL isWhileSnippet;
    @property (nonatomic, assign) BOOL isReturnSnippet;
    @property (nonatomic, assign) NSInteger environmentPoolRefCount;
    @property (nonatomic, copy) NSString *blockKey;
    @property (nonatomic, copy) NSArray> *args;
    @end
    
    @protocol IPIntructionMethodModelProtocol
    @property (nonatomic, copy) NSString *method;
    @property (nonatomic, assign) BOOL isStatic;
    @property (nonatomic, assign) BOOL isMsgForwardStret;
    @property (nonatomic, copy) NSArray> *messages;
    @end
    
    @protocol IPIntructionClassModelProtocol
    @property (nonatomic, copy) NSString *cls;
    @property (nonatomic, copy) NSArray> *methodList;
    @end
    
    @protocol IPIntructionModelProtocol
    @property (nonatomic, copy) NSArray> *instructions;
    @end
    
    IPIntructionArgumentModelProtocolここにはinnerMessageがあり、これが実現blockif-elsewhileの鍵であり、コード実装上は実は再帰呼び出しであり、環境プールを借りて呼び出しのたびに上位層の変数をキャプチャできるようにしている.
    TODO
  • jsonファイルを自動的に生成するスクリプトで、コードの可読性を高め、OCに近い
  • よりフレンドリーにカスタムモデルをサポート
  • GCD対応
  • if-else、while以外の基本的な文法をサポートし、ホットスワップを実現…

  • まとめ
    大体の構想は終わり、単測例、技術の細部処理は徐々に完備しており、全体の実現も非常に簡単である.ここでは多くの言叶を言って、热の修复はすでにリンゴの棒に打たれて死んで、今使っているいくつかの热の修复の枠组みも主にリンゴの审査を迂回することに頼って、しかし私はとても良い绍介のリンゴの审査の手段の関连する技术の文章を探し当てていないで、求めます!
    オープンソースアドレス:InstructionPatch