JSPatch 從入門到放棄

JSPatch 可以讓你用 JavaScript 書寫原生 iOS APP。只需在項(xiàng)目引入極小的引擎戏挡,就可以使用 JavaScript 調(diào)用任何 Objective-C 的原生接口,獲得腳本語言的優(yōu)勢:為項(xiàng)目動態(tài)添加模塊,或替換項(xiàng)目原生代碼動態(tài)修復(fù) bug士复。

一帘营、JSPatch 的基礎(chǔ)使用

在了解了JSPatch的基本概念之后票渠,我們來看一下JSPatch的使用方式。

1. require

在JSPatch中使用OC類之前都需要先調(diào)用 require('ClassName')

require('UIView')
var view = UIView.alloc().init()

這就類似于OC中的 import 芬迄,并且這里可以使用逗號分隔來一次性導(dǎo)入多個(gè)類:

require('UIView, UIColor')
var view = UIView.alloc().init()
var red = UIColor.redColor()

或者直接在使用時(shí)才調(diào)用 require()

require('UIView').alloc().init()

2. 調(diào)用OC方法

調(diào)用類方法

var redColor = UIColor.redColor();

調(diào)用對象方法

var view = UIView.alloc().init();
view.setNeedsLayout();

參數(shù)傳遞

跟OC一樣傳遞參數(shù):

var view = UIView.alloc().init();
var superView = UIView.alloc().init()
superView.addSubview(view)

屬性

獲取/修改屬性值等于調(diào)用這個(gè)屬性的 getter/setter 方法:

view.setBackgroundColor(redColor);
var bgColor = view.backgroundColor();

方法名轉(zhuǎn)換

多參數(shù)方法名使用 _ 分隔:

var indexPath = require('NSIndexPath').indexPathForRow_inSection(0, 1);

如果原OC方法名里包含下劃線问顷,則在JS使用雙下劃線代替:

// OC: [JPObject _privateMethod];
JPObject.__privateMethod()

3. defineClass

API

@param classDeclaration: 字符串,類名/父類名和Protocol
@param properties: 新增property禀梳,字符串?dāng)?shù)組杜窄,可省略
@param instanceMethods: 要添加或覆蓋的實(shí)例方法
@param classMethods: 要添加或覆蓋的類方法
defineClass(classDeclaration, [properties,] instanceMethods, classMethods)

覆蓋對象方法

  1. 在defineClass里定義OC已存在的方法即可覆蓋,方法名規(guī)則與調(diào)用規(guī)則一致:

    // OC
    @implementation JPTableViewController
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
    }
    @end
    
    // JS
    defineClass("JPTableViewController", {
      tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
        ......
      },
    })
    
  2. 使用雙下劃線代表原OC方法名里的下劃線:

    // OC
    @implementation JPTableViewController
    - (NSArray *) _dataSource {
    }
    @end
    
    // JS
    defineClass("JPTableViewController", {
      __dataSource: function() {
      },
    })
    
  3. 在方法名前面加 ORIG 即可調(diào)用未覆蓋前的OC原方法:

    // OC
    @implementation JPTableViewController
    - (void)viewDidLoad {
    }
    @end
    
    // JS
    defineClass("JPTableViewController", {
      viewDidLoad: function() {
         self.ORIGviewDidLoad();
         ......
      },
    })
    

覆蓋類方法

defineClass() 的第三個(gè)參數(shù)就是要添加或者覆蓋的類方法出皇,規(guī)則與覆蓋對象方法一致:

// OC
@implementation JPTestObject
+ (void)shareInstance
{
}
@end
// JS
defineClass("JPTableViewController", {
  //實(shí)例方法
  
}, {
  //類方法
  shareInstance: function() {
    ...
  },
})

動態(tài)新增 Property

可以在 defineClass() 的第二個(gè)參數(shù)為類新增property羞芍,格式為字符串?dāng)?shù)組,使用時(shí)與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()
  },
})

私有成員變量

使用 valueForKey()setValue_forKey() 獲取/修改私有成員變量:

// OC
@implementation JPTableViewController {
     NSArray *_data;
}
@end
// JS
defineClass("JPTableViewController", {
  viewDidLoad: function() {
     var data = self.valueForKey("_data")     //get member variables
     self.setValue_forKey(["JSPatch"], "_data")     //set member variables
  },
})

添加新方法

可以給一個(gè)類隨意添加OC未定義的方法郊艘,但所有的參數(shù)類型都是id類型:

// OC
@implementation JPTableViewController
- (void)viewDidLoad
{
     NSString* data = [self dataAtIndex:@(1)];
     NSLog(@"%@", data);      //output: Patch
}
@end
// JS
var data = ["JS", "Patch"]
defineClass("JPTableViewController", {
  dataAtIndex: function(idx) {
     return idx < data.length ? data[idx]: ""
  }
})

注意:如果新增的方法屬于Protocol里的方法荷科,則需要在defineClass的類聲明參數(shù)里指定實(shí)現(xiàn)的Protocol。

Protocol

可以在定義時(shí)讓一個(gè)類實(shí)現(xiàn)某些Protocol方法纱注,寫法與OC一樣:

defineClass("JPViewController: UIViewController<UIScrollViewDelegate, UITextViewDelegate>", {

})

這樣做的作用是畏浆,當(dāng)添加 Protocol 里定義的方法,而類里沒有實(shí)現(xiàn)的方法時(shí)狞贱,參數(shù)類型不再全是 id刻获,而是自動轉(zhuǎn)為 Protocol 里定義的類型:

@protocol UIAlertViewDelegate <NSObject>
...
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex;
...
@end
defineClass("JPViewController: UIViewController <UIAlertViewDelegate>", {
  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)
  }
})

二、JSPatch 的基礎(chǔ)原理

JSPatch 能做到通過 JS 調(diào)用和改寫 OC 方法最根本的原因是 OC 是動態(tài)語言瞎嬉,OC 上所有方法的調(diào)用/類的生成都通過 OC Runtime 在運(yùn)行時(shí)進(jìn)行蝎毡,我們可以通過類名/方法名反射得到相應(yīng)的類和方法:

Class class = NSClassFromString("UIViewController");
id viewController = [[class alloc] init];
SEL selector = NSSelectorFromString("viewDidLoad");
[viewController performSelector:selector];

也可以替換某個(gè)類的方法為新的實(shí)現(xiàn):

static void newViewDidLoad(id slf, SEL sel) {}
class_replaceMethod(class, selector, newViewDidLoad, @"");

還可以注冊一個(gè)新的類,并為其添加方法:

Class cls = objc_allocateClassPair(superCls, "JPObject", 0);
objc_registerClassPair(cls);
class_addMethod(cls, selector, implement, typedesc);

理論上你可以在運(yùn)行時(shí)通過類名/方法名調(diào)用到任何 OC 方法氧枣,替換任何類的實(shí)現(xiàn)以及新增任意類沐兵。所以 JSPatch 的基本原理就是:JS 傳遞字符串給 OC,OC 通過 Runtime 接口調(diào)用和替換 OC 方法便监。這是最基礎(chǔ)的原理扎谎,實(shí)際實(shí)現(xiàn)過程還有很多怪要打,接下來看看具體是怎樣實(shí)現(xiàn)的烧董。

方法調(diào)用原理

require('UIView')
var view = UIView.alloc().init()
view.setBackgroundColor(require('UIColor').grayColor())
view.setAlpha(0.5)

引入 JSPatch 后毁靶,可以通過以上 JS 代碼創(chuàng)建了一個(gè) UIView 實(shí)例,并設(shè)置背景顏色和透明度逊移,涵蓋了 require 引入類预吆,JS 調(diào)用接口,消息傳遞胳泉,對象持有和轉(zhuǎn)換拐叉,參數(shù)轉(zhuǎn)換這五個(gè)方面觅够,接下來逐一看看具體實(shí)現(xiàn)。

1. require

調(diào)用 require('UIView') 后巷嚣,就可以直接使用 UIView 這個(gè)變量去調(diào)用相應(yīng)的類方法了喘先,require 做的事很簡單,就是在JS全局作用域上創(chuàng)建一個(gè)同名變量廷粒,變量指向一個(gè)對象窘拯,對象屬性 __clsName 保存類名,同時(shí)表明這個(gè)對象是一個(gè) OC Class坝茎。

var _require = function(clsName) {
  if (!global[clsName]) {
    global[clsName] = {
      __clsName: clsName
    }
  }
  return global[clsName]
}

所以調(diào)用 require('UIView') 后涤姊,就在全局作用域生成了 UIView 這個(gè)變量,它指向一個(gè)這樣一個(gè)對象:

{
  __clsName: "UIView"
}

2. JS 調(diào)用接口

與 OC 那樣的消息轉(zhuǎn)發(fā)機(jī)制不同的是嗤放,JS 在調(diào)用沒有定義的屬性或者變量時(shí)會立馬拋出異常思喊。所以若要讓JS里 UIView.alloc() 這句調(diào)用不出錯(cuò),唯一的方法就是給 UIView 添加 alloc 方法次酌,不然是不可能調(diào)用成功的恨课。

__c()元函數(shù)

在 OC 執(zhí)行 JS 腳本之前,通過正則把所有方法調(diào)用都改成調(diào)用 __c()函數(shù)岳服,再執(zhí)行這個(gè) JS 腳本:

UIView.alloc().init()
->
UIView.__c('alloc')().__c('init')()

給 JS 對象基類 Object 加上 __c 成員剂公,這樣所有對象都可以調(diào)用到 __c,根據(jù)當(dāng)前對象類型判斷進(jìn)行不同操作:

Object.defineProperty(Object.prototype, '__c', {value: function(methodName) {
  if (!this.__obj && !this.__clsName) return this[methodName].bind(this);
  var self = this
  return function(){
    var args = Array.prototype.slice.call(arguments)
    return _methodFunc(self.__obj, self.__clsName, methodName, args, self.__isSuper)
  }
}})

_methodFunc() 就是把相關(guān)信息傳給OC吊宋,OC用 Runtime 接口調(diào)用相應(yīng)方法纲辽,返回結(jié)果值,這個(gè)調(diào)用就結(jié)束了璃搜。

var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
    var selectorName = methodName
    if (!isPerformSelector) {
      methodName = methodName.replace(/__/g, "-")
      selectorName = methodName.replace(/_/g, ":").replace(/-/g, "_")
      var marchArr = selectorName.match(/:/g)
      var numOfArgs = marchArr ? marchArr.length : 0
      if (args.length > numOfArgs) {
        selectorName += ":"
      }
    }
    var ret = instance ? _OC_callI(instance, selectorName, args, isSuper):
                         _OC_callC(clsName, selectorName, args)
    return _formatOCToJS(ret)
  }

3. 消息傳遞

搞清楚 JS 接口調(diào)用問題之后來看看 JS 和 OC 是怎樣互傳消息的拖吼。這里用到了 JavaScriptCore ,OC 在啟動 JSPatch 引擎時(shí)會創(chuàng)建一個(gè) JSContext 實(shí)例这吻,JSContext 是 JS 腳本的運(yùn)行環(huán)境吊档,可以給 JSContext 添加方法,JS 就可以直接調(diào)用到這個(gè)方法了:

JSContext *context = [[JSContext alloc] init];
context[@"hello"] = ^(NSString *msg) {
    NSLog(@"Hello %@", msg);
};
[_context evaluateScript:@"hello('word')"];     //output Hello word

JS 通過調(diào)用 JSContext 定義的方法把數(shù)據(jù)傳給 OC橘原,OC通過返回值回傳給JS蛔垢。調(diào)用這種方法申尤,它的參數(shù)/返回值 JavaScriptCore 都會自動轉(zhuǎn)換,OC 里的 NSArray噩咪,NSDictionary吩愧,NSString芋酌,NSNumber,NSBlock 會分別轉(zhuǎn)為 JS 的數(shù)組/對象/字符串/數(shù)字/函數(shù)類型雁佳。

4. 對象持有/轉(zhuǎn)換

經(jīng)過以上一系列描述之后脐帝,我們可以知道 UIView.alloc() 這個(gè)類方法的調(diào)用是怎樣執(zhí)行的了:

a. require('UIView') 這句話在 JS 全局作用域生成了 UIView 這個(gè)對象同云,它有個(gè)屬性叫 __clsName,表示這代表一個(gè) OC 類堵腹。

b. 調(diào)用 UIView 這個(gè)對象的 alloc() 方法炸站,會去到 __c() 函數(shù),在這個(gè)函數(shù)里判斷到調(diào)用者 __clsName 屬性疚顷,知道它是一個(gè) OC 類旱易,把方法名和類名傳遞給 OC 完成調(diào)用。

調(diào)用類方法的執(zhí)行過程基本是這樣腿堤,但是對象方法呢阀坏?事實(shí)上,UIView.alloc() 會返回一個(gè) UIView 實(shí)例對象給 JS笆檀,這個(gè) OC 實(shí)例對象在 JS 里又是怎樣表示的呢忌堂?怎樣可以在 JS 拿到這個(gè)實(shí)例對象后可以直接調(diào)用它的對象方法 UIView.alloc().init()

對于一個(gè)自定義id對象酗洒,JavaScriptCore 會把這個(gè)自定義對象的指針傳給 JS士修,雖然這個(gè)對象在 JS 里沒法直接使用,但是在回傳給 OC 時(shí)樱衷,OC 可以找到這個(gè)對象李命。對于這個(gè)對象的生命周期,如果 JS 有變量引用則引用計(jì)數(shù)加1箫老,JS 變量的引用釋放就減1封字。OC 上沒有特別的持有者則這個(gè)對象的生命周期就跟著 JS 走了,也會在 JS 進(jìn)行垃圾回收時(shí)釋放耍鬓。那么根據(jù)上面的描述阔籽,如果要在 JS 里調(diào)用這個(gè)對象的某個(gè)示例方法,只需要在 __c() 函數(shù)里把這個(gè)對象指針以及它要調(diào)用的方法名回傳給 OC 就行了牲蜀,所以現(xiàn)在就只剩下一個(gè)問題:怎樣在 __c() 函數(shù)里判斷調(diào)用者是一個(gè)OC對象指針笆制?

JSPatch的解決方案是在 OC 把對象返回給 JS 之前,先把它包裝成一個(gè) NSDictionary:

static NSDictionary *_wrapObj(id obj) {
    return @{@"__obj": obj};
}

讓 OC 對象作為這個(gè) NSDictionary 的一個(gè)值涣达,這樣在 JS 里這個(gè)對象就變成:

{__obj: [OC Object 對象指針]}

這樣就可以通過判斷對象是否有 __obj 屬性得知這個(gè)對象是否表示 OC 對象指針在辆,在 __c 函數(shù)里若判斷到調(diào)用者有 __obj 屬性,取出這個(gè)屬性度苔,跟調(diào)用的實(shí)例方法一起傳回給 OC匆篓,就完成了實(shí)例方法的調(diào)用。

5. 類型轉(zhuǎn)換

JS 把要調(diào)用的類名/方法名/對象傳給 OC 后寇窑,OC 調(diào)用類/對象相應(yīng)的方法是通過 NSInvocation 實(shí)現(xiàn)的鸦概,要想能順利調(diào)用到方法并取得返回值,則要做兩件事:

a. 取得要調(diào)用的 OC 方法各參數(shù)類型甩骏,把 JS 傳來的對象轉(zhuǎn)為對應(yīng)的類型進(jìn)行調(diào)用窗市;

b.根據(jù)返回值類型取出返回值先慷,包裝為對象傳回給 JS;

例如開頭例子的 view.setAlpha(0.5)咨察, JS 傳遞給 OC 的是一個(gè) NSNumber论熙,OC 需要通過要調(diào)用 OC 方法的 NSMethodSignature 得知這里參數(shù)要的是一個(gè) float 類型值,于是把 NSNumber 轉(zhuǎn)為 float 值再作為參數(shù)進(jìn)行 OC 方法調(diào)用摄狱。

方法替換原理

JSPatch 可以用 defineClass 接口任意替換一個(gè)類的方法赴肚,其基本實(shí)現(xiàn)原理介紹如下:

1. 基礎(chǔ)原理

在 OC 上,每個(gè)類都是以下面這樣一個(gè)結(jié)構(gòu)體保存的:

struct objc_class {
  struct objc_class * isa;
  const char *name;
  ….
  struct objc_method_list **methodLists; /*方法鏈表*/
};

其中 methodLists 方法鏈表里存儲的是 Method 類型的對象:

typedef struct objc_method *Method;
typedef struct objc_ method {
  SEL method_name;
  char *method_types;
  IMP method_imp;
};

Method 保存了一個(gè)方法的全部信息二蓝,包括 SEL 方法名誉券,type 各參數(shù)和返回值類型,IMP 該方法具體實(shí)現(xiàn)的函數(shù)指針刊愚。

通過 Selector 調(diào)用方法時(shí)踊跟,會從 methodLists 鏈表里找到對應(yīng) Method 進(jìn)行調(diào)用,這個(gè) methodLists 上的元素是可以動態(tài)替換的鸥诽,可以把某個(gè) Selector 對應(yīng)的函數(shù)指針 IMP 替換成新的商玫,也可以拿到已有的某個(gè) Selector 對應(yīng)的函數(shù)指針 IMP,讓另一個(gè) Selector 跟它對應(yīng)牡借,Runtime 提供了一些接口做這些事拳昌,以替換 UIViewController-viewDidLoad: 方法為例:

static void viewDidLoadIMP (id slf, SEL sel) {
   JSValue *jsFunction = …;
   [jsFunction callWithArguments:nil];
}

Class cls = NSClassFromString(@"UIViewController");
SEL selector = @selector(viewDidLoad);
Method method = class_getInstanceMethod(cls, selector);

//獲得viewDidLoad方法的函數(shù)指針
IMP imp = method_getImplementation(method)

//獲得viewDidLoad方法的參數(shù)類型
char *typeDescription = (char *)method_getTypeEncoding(method);

//新增一個(gè)ORIGViewDidLoad方法,指向原來的viewDidLoad實(shí)現(xiàn)
class_addMethod(cls, @selector(ORIGViewDidLoad), imp, typeDescription);

//把viewDidLoad IMP指向自定義新的實(shí)現(xiàn)
class_replaceMethod(cls, selector, viewDidLoadIMP, typeDescription);

這樣就把 UIViewController-viewDidLoad 方法給替換成我們自定義的方法钠龙,APP里調(diào)用 UIViewControllerviewDidLoad 方法都會去到上述 viewDidLoadIMP 函數(shù)里炬藤,在這個(gè)新的 IMP 函數(shù)里調(diào)用 JS 傳進(jìn)來的方法,就實(shí)現(xiàn)了替換 viewDidLoad 方法為 JS 代碼里的實(shí)現(xiàn)碴里,同時(shí)為 UIViewController 新增了個(gè)方法 -ORIGViewDidLoad 指向原來 viewDidLoad 的 IMP沈矿,JS 可以通過這個(gè)方法調(diào)用到原來的實(shí)現(xiàn)。

簡單的方法替換就這樣簡單的實(shí)現(xiàn)了咬腋,但是這么簡單的前提是這個(gè)方法沒有參數(shù)羹膳。如果這個(gè)方法有參數(shù),怎樣把參數(shù)傳給我們新的 IMP 函數(shù)呢根竿?例如 UIViewController-viewDidAppear: 方法陵像,調(diào)用者會傳一個(gè) Bool 值,我們需要在自己實(shí)現(xiàn)的 IMP 上拿到這個(gè)值寇壳,怎樣才能拿到醒颖?當(dāng)然,如果只是針對一個(gè)方法寫 IMP九巡,是可以直接拿到這個(gè)參數(shù)值的:

static void viewDidAppear (id slf, SEL sel, BOOL animated) {
   [function callWithArguments:@(animated)];
}

但是我們要的是實(shí)現(xiàn)一個(gè)通用的 IMP图贸,任意方法蹂季,任意參數(shù)都可以通過這個(gè) IMP 中轉(zhuǎn)冕广,拿到方法的所有參數(shù)回調(diào)給 JS 的實(shí)現(xiàn)疏日。

2. va_list 實(shí)現(xiàn)(32位)

static void commonIMP(id slf, SEL sel, ...)
  va_list args;
  va_start(args, slf);
  NSMutableArray *list = [[NSMutableArray alloc] init];
  NSMethodSignature *methodSignature = [[slf class] instanceMethodSignatureForSelector:sel];
  NSUInteger numberOfArguments = methodSignature.numberOfArguments;
  id obj;
  for (NSUInteger i = 2; i < numberOfArguments; i++) {
      const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
      switch(argumentType[0]) {
          case 'i':
              obj = @(va_arg(args, int));
              break;
          case 'B':
              obj = @(va_arg(args, BOOL));
              break;
          case 'f':
          case 'd':
              obj = @(va_arg(args, double));
              break;
                …… //其他數(shù)值類型
          default: {
              obj = va_arg(args, id);
              break;
          }
      }
      [list addObject:obj];
  }
  va_end(args);
  [function callWithArguments:list];
}

這樣無論方法參數(shù)是什么,有多少個(gè)撒汉,都可以通過 va_list的一組方法一個(gè)個(gè)取出來沟优,組成 NSArray 在調(diào)用 JS 方法時(shí)傳回。很 “完美地解決” 了參數(shù)的問題睬辐。

3. ForwardInvocation 實(shí)現(xiàn)(64位)

當(dāng)調(diào)用一個(gè) NSObject 對象不存在的方法時(shí)挠阁,并不會馬上拋出異常,而是會經(jīng)過多層轉(zhuǎn)發(fā)溯饵,層層調(diào)用對象的:-resolveInstanceMethod: --> -forwardingTargetForSelector: --> -methodSignatureForSelector: --> -forwardInvocation: 等方法侵俗,其中最后 -forwardInvocation: 是會有一個(gè) NSInvocation 對象,這個(gè) NSInvocation 對象保存了這個(gè)方法調(diào)用的所有信息丰刊,包括 Selector 名隘谣,參數(shù)和返回值類型,最重要的是有 所有參數(shù)值 啄巧,可以從這個(gè) NSInvocation 對象里拿到調(diào)用的所有參數(shù)值寻歧。所以我們可以想辦法讓每個(gè)需要被 JS 替換的方法調(diào)用最后都調(diào)到 -forwardInvocation:,就可以解決無法拿到參數(shù)值的問題了秩仆。

具體實(shí)現(xiàn)码泛,以替換 UIViewController-viewWillAppear: 方法為例:

  1. UIViewController-viewWillAppear: 方法通過 class_replaceMethod() 接口指向 _objc_msgForward ,這是一個(gè)全局 IMP澄耍,OC 調(diào)用方法不存在時(shí)都會轉(zhuǎn)發(fā)到這個(gè) IMP 上噪珊,這里直接把方法替換成這個(gè) IMP,這樣調(diào)用這個(gè)方法時(shí)就會走到 -forwardInvocation:;

  2. UIViewController 添加 -ORIGviewWillAppear:-_JPviewWillAppear: 兩個(gè)方法齐莲,前者指向原來的 IMP 實(shí)現(xiàn)卿城,后者是新的實(shí)現(xiàn),稍后會在新的實(shí)現(xiàn)里回調(diào) JS 函數(shù);

  3. 改寫 UIViewController-forwardInvocation: 方法為自定義實(shí)現(xiàn)铅搓。一旦 OC 里調(diào)用 UIViewController-viewWillAppear: 方法瑟押,經(jīng)過上面的處理會把這個(gè)調(diào)用轉(zhuǎn)發(fā)到 -forwardInvocation: ,這時(shí)已經(jīng)組裝好了一個(gè) NSInvocation星掰,包含了這個(gè)調(diào)用的參數(shù)多望。在這里把參數(shù)從 NSInvocation 反解出來,帶著參數(shù)調(diào)用上述新增加的方法 -_JPviewWillAppear:氢烘,在這個(gè)新方法里取到參數(shù)傳給 JS怀偷,調(diào)用 JS 的實(shí)現(xiàn)函數(shù);

整個(gè)調(diào)用過程圖解如下:

過程圖

最后一個(gè)問題,我們把 UIViewController-forwardInvocation: 方法的實(shí)現(xiàn)給替換掉了播玖,如果程序里真有用到這個(gè)方法對消息進(jìn)行轉(zhuǎn)發(fā)椎工,原來的邏輯怎么辦?首先我們在替換 -forwardInvocation: 方法前會新建一個(gè)方法 -ORIGforwardInvocation:,保存原來的實(shí)現(xiàn) IMP维蒙,在新的 -forwardInvocation: 實(shí)現(xiàn)里做了個(gè)判斷掰吕,如果轉(zhuǎn)發(fā)的方法是我們想改寫的,就走我們的邏輯颅痊,若不是殖熟,就調(diào) -ORIGforwardInvocation: 走原來的流程。

Demo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末斑响,一起剝皮案震驚了整個(gè)濱河市菱属,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舰罚,老刑警劉巖纽门,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異营罢,居然都是意外死亡膜毁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門愤钾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瘟滨,“玉大人,你說我怎么就攤上這事能颁≡尤常” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵伙菊,是天一觀的道長败玉。 經(jīng)常有香客問我,道長镜硕,這世上最難降的妖魔是什么运翼? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮兴枯,結(jié)果婚禮上血淌,老公的妹妹穿的比我還像新娘。我一直安慰自己财剖,他們只是感情好悠夯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著躺坟,像睡著了一般沦补。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咪橙,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天夕膀,我揣著相機(jī)與錄音虚倒,去河邊找鬼。 笑死产舞,一個(gè)胖子當(dāng)著我的面吹牛魂奥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庞瘸,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼捧弃,長吁一口氣:“原來是場噩夢啊……” “哼赠叼!你這毒婦竟也來了擦囊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤嘴办,失蹤者是張志新(化名)和其女友劉穎瞬场,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涧郊,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贯被,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妆艘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彤灶。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖批旺,靈堂內(nèi)的尸體忽然破棺而出幌陕,到底是詐尸還是另有隱情,我是刑警寧澤汽煮,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布搏熄,位于F島的核電站,受9級特大地震影響暇赤,放射性物質(zhì)發(fā)生泄漏心例。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一鞋囊、第九天 我趴在偏房一處隱蔽的房頂上張望止后。 院中可真熱鬧,春花似錦溜腐、人聲如沸坯门。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽古戴。三九已至,卻和暖如春矩肩,著一層夾襖步出監(jiān)牢的瞬間现恼,已是汗流浹背肃续。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叉袍,地道東北人始锚。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像喳逛,于是被迫代替她去往敵國和親瞧捌。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容