JSPatchの簡単整理

16135 ワード

これを簡単に整理したのは、いくつかの必要があるからです.実は著者の文書には、原理が含まれています.ほとんどは直接抜粋しています.
bankg 590/JSPatch
JSPatchはiOSのダイナミックアップデートフレームで、プロジェクトに極小エンジンを導入するだけで、JavaScriptを使って任意のObjective-C原生インターフェースを呼び出すことができ、スクリプト言語の利点を得ることができます.プロジェクトの動的な追加モジュール、またはプロジェクトの元コードを置き換えるために動的にバグを修復することができます.
1.プロセス
主なプロセスは、JPプラットフォーム(または自分のバックグラウンド)でパッチJSファイルをリリースし、次の送信方法(フルボリューム、グレースケール、開発など)を選択することです.クライアントはJPSDKにアクセスし(または自分のサービスのダウンロード更新ロジックを使用して)、最新のjsを自動的に更新してダウンロードして実行します.
2.原理
1).基礎原理
Objective-C Runtimeにより運転中にクラス名/メソッド名で反射して該当するクラスと方法を得ることができます.
  • は、ある種類の方法を置き換えることもできます.
  • はまた、クラスの追加方法として、新しいクラスを登録することができます.
  • 基本的な原理は、JSがOCに文字列を伝え、OCがRuntimeインターフェースを通じてOCを呼び出し、置換する方法です.
    2)JSインターフェース実現
    まず、JSでOCメソッドを呼び出すにはどうすればいいですか?
    require('UIView');
    var view = UIView.alloc().init();
    
    jsでは、このような構文はエラーとして報告されます.著者らのやり方は、パッチmain.jsファイルをロードする前に、JSPatch.jsに匿名の自己実行関数をロードしたものです.主な役割はこれらのインターフェースを置き換えるためです.
    正規表現により、すべての方法を呼び出し__c()関数に変更しました.
    JSオブジェクトベースのObjectのprototypeに__cのメンバーを加えると、すべてのオブジェクトが__cに呼び出され、現在のオブジェクトタイプの判断によって異なる動作が行われる.
    具体的な方法:OCローディングmain.jsにおいて、jsコードが修正され、この匿名関数が呼び出され、正規表現を利用して__cの呼び出しが増加した.
     NSString *formatedScript = [NSString stringWithFormat:
     @";(function(){try{%@}catch(e){_OC_catch(e.message, e.stack)}})();",
      [_regex stringByReplacingMatchesInString:script options:0 range:NSMakeRange(0, script.length) withTemplate:_replaceStr]];
    
    したがって、main.jsの実行時には、次のように変換されており、allocinitは文字列に変換される.
    ;(function(){
        try{require('UIView');
        var view = UIView.__c("alloc")().__c("init")();
    }
    catch(e){
        _OC_catch(e.message, e.stack)
    }
    })();
    
    3)メッセージ伝達
    JSインターフェースが完成したら、OCに伝達して使うJavaScripCoreのインターフェースをします.JP Engineが起動する時、対応する実行ブロックを定義します.js中のdefinClassメソッドの置き換えを実行すると、runtimeが起動され、方法が置換される.
    context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) 
    {
        return defineClass(classDeclaration, instanceMethods, classMethods);
    };
    
    context[@"_OC_defineProtocol"] = ^(NSString *protocolDeclaration, JSValue *instProtocol, JSValue *clsProtocol) 
    {
        return defineProtocol(protocolDeclaration, instProtocol,clsProtocol);
     };
    
    
    4)方法置換
    ここでは、パラメータの理由を考慮して、簡単にclass_replaceMethodを使用して方法の置き換えが行われない.NSObjectオブジェクトが存在しない方法を呼び出すと、すぐに異常を投げずに、複数の転送を経て、オブジェクトのレノベInstance Methodを呼び出します.
    -forwardingTarget ForSelector:
    -methodSignature ForSelector:,-forwardInvocation:などの方法です.
    第3段階-forwardInvocation:にはNSInvocationオブジェクトがあり、このNSInvocationオブジェクトは、Selector名、パラメータ、および戻り値タイプを含むこの方法で呼び出されたすべての情報を保持しています.最も重要なのは、全パラメータ値があり、このNSInvocationオブジェクトから呼び出されるすべてのパラメータ値を持つことです.
    具体的には、UIView Controllerの-view Willapearを置き換える方法を例に挙げます.
  • は、UIView Controllerの-viewWillAppear:方法をclass_replaceMethod()インターフェースを介して_objc_msgForwardに向けて指す.これは大域IMPであり、OC呼び出し方法が存在しない場合は、このIMPに転送され、ここで直接に方法をこのIMPに置き換え、この方法を呼び出すと-forwardInvocation:に進む.
  • はUID View Controllerに-ORIGviewWillAppear:および-_JPviewWillAppear:の2つの方法を追加し、前者は元のIMP実装を指し、後者は新しい実装であり、後にこの実装にJS関数をフィードバックする.
  • UID View Controllerを書き換える-forwardInvocation:方法は、カスタム実装である.一旦OCでUIView Controllerの-viewWillAppear:方法が起動されると、上記の処理によって-forwardInvocation:にこの呼び出しが転送され、このときにはNSInvocationが組み込まれていて、この呼び出しのパラメータが含まれています.ここでは、NSInvocationからパラメータを逆に解き、パラメータを持って上記の新たな増加の方法-JPviewWillAppear:を呼び出し、この新しい方法でパラメータを取ってJSに伝え、JSの実現関数を呼び出します.コール全体が終了します.
  • 呼び出しプロシージャ
    3.使用
    詳細なJSPatchの使い方はドキュメントに具体的に紹介されていますが、OCの利用過程で必要とされるほとんどの状況をカバーしています.
    JSPatchの基礎的な使い方
    以下は比較的によく使われるいくつかの種類です.
    1.require
    Objective-Cクラスを使用する前に呼び出しが必要です.
    require('UIView, UIColor')
    var view = UIView.alloc().init()
    var red = UIColor.redColor()
    
    または直接使用時にレギュイルを呼び出します.
    require('UIView').alloc().init()
    
    2.呼び出し方法
  • クラスの方法を呼び出す
  • var redColor = UIColor.redColor();
    
  • 起動方法例
  • var view = UIView.alloc().init();
    view.setNeedsLayout();
    
  • Propertyを取得/修正することは、このPropertyを呼び出すgetter/setter方法に等しい.
    view.setBackgroundColor(redColor);
    var bgColor = view.backgroundColor();
    
  • メソッド名変換マルチパラメータ方法名使用require('className’)分離:
  • var indexPath = require('NSIndexPath').indexPathForRow_inSection(0, 1);
    
    3.defineClass_
    @param classDeclaration:    ,  /    Protocol
    @param properties:   property,     ,   
    @param instanceMethods:            
    @param classMethods:           
    
  • カバー実例方法
  • //   viewDidAppear
    defineClass("HYWebViewController", {
        viewDidAppear: function(animated) {
        self.super().viewDidAppear(animated);
        console.log("JS---viewDidAppear---jspatch test");
          },
     });
    
  • 方法名の前にORIGを加えると、カバーされていない前のOC元の方法が起動されます.
  • // JS
    defineClass("HYWebViewController", {
      viewDidLoad: function() {
         self.ORIGviewDidLoad();
      },
    })
    
  • 種類の方法
  • // JS
    defineClass("JPTableViewController", {
      //    
    }, {
      //   
      shareInstance: function() {
        ...
      },
    })
    
  • カバレッジCategory方法は、通常の方法をカバーするのと同じである.
  • は、defineClass(classDeclaration, [properties,] instanceMethods, classMethods)インターフェースを使用してsuperキーワードを表し、super方法
  • を起動する.
  • 動的追加Propertyは、defineClass()の第二のパラメータがクラスのためにpropertyを追加し、フォーマットは文字列配列であり、使用時はOC propertyインターフェースと一致する:
  • defineClass("JPTableViewController", ['data', 'totalCount'], {
      init: function() {
         self = self.super().init()
         self.setData(["a", "b"])     //     Property (id data)
         self.setTotalCount(2)
         return self
      },
      viewDidLoad: function() {
         var data = self.data()     //   Property  
         var totalCount = self.totalCount()
      },
    })
    
  • Protocolは、定義時に、クラスにいくつかのProtocolインターフェースを実現させることができます.書き方はOCと同じです.
  • defineClass("JPViewController: UIViewController", {
    })
    
    このようにする役割は、Protoclで定義されている方法を追加すると、クラスには実現されていない方法がある場合、パラメータタイプはすべてidではなく、自動的にProtocolで定義されているタイプに変わります.
    // objc
    @protocol UIAlertViewDelegate 
    ...
    - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex;
    ...
    @end
    
    // js
    defineClass("JPViewController: UIViewController ", {
      viewDidAppear: function(animated) {
        var alertView = require('UIAlertView').alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles(
            "Alert",self.dataSource().objectAtIndex(indexPath.row()),self, "OK", null)
         alertView.show()
      }
      alertView_clickedButtonAtIndex: function(alertView, buttonIndex) {
        console.log('clicked index ' + buttonIndex)
      }
    })
    
    4.特殊タイプ
  • Struct
  • JSPatch原生はCGRect/CGPoid/CGS ize/NSRangeの4つのstructタイプをサポートし、JSオブジェクトで表します.
    // Obj-C
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)];
    [view setCenter:CGPointMake(10,10)];
    [view sizeThatFits:CGSizeMake(100, 100)];
    CGFloat x = view.frame.origin.x;
    
    NSRange range = NSMakeRange(0, 1);
    
    // JS
    var view = UIView.alloc().initWithFrame({x:20, y:20, width:100, height:100})
    view.setCenter({x: 10, y: 10})
    view.sizeThatFits({width: 100, height:100})
    
    var x = view.frame().x
    var range = {location: 0, length: 1}
    
  • Selector
  • JSで文字列を使ってSelectorを表します.
    //Obj-C
    [self performSelector:@selector(viewWillAppear:) withObject:@(YES)];
    //JS
    self.performSelector_withObject("viewWillAppear:", 1)
    
    5.nil
    JS上のnullとundefinedはOCのnilを表しています.NSNullを表すなら、nsnullを使います.NULLを表すなら、nullを使います.
    6.NSAray/NSString/NSDictionary
    NSAray/NSString/NSDictionaryは、自動的に対応するJSタイプに変換されず、通常のNSObjectのように使用される.
    //Obj-C
    @implementation JPObject
    + (NSArray *)data
    {
      return @[[NSMutableString stringWithString:@"JS"]]
    }
    + (NSMutableDictionary *)dict
    {
        return [[NSMutableDictionary alloc] init];
    }
    @end
    
    // JS
    require('JPObject')
    var ocStr = JPObject.data().objectAtIndex(0)
    ocStr.appendString("Patch") //   oc    
    
    var dict = JPObject.dict()
    dict.setObject_forKey(ocStr, 'name')
    console.log(dict.objectForKey('name')) //output: JSPatch
    
    NSAray/NSString/NSDictionaryを対応するJSタイプに変換するには.toJS()インターフェース:
    // JS
    var data = require('JPObject').data().toJS()
    //data instanceof Array === true
    data.push("Patch") //     js    
    
    var dict = JPObject.dict()
    dict.setObject_forKey(data.join(''), 'name')
    dict = dict.toJS()
    console.log(dict['name'])    //output: JSPatch
    
    7.ブロック
    ブロック転送
    JS関数をblockパラメータとしてOCに渡すには、まずblockインターフェースで包装する必要があります.
    // Obj-C
    @implementation JPObject
    + (void)request:(void(^)(NSString *content, BOOL success))callback
    {
      callback(@"I'm content", YES);
    }
    @end
    
    // JS
    require('JPObject').request(block("NSString *, BOOL", function(ctn, succ) {
      if (succ) log(ctn)  //output: I'm content
    }))
    
    ここでblockのパラメータの種類を文字列で表します.このblockの各パラメータの種類を書いて、カンマで区切ります.NSObjectオブジェクトは、NSString*のように、NSArryなどはidで表されるが、blockオブジェクトはNSBlockで表される.
    OCからJSに戻るブロックは自動的にJS functionに変わります.直接起動すればいいです.
    // Obj-C
    @implementation JPObject
    typedef void (^JSBlock)(NSDictionary *dict);
    + (JSBlock)genBlock
    {
      NSString *ctn = @"JSPatch";
      JSBlock block = ^(NSDictionary *dict) {
        NSLog(@"I'm %@, version: %@", ctn, dict[@"v"])
      };
      return block;
    }
    + (void)execBlock:(JSBlock)blk
    {
    }
    @end
    
    // JS
    var blk = require('JPObject').genBlock();
    blk({v: "0.0.1"});  //output: I'm JSPatch, version: 0.0.1
    
    もしこれをOCから届いたブロックをOCに戻したいなら、同じようにblockで包装する必要があります.ここでblkはもう普通のJS functionです.私達が定義したJS functionと同じです.
    // JS
    var blk = require('JPObject').genBlock();
    blk({v: "0.0.1"});  //output: I'm JSPatch, version: 0.0.1
    require('JPObject').execBlock(block("id", blk));
    
    まとめ:JSにはblockタイプの変数がありません.OCのblockオブジェクトはJSに伝わってJS functionになります.JSからblockをOCに送るには、すべてblock()インターフェースで包装する必要があります.
    blockにself変数を使う
    blockでself変数が使えません.blockに入る前に臨時変数を使って保存する必要があります.
    defineClass("JPViewController", {
      viewDidLoad: function() {
        var slf = self;
        require("JPTestObject").callBlock(block(function(){
          //`self` is not available here, use `slf` instead.
          slf.doSomething();
        });
      }
    }
    
    制限
    JSからblockにOCには二つの制限があります.
    A.blockパラメータの個数は最大6個までサポートします.(より多くのサポートが必要なら、ソースを変更することができます)B.blockパラメータのタイプはdoubleではありません.
    また、JSパッケージに対応していないblockがOCに伝わってからJSに呼び戻される(原因はissue落155参照):
    - (void)callBlock:(void(^)(NSString *str))block {
    }
    
    defineClass('JPTestObject', {
        run: function() {
            self.callBlock(block('NSString*', function(str) {
                console.log(str);
            }));
        },
        callBlock: function(blk) {
            //blk    block      run     JS    OC      ,    。
            blk("test block");   
        }
    });
    
    8.ダイナミックライブラリの読み込み
    iOSに内蔵されているダイナミックライブラリについては、元のAPPにロードされていない場合、SafariServices.frame ebookをロードするために、以下のように動的にロードすることができます.
    var bundle = NSBundle.bundleWithPath("/System/Library/Frameworks/SafariServices.framework");
    bundle.load();
    
    ローディングしたらSafariServices.frame ebookが使えます.
    9.デバッグ
    一つのオブジェクトを印刷するためにconsone.log()を使用することができます.NSLog()に相当します.直接にXCodeコンソールに打ちます.
    consolie.log()は任意のパラメータをサポートしていますが、NSLogのようなNSLog(@「num:%f」1.0)のパッチはサポートされていません.
    var view = UIView.alloc().init();
    var str = "test";
    var num = 1;
    console.log(view, str, num)
    console.log(str + num);   //   JS     
    
    SafariのデバッグツールでJSをブレークポイントデバッグすることもできます.詳細はJSのブレークポイントデバッグを参照してください.
    4.JPSDK統合関連API
        
        //             
         [JSPatch setLogger:^(NSString *msg) {
            // msg   JSPatch log    ,      logger  
            YOUR_APP_LOG(@"%@", msg);
        }];
         
        
        /*
                  +testScriptInBundle    main.js  
                 +startWithAppKey:   
         */
         [JSPatch testScriptInBundle];
        
        
        /**
          JSPatch           
          //       //       //       //       //    
         */
         [JSPatch setupCallback:^(JPCallbackType type, NSDictionary *data, NSError *error) {
             switch (type) {
                  case JPCallbackTypeUpdate: {
                        NSLog(@"updated %@ %@", data, error);
                  break;
            }
                  case JPCallbackTypeRunScript: {
                      NSLog(@"run script %@ %@", data, error);
                  break;
            }
                  default:
                  break;
        }
    }];
        
        
        /**
                             sync    
         */
    
            [JSPatch setupUserData:@{
                 @"userId": @"100867",
                 @"location": @"guangdong"
            }];
            
            
        /**
                RSA key,  +sync:     ,
         */
        [JSPatch setupRSAPublicKey:];
        
        /**
                              
         */
    #ifdef DEBUG
        [JSPatch setupDevelopment];
    #endif
    
    
        //            -applicationDidBecomeActive:
        [JSPatch sync];
            
    
    5.簡単な代替試験の追加
    //   viewDidLoad
    defineClass('RootViewController', {
                viewDidLoad: function(){
                // ORIG           OC    :
                // self.ORIGviewDidLoad();
                self.super().viewDidLoad();
                console.log("JS---viewDidLoad---viewDidLoad");
    
                //        
                require('NSUserDefaults');
                //         var firstLoad = require('NSUserDefaults').standardUserDefaults();
                var firstLoad = NSUserDefaults.standardUserDefaults();
                require('NSString');
                var saveVersion = NSString.alloc().init();
                saveVersion = firstLoad.objectForKey("YENT_Version_Flag");
              
                require('HYUtil');
                var currentVersion = HYUtil.getCurrentVersion();
                self.labelTitle().setText("  ");
                
                self.setRequestUrl(HYUtil.getServerURL("/main.html#example/index/index"
    ));
                self.tabbar().setSelectedIndex(0);
                self.loadRequest();
                
                require('HYString');
                if(HYString.isValid(saveVersion))
                {
                  //      
                  var iSaveVersion = saveVersion.stringByReplacingOccurrencesOfString_withString(".","").integerValue();
                  var iCurrentVersion = currentVersion.stringByReplacingOccurrencesOfString_withString(".","").integerValue();
                  //        
                  if (iCurrentVersion>iSaveVersion)
                 {
                      firstLoad.setObject_forKey(currentVersion,"YENT_Version_Flag");
                      firstLoad.synchronize();
    
                      self.initGuide();
                  }
                  else
                  {
                      self.initRoot();
                  }
                }
                else
                {
                    //     
                    firstLoad.setObject_forKey(currentVersion,"YENT_Version_Flag");
                    firstLoad.synchronize();
                    
                    self.initGuide();
                 }
                
                //     
                self.showMsg();
    
    
          },
      });
    
    //   showMsg  
    defineClass('RootViewController', {
                showMsg:function(){
                require('HYUtil').showAlert_message("JSPatch  ", "JSPatch  ");
                },
                });