JavaScriptCore學習之JSPatch源碼閱讀


JSPatch的基本原理:JS傳遞字符串給OC,OC通過Runtime接口調用和替換OC方法。

方法調用

1. require實現

  var _require = function(clsName) {
if (!global[clsName]) {
global[clsName] = {
// _clsName表示這是一個OC的對象
__clsName: clsName
}
}
return global[clsName]
}

global.require = function(clsNames) {
var lastRequire
clsNames.split(',').forEach(function(clsName) {
lastRequire = _require(clsName.trim())
})
return lastRequire
}

通過這個方法可以看到requre(‘UIView’)這個函數只做了什么,在全局作用域上聲明一個對應的變量 UIView,它是類型是一個對象,這個對象有個屬性 __clsName 就是’UIView’。

通過clsNames.split(‘,’).foreach這行代碼,我們可以看到require一次包含多個對象是如何實現的。

2. JS接口

由於JS不存在OC/ruby那樣的消息轉發機制,JSPatch作者做了一個很有意思的處理,對JS腳本做個簡單編譯。將所有JS的方法調用都通過正則進行替換,統一調用 __c() 函數,然后再進行分發。例如下面的轉換:

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

下面我們可以看一下 __c()需要返回一個function,然后這個function會被調用,下面是__c元函數的源代碼

// 為Object的原型定義一個稱之為__c的屬性,它的值是返回一個function返回值的function,並且設置這個屬性configurable:false, enumerable: false。
// JS通過這種為prototype原型添加屬性的方式,為所有的JS對象都添加了這個方法。JS對象調用__c方法的時候,通過調用鏈就可以找到這個方法。
Object.defineProperty(Object.prototype, "__c", {value: function(methodName) {
// ??
if (this instanceof Boolean) {
return function() {
return false
}
}

// 如果對象的__obj和__clsName都是空,表明這是一個JS對象,在對象中查找並返回對應的方法即可
if (!this.__obj && !this.__clsName) {
// 如果在這個JS對象中根本沒有這個方法,拋出異常
if (!this[methodName]) {
throw new Error(this + '.' + methodName + ' is undefined')
}
// 調用bind(this)跟JS的語法特性有關系,綁定返回的這個方法的上下文執行環境為這個對象自身,否則調用的時候,方法內部的this會指向其他離它最近的一個執行環境
return this[methodName].bind(this);
}

// 這里使用self變量保存this的原因是,如果我們直接返回這個function,在里面使用this,指向的就不是當前調用這個函數的對象,所以需要使用self保持this,當然也可以使用bind
var self = this
// 在調用super的時候,做了一個特殊處理
if (methodName == 'super') {
return function() {
if (self.__obj) {
self.__obj.__clsDeclaration = self.__clsDeclaration;
}
// 返回一個新的對象,這個對象的__isSuper標識等於1,在OC調用執行的時候,會判斷這個標識來決定執行哪一個方法
return {__obj: self.__obj, __clsName: self.__clsName, __isSuper: 1}
}
}

/**
* _methodFunc的作用是把JS的相關調用信息傳遞給OC,然后OC通過runtime來執行這些調用,返回結果
*/


if (methodName.indexOf('performSelector') > -1) {
if (methodName == 'performSelector') {
// return的這個function的作用是:直接調用OC對應的SEL,而不是通過OC調用performSelector,與最下面那個直接調用OC方法的function不同的是關於參數的處理
return function(){
var args = Array.prototype.slice.call(arguments)
return _methodFunc(self.__obj, self.__clsName, args[0], args.splice(1), self.__isSuper, true)
}
} else if (methodName == 'performSelectorInOC') {
// 這個與上面的區別在於,這里的調用是異步,為了防止出現線程競爭問題,調用完畢之后進行callback回調
// refer: https://github.com/bang590/JSPatch/wiki/performSelectorInOC-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3
return function(){
var args = Array.prototype.slice.call(arguments)
return {__isPerformInOC:1, obj:self.__obj, clsName:self.__clsName, sel: args[0], args: args[1], cb: args[2]}
}
}
}

// return的這個function的作用是:處理參數,通過_methodFunc進行OC調用,然后返回執行結果
return function(){
var args = Array.prototype.slice.call(arguments)
return _methodFunc(self.__obj, self.__clsName, methodName, args, self.__isSuper)
}
}, configurable:false, enumerable: false})

3. 消息傳遞

JS調用OC函數時,JavaScriptCore會自動會參數和返回值進行轉換。基本類型會自動進行轉換,詳細的轉換參考JavaScriptCore的API

通過上面的講解我們知道,JSPatch主要是通過_methodFunc方法調用OC的消息

  var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
var selectorName = methodName
if (!isPerformSelector) {
// 首先對JS的函數名處理成OC的消息名稱
// 如果OC的方法名中含有"_",在JS中調用的時候需要變成"__",例如OC中_privateMethod,在JS中是__privateMethod()
methodName = methodName.replace(/__/g, "-")
// 處理多參數方法名,例 indexPathForRow_inSection --> indexPathForRow:inSection:以及方法名中含有 "_" 做一下特殊處理
selectorName = methodName.replace(/_/g, ":").replace(/-/g, "_")
var marchArr = selectorName.match(/:/g)
var numOfArgs = marchArr ? marchArr.length : 0
if (args.length > numOfArgs) {
selectorName += ":"
}
}

// 通過self.__obj變量來判斷要調用的方法是instance method還是class method
// _OC_call / _OC_callC方法是在OC中實現,然后export到JS中的
var ret = instance ? _OC_callI(instance, selectorName, args, isSuper):
_OC_callC(clsName, selectorName, args)
return _formatOCToJS(ret)
}

關於_OC_call / _OC_callC / _formatOCToJS 后面介紹

4. 對象持有/轉換

通過上面的介紹,我們了解到UIView.alloc() 這個類方法調用是如何執行的:
a. require('UIView') 這句話在JS全局變量作用域生成了 UIView 對象,它有個屬性叫__isCls,表示這代表一個OC類。
b. 調用UIView這個對象的alloc()方法,會去到__c()函數,在這個函數里判斷調用者 __isCls 屬性,知道它是代表OC類,把方法名和類名傳遞給OC完成調用

那問題來了,alloc()返回UIView實例對象給JS,如果直接使用這個對象調用UIView的方法的話,就會拋出異常,因為UIView並不是confirms to JSExport的,所以我們盡管可以在JS中拿到這個對象的指針,並且將這個對象以參數形式出傳遞給OC,但是卻並沒有辦法操作它的方法或屬性,所以這種只是簡單wrap的OC對象基本上沒法使用。這個問題我在學習JavaScriptCore的時候,一直覺得這個地方嚴重限制了JavaScriptCore的應用。

在JSPatch中,前面說過,所有的函數調用都會被調換為__c()方法,然后由它進行分發,在這個方法中,我們把OC的對象實例指針以及對應的消息名傳遞給OC,然后通過runtime去調用實例對應的消息即可。這里的問題變轉換為,如何確定這個JS變量是OC對象指針的wrapper?跟前面 UIView 的區分方式類似,這里用的是 __obj 變量來wrap這個對象指針,也就是說當OC對象指針傳遞給JS之前,轉換成一個NSDictionary在發送給JS,而根據JavaScriptCore內置的對象轉換規則,NSDictionary會被轉為為JS Object。

__c() 方法中的調用代碼是:

     return function(){
var args = Array.prototype.slice.call(arguments)
// self.__obj指向OC對象指針,self.__clsName是OC的class name。所以如果self.__obj有值,則表明這個JS對象是用來wrap OC對象指針的,這里的調用就變成了調用這個OC實例對象的方法。
return _methodFunc(self.__obj, self.__clsName, methodName, args, self.__isSuper)
}

而OC中對應的wrap OC對象指針的代碼為:

static NSDictionary *_wrapObj(id obj)
{
if (!obj || obj == _nilObj) {
return @{@"__isNil": @(YES)};
}
return @{@"__obj": obj};
}

5. 類型轉換

JS調用OC的過程,首先傳遞各個參數給OC,轉換JS傳來的對象到相應的參數類型,然后調用OC方法,並將返回值包裝為對象,然后返回給JS。

6. 示例

思考一下下面的例子,看看其中的問題,就能基本上理解JS與OC的通信過程了

require(‘UIViewController')
var viewController = UIViewController.alloc().init() // UIViewController是如何找到它的alloc()方法的?返回的對象指針,又是如何找到它的init()方法的呢?
var view = viewController.view() // 這里返回一個UIView對象,為什么不需要執行require(‘UIView')?
view.setFrame({x:0, y:0, width:20, height:20}) // view這個OC對象指針的JS wrapper是如何調用它的setFrame方法的?

方法替換/實現

1.基礎原理

通過JS替換/新增OC方法,基礎原理是OC的method swizzling,具體到JSPatch則是說交換IMP實現,把所有替換或者新增的方法都轉發到一個通用的IMP中,JPForwardInvocation。在這個函數中,從它的NSInvocation類型的參數中中取出所有的OC消息的參數。然后把這些參數傳遞給JS,再調用JS中對應的替換后/新增的方法,最終完成整個調用過程。

有一點需要注意,NSInvocation最少有兩個參數, self_cmd"There are always at least 2 arguments, because an NSMethodSignature object includes the hidden arguments self and _cmd, which are the first two arguments passed to every method implementation.",參考:http://stackoverflow.com/questions/5788346/calling-a-selector-with-unknown-number-of-arguments-using-reflection-introspec

JSPatch override

具體的交換過程是在overrideMethod函數中實現的,例如上面中的過程,說明一個JSPatch是如何把SEL的調用交換到通用的IMP – JPForwardInvocation上的,我們現在有個SEL名字是XXX,IMP地址為YYY,則在overrideMethod中會額外產生兩個新的SEL,同時原始的SEL的IMP也會被修改。我們可以看上圖中的過程,原始selector XXX和新產生的selector _JPXXX都會指向IMP – _objc_msgFroward ,而這個C函數的作用,查看頭文件可以發現,Use these functions to forward a message as if the receiver did not respond to it.,也就是會觸發OC消息轉發的過程,也就是到達JSPatch要求的方法 -forwardInvocation:。

JSPatch之所以使用這種方式來轉發通過JS實現替換的調用,而不是直接替換為一個通用的IMP,問題在於64位情況下,無法使用va_list獲取IMP的參數列表,所以只能通過一種wordaround來解決。把調用在forwardInvocation:的時候進行攔截和轉發,因為這個時候可以通過NSInvocation得到這個調用的參數列表和返回值類型。所以在overrideMethod函數中也同樣把forwardInvocation:的IMP進行了替換,替換成了通用的IMP指針 JPForwardInvocation。

最后,會把指向對應JS實現JSValue * function以_JPXXX為key緩存到_JSOverideMethods字典中,然后在JPForwardInvocation中取出這個指向JS function的指針進行調用。

還有一點需要注意,對於新增的method,由於無法從JS定義中獲取關於方法參數的type encoding,所以對於新增的方法,它的參數類型只能是id,參考:https://github.com/bang590/JSPatch/wiki/JSPatch-%E5%9F%BA%E7%A1%80%E7%94%A8%E6%B3%95#%E6%B7%BB%E5%8A%A0%E6%96%B0%E6%96%B9%E6%B3%95

2. JPForwardInvocation

JPForwardInvocation的功能,首先獲取NSInvocation中的所有參數,將消息轉發給JS中對應的實現,獲取JS調用返回的結果,完整整個的調用過程。

第一段代碼的主要是用來初始化

    // 表示這個OC對象是否已經被釋放
BOOL deallocFlag = NO;
id slf = assignSlf;
// 初始化獲取參數數量
NSMethodSignature *methodSignature = [invocation methodSignature];
NSInteger numberOfArguments = [methodSignature numberOfArguments];

// 轉換調用的selector的名字為JSPatch中用來緩存JSValue *function的key的格式
NSString *selectorName = NSStringFromSelector(invocation.selector);
NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];
SEL JPSelector = NSSelectorFromString(JPSelectorName);

// 容錯處理,查看是否有對應的JS函數的實現,沒有的話,進入OC正常的消息轉發流程
if (!class_respondsToSelector(object_getClass(slf), JPSelector)) {
JPExcuteORIGForwardInvocation(slf, selector, invocation);
return;
}

下面這段代碼從OC的NSInvocation中獲取調用的參數,這里涉及到OC的type encoding,可以參考Apple的官方guide <Objective-C runtime programming Guide>,參考官方文檔:https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html

    // 初始化數組,用來存儲從NSInvocation中獲取的參數列表,然后傳遞給對應的JS函數
NSMutableArray *argList = [[NSMutableArray alloc] init];
if ([slf class] == slf) {
// 如果是類方法,設置__clsName標識表明這是一個OC對象
[argList addObject:[JSValue valueWithObject:@{@"__clsName": NSStringFromClass([slf class])} inContext:_context]];
} else if ([selectorName isEqualToString:@"dealloc"]) {
// 針對要被釋放的對象,使用assign來保存self指針
[argList addObject:[JPBoxing boxAssignObj:slf]];
deallocFlag = YES;
} else {
// 使用weak來保存self指針
[argList addObject:[JPBoxing boxWeakObj:slf]];
}

// NSInvocation的前兩個參數分別為self和_cmd,所以直接從第3個參數開始獲取
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
// 判斷返回值是否為const,如果const,獲取后面的encoding來判斷類型
switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {

#define JP_FWD_ARG_CASE(_typeChar, _type) \
case _typeChar: { \
_type arg; \
// 從invocation中獲取參數,並將參數添加到argList數組中
[invocation getArgument:&arg atIndex:i]; \
[argList addObject:@(arg)]; \
break; \
}
JP_FWD_ARG_CASE('c', char)
JP_FWD_ARG_CASE('C', unsigned char)
JP_FWD_ARG_CASE('s', short)
JP_FWD_ARG_CASE('S', unsigned short)
JP_FWD_ARG_CASE('i', int)
JP_FWD_ARG_CASE('I', unsigned int)
JP_FWD_ARG_CASE('l', long)
JP_FWD_ARG_CASE('L', unsigned long)
JP_FWD_ARG_CASE('q', long long)
JP_FWD_ARG_CASE('Q', unsigned long long)
JP_FWD_ARG_CASE('f', float)
JP_FWD_ARG_CASE('d', double)
JP_FWD_ARG_CASE('B', BOOL)
case '@': {
// 處理id類型的參數,使用__unsafe_unretianed參數,避免內存泄露???
__unsafe_unretained id arg;
[invocation getArgument:&arg atIndex:i];
// 對於block參數做特殊處理,需要進行copy;使用_nilObj表示nil,讓參數可以完整傳遞給JS
if ([arg isKindOfClass:NSClassFromString(@"NSBlock")]) {
[argList addObject:(arg ? [arg copy]: _nilObj)];
} else {
[argList addObject:(arg ? arg: _nilObj)];
}
break;
}
case '{': {
// 處理結構體類型參數
// 獲取結構體類型的名稱,然后根據這個把參數包裝為JSValue類型
NSString *typeString = extractStructName([NSString stringWithUTF8String:argumentType]);
#define JP_FWD_ARG_STRUCT(_type, _transFunc) \
if ([typeString rangeOfString:@#_type].location != NSNotFound) { \
_type arg; \
[invocation getArgument:&arg atIndex:i]; \
[argList addObject:[JSValue _transFunc:arg inContext:_context]]; \
break; \
}
JP_FWD_ARG_STRUCT(CGRect, valueWithRect)
JP_FWD_ARG_STRUCT(CGPoint, valueWithPoint)
JP_FWD_ARG_STRUCT(CGSize, valueWithSize)
JP_FWD_ARG_STRUCT(NSRange, valueWithRange)

// 處理自定義類型的結構體
@synchronized (_context) {
NSDictionary *structDefine = _registeredStruct[typeString];
if (structDefine) {
size_t size = sizeOfStructTypes(structDefine[@"types"]);
if (size) {
void *ret = malloc(size);
[invocation getArgument:ret atIndex:i];
NSDictionary *dict = getDictOfStruct(ret, structDefine);
[argList addObject:[JSValue valueWithObject:dict inContext:_context]];
free(ret);
break;
}
}
}

break;
}
case ':': {
// 處理selector類型參數
SEL selector;
[invocation getArgument:&selector atIndex:i];
NSString *selectorName = NSStringFromSelector(selector);
[argList addObject:(selectorName ? selectorName: _nilObj)];
break;
}
case '^':
case '*': {
// 處理指針類型
void *arg;
[invocation getArgument:&arg atIndex:i];
[argList addObject:[JPBoxing boxPointer:arg]];
break;
}
case '#': {
// Class類類型
Class arg;
[invocation getArgument:&arg atIndex:i];
[argList addObject:[JPBoxing boxClass:arg]];
break;
}
default: {
NSLog(@"error type %s", argumentType);
break;
}
}
}

下面這段代碼根據上面獲取的參數列表,執行對應的JS方法

// 將OC的對象轉換為對應的JS類型
static id formatOCToJS(id obj)
{
// 對於這四種類型的數據,其中的NSArray/NSString/NSDictionary類型,JavaScriptCore是支持JS與OC之間的轉換的,但是JSPatch對他們做了特殊的處理,所以在JS中下面這些數據的使用跟普通的NSObject一樣,如果要使用對應的JS類型,使用toJS()接口轉換一下即可。參考:https://github.com/bang590/JSPatch/wiki/JSPatch-%E5%9F%BA%E7%A1%80%E7%94%A8%E6%B3%95#5-nsarray--nsstring--nsdictionary
if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSDictionary class]] || [obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSDate class]]) {
return _wrapObj([JPBoxing boxObj:obj]);
}
// 對於NSNumber/NSBlock/JSValue則直接傳遞這個對象給JS即可
if ([obj isKindOfClass:[NSNumber class]] || [obj isKindOfClass:NSClassFromString(@"NSBlock")] || [obj isKindOfClass:[JSValue class]]) {
return obj;
}
// 否則,對於不在特殊處理范圍之內的對象,轉換為NSDictioanry的封裝 {"__obj":obj} ,用於在JS中識別這是一個OC對象
return _wrapObj(obj);
}
    // _formatOCToJSList通過調用formatOCToJS將參數數組轉為對應的JS對象數組
NSArray *params = _formatOCToJSList(argList);
// 獲取這個method返回值的類型
const char *returnType = [methodSignature methodReturnType];

// 判斷返回值是否為const,如果const,獲取后面的encoding來判斷類型
switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {
#define JP_FWD_RET_CALL_JS \
// 通過JPSelectorName,獲取在overrideMethod()函數中保存的指向對應JS實現的函數指針
JSValue *fun = getJSFunctionInObjectHierachy(slf, JPSelectorName); \
JSValue *jsval; \
[_JSMethodForwardCallLock lock]; \
// 通過JSValue *function這個JS函數指針,調用對應的JS實現,同時傳入之前處理得到的參數列表params
jsval = [fun callWithArguments:params]; \
[_JSMethodForwardCallLock unlock]; \
// 如果這個JS函數是特殊的調用,即performSelectorInOC,則直接通過callSelector函數在OC中直接調用處理,參考:https://github.com/bang590/JSPatch/wiki/performSelectorInOC-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3
while (![jsval isNull] && ![jsval isUndefined] && [jsval hasProperty:@"__isPerformInOC"]) { \
NSArray *args = nil; \
JSValue *cb = jsval[@"cb"]; \
if ([jsval hasProperty:@"sel"]) { \
// 直接通過OC調用這個方法
id callRet = callSelector(![jsval[@"clsName"] isUndefined] ? [jsval[@"clsName"] toString] : nil, [jsval[@"sel"] toString], jsval[@"args"], ![jsval[@"obj"] isUndefined] ? jsval[@"obj"] : nil, NO); \
// 處理調用返回結果,將返回的OC結果處理為JS調用適合的參數類型
args = @[[_context[@"_formatOCToJS"] callWithArguments:callRet ? @[callRet] : _formatOCToJSList(@[_nilObj])]]; \
} \
[_JSMethodForwardCallLock lock]; \
// 使用之前處理過的參數,執行JS回調
jsval = [cb callWithArguments:args]; \
[_JSMethodForwardCallLock unlock]; \
}

// 根據返回結果的encoding,來設置返回結果的return value
#define JP_FWD_RET_CASE_RET(_typeChar, _type, _retCode) \
case _typeChar : { \
JP_FWD_RET_CALL_JS \
_retCode \
[invocation setReturnValue:&ret];\
break; \
}

#define JP_FWD_RET_CASE(_typeChar, _type, _typeSelector) \
JP_FWD_RET_CASE_RET(_typeChar, _type, _type ret = [[jsval toObject] _typeSelector];) \

#define JP_FWD_RET_CODE_ID \
id __autoreleasing ret = formatJSToOC(jsval); \
if (ret == _nilObj || \
([ret isKindOfClass:[NSNumber class]] && strcmp([ret objCType], "c") == 0 && ![ret boolValue])) ret = nil; \

#define JP_FWD_RET_CODE_POINTER \
void *ret; \
id obj = formatJSToOC(jsval); \
if ([obj isKindOfClass:[JPBoxing class]]) { \
ret = [((JPBoxing *)obj) unboxPointer]; \
}

#define JP_FWD_RET_CODE_CLASS \
Class ret; \
id obj = formatJSToOC(jsval); \
if ([obj isKindOfClass:[JPBoxing class]]) { \
ret = [((JPBoxing *)obj) unboxClass]; \
}

#define JP_FWD_RET_CODE_SEL \
SEL ret; \
id obj = formatJSToOC(jsval); \
if ([obj isKindOfClass:[NSString class]]) { \
ret = NSSelectorFromString(obj); \
}

JP_FWD_RET_CASE_RET('@', id, JP_FWD_RET_CODE_ID)
JP_FWD_RET_CASE_RET('^', void*, JP_FWD_RET_CODE_POINTER)
JP_FWD_RET_CASE_RET('*', void*, JP_FWD_RET_CODE_POINTER)
JP_FWD_RET_CASE_RET('#', Class, JP_FWD_RET_CODE_CLASS)
JP_FWD_RET_CASE_RET(':', SEL, JP_FWD_RET_CODE_SEL)

JP_FWD_RET_CASE('c', char, charValue)
JP_FWD_RET_CASE('C', unsigned char, unsignedCharValue)
JP_FWD_RET_CASE('s', short, shortValue)
JP_FWD_RET_CASE('S', unsigned short, unsignedShortValue)
JP_FWD_RET_CASE('i', int, intValue)
JP_FWD_RET_CASE('I', unsigned int, unsignedIntValue)
JP_FWD_RET_CASE('l', long, longValue)
JP_FWD_RET_CASE('L', unsigned long, unsignedLongValue)
JP_FWD_RET_CASE('q', long long, longLongValue)
JP_FWD_RET_CASE('Q', unsigned long long, unsignedLongLongValue)
JP_FWD_RET_CASE('f', float, floatValue)
JP_FWD_RET_CASE('d', double, doubleValue)
JP_FWD_RET_CASE('B', BOOL, boolValue)

case 'v': {
JP_FWD_RET_CALL_JS
break;
}

case '{': {
NSString *typeString = extractStructName([NSString stringWithUTF8String:returnType]);
#define JP_FWD_RET_STRUCT(_type, _funcSuffix) \
if ([typeString rangeOfString:@#_type].location != NSNotFound) { \
JP_FWD_RET_CALL_JS \
_type ret = [jsval _funcSuffix]; \
[invocation setReturnValue:&ret];\
break; \
}
JP_FWD_RET_STRUCT(CGRect, toRect)
JP_FWD_RET_STRUCT(CGPoint, toPoint)
JP_FWD_RET_STRUCT(CGSize, toSize)
JP_FWD_RET_STRUCT(NSRange, toRange)

@synchronized (_context) {
NSDictionary *structDefine = _registeredStruct[typeString];
if (structDefine) {
size_t size = sizeOfStructTypes(structDefine[@"types"]);
JP_FWD_RET_CALL_JS
void *ret = malloc(size);
NSDictionary *dict = formatJSToOC(jsval);
getStructDataWithDict(ret, dict, structDefine);
[invocation setReturnValue:ret];
free(ret);
}
}
break;
}
default: {
break;
}
}

如果被JS替換的方法是dealloc,則需要特殊處理一下

    // 如果hook的是dealloc方法,則不能通過調用ORIGXXX()的方式來調用原dealloc方法
if (deallocFlag) {
slf = nil;
Class instClass = object_getClass(assignSlf);
Method deallocMethod = class_getInstanceMethod(instClass, NSSelectorFromString(@"ORIGdealloc"));
// 獲取原dealloc的IMP指針,然后直接調用dealloc,防止產生內存泄露
void (*originalDealloc)(__unsafe_unretained id, SEL) = (__typeof__(originalDealloc))method_getImplementation(deallocMethod);
originalDealloc(assignSlf, NSSelectorFromString(@"dealloc"));
}

OC調用

在JSPatch中,JS調用OC的方法,是通過 callSelector 實現的,通過傳入的class name / instance 指針 / selector name / 參數列表,即可以做到直接調用OC的selector。這個跟OC的動態特性有關系,對一個消息的發送可以很靈活控制,這也是JSPatch能實現的技術基礎。這也是為什么純粹swift class不能夠使用JSPatch的原因。

至於這部分的具體實現,由於這里調用的selector都是OC對象現有的方法,所以很容易拿到這個方法的簽名,進而獲得NSInvocation,然后根據type encoding,設置NSInvocation的參數。如果對OC的runtime比較熟悉的話,還是比較容易理解的,這里不多寫了。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com