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)
覆蓋對象方法
-
在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) { ...... }, })
-
使用雙下劃線代表原OC方法名里的下劃線:
// OC @implementation JPTableViewController - (NSArray *) _dataSource { } @end
// JS defineClass("JPTableViewController", { __dataSource: function() { }, })
-
在方法名前面加
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)用 UIViewController
的 viewDidLoad
方法都會去到上述 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:
方法為例:
把
UIViewController
的-viewWillAppear:
方法通過class_replaceMethod()
接口指向_objc_msgForward
,這是一個(gè)全局 IMP澄耍,OC 調(diào)用方法不存在時(shí)都會轉(zhuǎn)發(fā)到這個(gè) IMP 上噪珊,這里直接把方法替換成這個(gè) IMP,這樣調(diào)用這個(gè)方法時(shí)就會走到-forwardInvocation:
;為
UIViewController
添加-ORIGviewWillAppear:
和-_JPviewWillAppear:
兩個(gè)方法齐莲,前者指向原來的 IMP 實(shí)現(xiàn)卿城,后者是新的實(shí)現(xiàn),稍后會在新的實(shí)現(xiàn)里回調(diào) JS 函數(shù);改寫
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:
走原來的流程。