欢迎来真孝善网,为您提供真孝善正能量书籍故事!

JSPatch 源代码深入剖析

时间:11-17 现代故事 提交错误

大家好,如果您还对JSPatch 源代码深入剖析不太了解,没有关系,今天就由本站为大家分享JSPatch 源代码深入剖析的知识,包括的问题都会给大家分析到,还望可以解决大家的问题,下面我们就开始吧!

首先搭建第一个JSPatch项目

1.下载源码并拖进去,只需要以下目录

JSPatch目录2.创建自己的js文件,然后在js文件中输入以下代码

需要("UIView,UIColor,UILabel")

DefineClass("AppDelegate", {

//替换-genView方法

getView: 函数(){

var view=self.ORIGgetView();

view.setBackgroundColor(UIColor.greenColor())

var label=UILabel.alloc().initWithFrame(view.frame());

label.setText("JSPatch");

标签.setTextAlignment(1);

view.addSubview(标签);

返回视图;

}

});大致意思就是重写AppDelegate的getView方法,在方法中调用ORIGgetView,也就是原来的getview方法。 require 创建这些全局变量,它们指向_clsName 为“UIView”的对象。

当require生成类对象时,类名被传入OC。 OC通过runtime方法找出该类的所有方法并返回给JS。 JS 类对象为每个方法名生成一个函数。函数的内容就是将方法名带到OC中。调用相应的方法。

3.然后在appdelegate中调用。

[JPEngine启动引擎];

NSString *jsPath=[[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];

NSString *script=[NSString stringWithContentsOfFile:jsPath 编码:NSUTF8StringEncoding error:nil];

[JPEngine评估脚本:脚本];这里需要注意的是,Build Phases的Copy Bundle Resources中一定要有demo.js,否则会获取不到。

嗯,构建过程就是这么简单。关于如何创建新的类或者替换类中的方法的具体说明,请参考作者的gitbub中的详细使用介绍。

JSPatch的原理是利用oc的动态性,所以在阅读源码之前。可以先看一下http://www.jianshu.com/p/a3f95abc745f了解OC中runtime是如何调用的以及如何偷偷改变runtime方法(addMethod和replaceMethod)

从入口开始读源码

/*!

@方法

@discussion启动JSPatch引擎,只执行一次。

*/

+ (void)启动引擎;

/*!

@方法

@description 从文件路径评估Javascript 代码。称呼

它在+startEngine之后。

@param filePath: Javascript 代码的文件路径。

@result 脚本生成的最后一个值。

*/

+ (JSValue *)evaluateScriptWithPath:(NSString *)filePath;我是JSPatch 的新手,只使用这两种方法。

我们先看第一种方法

-(void)启动引擎;具体源码可以下载查看。在源码中你可以看到很多类似的东西。

例如

JSContext *context=[[JSContext alloc] init];

context[@"_OC_defineClass"]=^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {

返回defineClass(类声明,实例方法,类方法);

};

context[@"_OC_defineProtocol"]=^(NSString *protocolDeclaration, JSValue *instProtocol, JSValue *clsProtocol) {

返回defineProtocol(protocolDeclaration, instProtocol,clsProtocol);

};JScontext 是JavaScriptCore 库中的一个类。暂时了解一下js和ios交互的上下文。通过传入一个方法名,就可以在js中调用这个方法,并执行block中的代码。参数列表也是在js中调用方法时传入的,由oc接收。

简单看一下jspatch.m中的代码。

找到js中调用_OC_defineClass的代码。

global.defineClass=函数(声明, instMethods,

cls方法) {

var newInstMethods={}, newClsMethods={}

_formatDefineMethods(instMethods,newInstMethods,声明)

_formatDefineMethods(clsMethods,newClsMethods,声明)

var ret=_OC_defineClass(声明, newInstMethods, newClsMethods)

返回require(ret["cls"])

}global.defineClass 我这里理解的是全局的defineClass方法定义为function(···){}; (我在demo.js中调用的defineclass方法甚至在这里定义了)

然后具体看一下,在defineClass的时候,都进行了哪些操作

首先初始化两个方法的字典对象(因为oc是运行时消息发送机制,一个方法需要有方法名、方法指针、方法参数、方法接收对象等参数)。

然后看一下_formatDefineMethods方法中执行了什么,直接贴源码

var _formatDefineMethods=函数(方法,

newMethods,declaration) {//遍历我们需要重写的方法

for (var methodName in 方法) {

(功能(){

var originMethod=方法[方法名]

newMethods[方法名]=[originMethod.length, function() {

//oc转js,arguments代表js中传入的参数,这里是将参数转成js数组

var args=_formatOCToJS(Array.prototype.slice.call(参数))

var LastSelf=全局.self

var ret;

尝试{

全局.self=args[0]

如果(全局.self){

//将类名保存为全局变量

global.self.__clsDeclaration=声明

}

//删除第0个参数,即self。因为执行过程中,第一个参数是消息接收到的对象,现在需要复制

因此,该方法不需要第一个参数,因为被调用的对象可能不再是self。

args.splice(0,1)

在js中应用

//复制originMethod的方法和属性。据我了解,它只是更新参数,然后返回方法名称。

ret=originMethod.apply(originMethod, args)

全局.self=最后的Self

} 抓住(e){

_OC_catch(e.消息,e.堆栈)

}

返回ret

}]

})()

}

}

看以上代码,能够知道,是把新方法中的实现和相关参数,关联到了老方法中。也就是生成了一个方法名和老方法一样,但是执行函数不一样的方法(oc用是一个结果体),这里生成一个字典,在oc中再去拿到相应值去处理。

然后在defineClass方法中,调用了oc中的方法OC_defineClass(declaration, newInstMethods, newClsMethods)。具体实现内容可以查看源码。这里,引擎从js中提取出我们想要覆盖的类,以及方法。然后将其留给oc 来重写该方法。

以下是纯oc中的实现

静态NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *

类方法)

{

NSDictionary *declarationDict=ConvertJPDeclarationString(classDeclaration);

NSString *className=declarationDict[@"className"];

NSString *superClassName=declarationDict[@"superClassName"];

//我刚刚跳过了有关协议的部分。这让我很头疼。

NSArray *protocols=[declarationDict[@"protocolNames"] length] ?

[declarationDict[@"protocolNames"] ComponentsSeparatedByString:@","] : nil;

类cls=NSClassFromString(className);

如果(!cls){

类superCls=NSClassFromString(superClassName);

如果(!superCls){

NSCAssert(NO, @"找不到超类%@", superClassName);

返回@{@"cls": 类名};

}

//找到父类,然后分配内存并创建一个新类。具体使用请参考运行时API。

cls=objc_allocateClassPair(superCls, className.UTF8String, 0);

objc_registerClassPair(cls);

}

for (int i=0; i 2; i ++) {(传入的参数中,第一个为实例方法,最后一个为类方法)

BOOL isInstance=i==0;

//判断是实例方法还是类方法?

JSValue *jsMethods=isInstance ? instanceMethods: 类方法;

//如果是实例方法,则取其所属的类。如果是类方法,则取它所属的元类。 (在oc 中,实例是类对象。

//他的isa指向它的类。类也是一个类(元类)对象,它的类方法(isa指向元类)存在于元类中)

类currCls=isInstance ? cls: objc_getMetaClass(className.UTF8String);

NSDictionary *methodDict=[jsMethods toDictionary];

//这个for循环开始遍历该类中添加的所有方法并覆盖原来的方法

for (methodDict.allKeys 中的NSString *jsMethodName) {

JSValue *jsMethodArr=[jsMethods valueForProperty:jsMethodName];

int numberOfArg=[jsMethodArr[0] toInt32];

//选择器名称,即方法(method)中的方法名称

NSString *selectorName=ConvertJPSelectorString(jsMethodName);

if ([selectorName ComponentsSeparatedByString:@":"].count - 1 numberOfArg) {

选择器名称=[选择器名称stringByAppendingString:@":"];

}

JSValue *jsMethod=jsMethodArr[1];

//如果currCls实现了这个方法,则重写,重写

if (class_respondsToSelector(currCls, NSSelectorFromString(selectorName))) {

//overrideMethod方法中的核心方法是replaceMethod,向一个对象传入一个选择器方法名

//以及一个需要覆盖这个选择器的imp(函数地址)及相关参数

overrideMethod(currCls, 选择器名称, jsMethod,isInstance, NULL);

} 别的{

BOOL 覆盖=NO;

for (NSString *协议中的协议名称) {

char *types=methodTypesInProtocol(协议名称, 选择器名称, isInstance, YES);

if (!types) types=methodTypesInProtocol(协议名称, 选择器名称, isInstance, NO);

如果(类型){

overrideMethod(currCls, 选择器名称, jsMethod,isInstance, 类型);

免费(类型);

覆盖=是;

休息;

}

}

如果(!覆盖){

NSMutableString *typeDescStr=[@"@@:" mutableCopy];

for (int i=0; i numberOfArg; i ++) {

[typeDescStrappendString:@"@"];

}

overrideMethod(currCls, 选择器名称, jsMethod,isInstance, [typeDescStr cStringUsingEncoding:NSUTF8StringEncoding]);

}

}

}

}

#pragma clang 诊断推送

#pragma clang 诊断忽略了“-Wundeclared-selector”

class_addMethod(cls, @selector(getProp:), (IMP)getPropIMP, "@@:@");

class_addMethod(cls, @selector(setProp:forKey:), (IMP)setPropIMP, "v@:@@");

#pragma clang 诊断流行

返回@{@"cls": 类名};

用户评论

敬情

终于找到了时间好好研究一下 JSPatch 的实现原理!

    有6位网友表示赞同!

该用户已上天

对这个框架一直有兴趣,想了解它到底是怎么工作的。

    有16位网友表示赞同!

像从了良

学习代码是提升技术能力的最好途径,希望能够从 JSPatch 源码中学到东西。

    有12位网友表示赞同!

我怕疼别碰我伤口

JSPatch 确实是个非常实用的工具,源码解析能让我更深入地理解它的功能。

    有11位网友表示赞同!

裸睡の鱼

想要成为一名优秀的 iOS 开发者,就应该对核心框架有所了解,JSPatch 就是很好的例子。

    有19位网友表示赞同!

未来未必来

开源项目是学习的好地方,JSPatch 的代码结构一定很清晰易懂。

    有9位网友表示赞同!

柠栀

看了很多教程,还是想深入看一看源码更放心哈!

    有9位网友表示赞同!

哽咽

希望能从 JSPatch 源码中找到一些开发技巧和经验分享。

    有10位网友表示赞同!

心已麻木i

对 iOS 编程有憧憬,学习 JSPatch 源码能让我更接近这个目标。

    有8位网友表示赞同!

笑傲苍穹

希望能够在解析过程中遇到有用的学习资源和解答疑问的地方。

    有8位网友表示赞同!

坏小子不坏

现在很多项目都用到动态更新的特点,JSPatch 就是关键技术的体现啊!

    有18位网友表示赞同!

我家的爱豆是怪比i

源码解析是一项很好的实践活动,能锻炼代码阅读理解能力。

    有14位网友表示赞同!

采姑娘的小蘑菇

学习 JSPatch 源码能让我更全面地了解 iOS 开发生态系统。

    有11位网友表示赞同!

来自火星的我

对项目二次开发很有帮助,JSPatch 的灵活性和功能性很令人折服!

    有5位网友表示赞同!

海盟山誓总是赊

预祝源码解析顺利,希望能够收获满满的知识点。

    有7位网友表示赞同!

安好如初

代码注释是神器啊!期待 JSPatch 源码的清晰解释和逻辑思路。

    有5位网友表示赞同!

我要变勇敢℅℅

了解 iOS 的运行机制以及框架内部原理,从 JSPatch 开始!

    有19位网友表示赞同!

ー半忧伤

也许能找到一些JSPatch 可以改进的地方呢!

    有9位网友表示赞同!

。婞褔vīp

希望能找到更多关于 JSPatch 进阶学习的资料。

    有10位网友表示赞同!

【JSPatch 源代码深入剖析】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活