按照常例,是要給demo的
JSPatch下載
還有作者詳解
首先搭建第一個(gè)JSPatch項(xiàng)目
1奈揍、下載源碼拖進(jìn)去曲尸,只需要如下目錄
2、創(chuàng)建自己的js文件男翰,然后在js文件中敲下如下代碼
require('UIView, UIColor, UILabel')
defineClass('AppDelegate', {
// replace the -genView method
getView: function() {
var view = self.ORIGgetView();
view.setBackgroundColor(UIColor.greenColor())
var label = UILabel.alloc().initWithFrame(view.frame());
label.setText("JSPatch");
label.setTextAlignment(1);
view.addSubview(label);
return view;
}
});
大概意思就是另患,為AppDelegate重寫(xiě)getView方法,方法中調(diào)用ORIGgetView蛾绎,也就是原來(lái)的getview方法昆箕。require就是創(chuàng)建了這幾個(gè)全局變量鸦列,變量指向一個(gè)_clsName為“UIView"的對(duì)象。
require生成類(lèi)對(duì)象時(shí)鹏倘,把類(lèi)名傳入OC薯嗤,OC 通過(guò)runtime方法找出這個(gè)類(lèi)所有的方法返回給 JS,JS 類(lèi)對(duì)象為每個(gè)方法名都生成一個(gè)函數(shù)纤泵,函數(shù)內(nèi)容就是拿著方法名去 OC 調(diào)用相應(yīng)方法骆姐。
3、然后在appdelegate中捏题,調(diào)用玻褪。
[JPEngine startEngine];
NSString *jsPath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:jsPath encoding:NSUTF8StringEncoding error:nil];
[JPEngine evaluateScript:script];
這里需要注意的是,在Build Phases 中Copy Bundle Resources 中一定要有demo.js公荧,不然就拿不到了带射。
好,搭建流程就是這么簡(jiǎn)單循狰。具體的怎么新建類(lèi)窟社,或者替換類(lèi)中的方法,可以參考作者gitbub中詳細(xì)的用法介紹晤揣。
JSPatch的原理是運(yùn)用oc的動(dòng)態(tài)性桥爽,所以在讀源碼之前∶潦叮可以先看一下http://www.reibang.com/p/a3f95abc745f ,了解一下OC 中runtime是怎么調(diào)用盗扒,以及偷換運(yùn)行時(shí)方法的(addMethod以及replaceMethod)
從入口開(kāi)始讀源碼
/*!
@method
@discussion start the JSPatch engine, execute only once.
*/
+ (void)startEngine;
/*!
@method
@description Evaluate Javascript code from a file Path. Call
it after +startEngine.
@param filePath: The filePath of the Javascript code.
@result The last value generated by the script.
*/
+ (JSValue *)evaluateScriptWithPath:(NSString *)filePath;
剛接觸JSPatch跪楞,只用到了這兩個(gè)方法。
先看第一個(gè)方法
-(void)startEngine;
具體的源碼可以下載下來(lái)查看侣灶,在源碼里面看到很多相似的東西
比如
JSContext *context = [[JSContext alloc] init];
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);
};
JScontext是JavaScriptCore這個(gè)庫(kù)里面的類(lèi)甸祭。暫時(shí)理解一個(gè)js與ios交互的上下文。傳入一個(gè)方法名褥影,js中就可以調(diào)用這個(gè)方法池户,執(zhí)行的就是block中的這段代碼。參數(shù)列表也是從js中調(diào)用方法的時(shí)候傳入凡怎,oc這邊接收校焦。
簡(jiǎn)單看一下jspatch.m中的代碼。
找到j(luò)s中調(diào)用_OC_defineClass的代碼统倒。
global.defineClass = function(declaration, instMethods,
clsMethods) {
var newInstMethods = {}, newClsMethods = {}
_formatDefineMethods(instMethods, newInstMethods,declaration)
_formatDefineMethods(clsMethods, newClsMethods,declaration)
var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods)
return require(ret["cls"])
}
global.defineClass這里我理解為寨典,定義了全局的defineClass方法為function(···){};(我在demo.js中調(diào)用的defineclass方法房匆,就算在這里定義的)
然后具體看一下,在defineClass的時(shí)候耸成,都進(jìn)行了哪些操作
先是初始化了兩個(gè)方法的字典對(duì)象(因?yàn)閛c中是運(yùn)行時(shí)發(fā)送消息機(jī)制报亩,所以,一個(gè)方法中需要帶有方法名井氢,方法指針弦追,方法參數(shù),方法接收對(duì)象等參數(shù))花竞。
然后看一下_formatDefineMethods方法中執(zhí)行了什么骗卜,直接貼上源碼
var _formatDefineMethods = function(methods,
newMethods, declaration) {
//遍歷我們要求覆蓋的方法
for (var methodName in methods) {
(function(){
var originMethod = methods[methodName]
newMethods[methodName] = [originMethod.length, function() {
//oc轉(zhuǎn)js , arguments在js中代表被傳遞的參數(shù)左胞,這里是為了把參數(shù)轉(zhuǎn)化為js數(shù)組
var args = _formatOCToJS(Array.prototype.slice.call(arguments))
var lastSelf = global.self
var ret;
try {
global.self = args[0]
if (global.self) {
// 把類(lèi)名作為全局變量保存下來(lái)
global.self.__clsDeclaration = declaration
}
//刪除第0個(gè)參數(shù)寇仓,也就是self。因?yàn)樵趫?zhí)行的過(guò)程中烤宙,第一個(gè)參數(shù)是消息接收的對(duì)象遍烦,現(xiàn)在需要復(fù)制
這個(gè)方法,所以躺枕,不需要第一個(gè)參數(shù)服猪,因?yàn)檎{(diào)用的對(duì)象可能就不再是self了。
args.splice(0,1)
js 中apply
//復(fù)制了originMethod的方法和屬性拐云,我理解為只是更新了參數(shù)罢猪,然后返回方法名。
ret = originMethod.apply(originMethod, args)
global.self = lastSelf
} catch(e) {
_OC_catch(e.message, e.stack)
}
return ret
}]
})()
}
}
看以上代碼叉瘩,能夠知道膳帕,是把新方法中的實(shí)現(xiàn)和相關(guān)參數(shù),關(guān)聯(lián)到了老方法中薇缅。也就是生成了一個(gè)方法名和老方法一樣危彩,但是執(zhí)行函數(shù)不一樣的方法(oc用是一個(gè)結(jié)果體),這里生成一個(gè)字典泳桦,在oc中再去拿到相應(yīng)值去處理汤徽。
然后在defineClass方法中,調(diào)用了oc中的方法OC_defineClass(declaration, newInstMethods, newClsMethods)灸撰,具體實(shí)現(xiàn)內(nèi)容可以在源碼中查看谒府,這里,引擎就從js中抽取了我們要覆蓋的類(lèi)浮毯,和方法完疫。然后就交給oc去覆蓋方法。
以下是純oc中的實(shí)現(xiàn)
static NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *
classMethods)
{
NSDictionary *declarationDict = convertJPDeclarationString(classDeclaration);
NSString *className = declarationDict[@"className"];
NSString *superClassName = declarationDict[@"superClassName"];
//有關(guān)protocol的我直接略過(guò)了亲轨,看著好頭疼趋惨。
NSArray *protocols = [declarationDict[@"protocolNames"] length] ?
[declarationDict[@"protocolNames"] componentsSeparatedByString:@","] : nil;
Class cls = NSClassFromString(className);
if (!cls) {
Class superCls = NSClassFromString(superClassName);
if (!superCls) {
NSCAssert(NO, @"can't find the super class %@", superClassName);
return @{@"cls": className};
}
//找到父類(lèi),然后分配內(nèi)存,新建類(lèi)惦蚊,具體用法查閱runtime 的API
cls = objc_allocateClassPair(superCls, className.UTF8String, 0);
objc_registerClassPair(cls);
}
for (int i = 0; i < 2; i ++) {(傳進(jìn)來(lái)的參數(shù)中,在前的是實(shí)例方法,在后的是類(lèi)方法)
BOOL isInstance = i == 0;
// 判斷是實(shí)例方法還是類(lèi)方法器虾?
JSValue *jsMethods = isInstance ? instanceMethods: classMethods;
//是實(shí)例方法就拿它所屬的類(lèi)讯嫂,是類(lèi)方法就拿它鎖屬的元類(lèi)(oc中,一個(gè)實(shí)例是類(lèi)對(duì)象兆沙,
//他的isa指向它的類(lèi)欧芽,一個(gè)類(lèi)也是類(lèi)(元類(lèi))對(duì)象,它的類(lèi)方法(isa指向元類(lèi))存在于元類(lèi)中)
Class currCls = isInstance ? cls: objc_getMetaClass(className.UTF8String);
NSDictionary *methodDict = [jsMethods toDictionary];
//這個(gè)for循環(huán)開(kāi)始遍歷給這個(gè)類(lèi)添加的所有方法葛圃,并覆蓋原有方法
for (NSString *jsMethodName in methodDict.allKeys) {
JSValue *jsMethodArr = [jsMethods valueForProperty:jsMethodName];
int numberOfArg = [jsMethodArr[0] toInt32];
//選擇器名千扔,也就是一個(gè)方法(method)中的方法名
NSString *selectorName = convertJPSelectorString(jsMethodName);
if ([selectorName componentsSeparatedByString:@":"].count - 1 < numberOfArg) {
selectorName = [selectorName stringByAppendingString:@":"];
}
JSValue *jsMethod = jsMethodArr[1];
//如果currCls實(shí)現(xiàn)了這個(gè)方法,則override库正,覆蓋
if (class_respondsToSelector(currCls, NSSelectorFromString(selectorName))) {
//overrideMethod方法中的核心方法是曲楚,replaceMethod,給一個(gè)對(duì)象傳入一個(gè)selector方法名
//和一個(gè)需要覆蓋這個(gè)selector的imp(函數(shù)地址)和相關(guān)的參數(shù)
overrideMethod(currCls, selectorName, jsMethod, !isInstance, NULL);
} else {
BOOL overrided = NO;
for (NSString *protocolName in protocols) {
char *types = methodTypesInProtocol(protocolName, selectorName, isInstance, YES);
if (!types) types = methodTypesInProtocol(protocolName, selectorName, isInstance, NO);
if (types) {
overrideMethod(currCls, selectorName, jsMethod, !isInstance, types);
free(types);
overrided = YES;
break;
}
}
if (!overrided) {
NSMutableString *typeDescStr = [@"@@:" mutableCopy];
for (int i = 0; i < numberOfArg; i ++) {
[typeDescStr appendString:@"@"];
}
overrideMethod(currCls, selectorName, jsMethod, !isInstance, [typeDescStr cStringUsingEncoding:NSUTF8StringEncoding]);
}
}
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
class_addMethod(cls, @selector(getProp:), (IMP)getPropIMP, "@@:@");
class_addMethod(cls, @selector(setProp:forKey:), (IMP)setPropIMP, "v@:@@");
#pragma clang diagnostic pop
return @{@"cls": className};
}
大概的調(diào)用流程就是這樣的,(js拿到各種參數(shù)->轉(zhuǎn)oc->runtime添加方法)更詳細(xì)的原理以及思考可以在作者github中看到褥符。其他還有很多其他函數(shù)的實(shí)現(xiàn)龙誊。如果要讀,也可以直接從入口startEngine中去看喷楣。
在實(shí)際的操作中趟大,我們需要設(shè)計(jì)一個(gè)下載機(jī)制,然后在通過(guò)作者提供的使用方法铣焊,把需要修復(fù)的類(lèi)的某個(gè)方法替換掉逊朽,就可以實(shí)行熱修復(fù)了。