
基础原理
能做到通过JS调用和改写OC方法最根本的原因是 Objective-C 是动态语言,OC上所有方法的调用/类的生成都通过 Objective-C Runtime 在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法:
Class class = NSClassFromString("UIViewController")
id viewController = [[class alloc] init]
SEL selector = NSSelectorFromString("viewDidLoad")
[viewController performSelector:selector]
也可以替换某个类的方法为新的实现:
static void newViewDidLoad(id slf, SEL sel) {}
class_replaceMethod(class, selector, newViewDidLoad, @"")
还可以新注册一个类,为类添加方法:
Class cls = objc_allocateClassPair(superCls, "JPObject", 0)
objc_registerClassPair(cls)
class_addMethod(cls, selector, implement, typedesc)
对于 Objective-C 对象模型和动态消息发送的原理已有很多文章阐述得很详细,例如这篇,这里就不详细阐述了。理论上你可以在运行时通过类名/方法名调用到任何OC方法,替换任何类的实现以及新增任意类。所以 JSPatch 的原理就是:JS传递字符串给OC,OC通过 Runtime 接口调用和替换OC方法。这是最基础的原理,实际实现过程还有很多怪要打,接下来看看具体是怎样实现的。
方法调用
require('UIView')
var view = UIView.alloc().init()
view.setBackgroundColor(require('UIColor').grayColor())
view.setAlpha(0.5)
在WWDC2014大会之前是不允许使用动态库,在wwdc2014大会上,苹果对ios8开放了动态库挂载,通过动态库来实现热更新,现在貌似已经不能通过审核了,这里推介一个滴滴大神写的 JSpatch ,话不多说,直奔主题。
创建iOS动态库
打开Xcode,左上角选择File->New->Project...
编码工作,在这里我简单的写了一个MyTest的类,并写一个log方法
把你创建的MyTest类的.h 添加上去,方便后边引用只引用只一个类就行。
设置开放的头文件:Framework中有些类可能是一些私有的辅助工具,不需要使用者看到,在这里只需要把开放出去的类放到Public下, 如图把Project里的MyTest.h拖到Public里,Public都是对外暴露的.h文件
到这可以说一个简单的framework就已经完成了。但是,但是,但是我们要做一个高大上的通用动态库,不仅仅自己使用,可以分享给别人使用,逼格瞬间提升了好多,有木有!
制作通用动态库
那我们该怎样制作一个通用的动态库呢? 简单的方法是分别生成模拟器和真机上运行的库,然后在合并,这个方法,在每次生成动态库的时候,过程都会很繁琐,下面我们用一个脚本来自动完成它。Xcode 左上角Fiel ->New->Target...
脚本内容如下:
之后我们运行程序,需要注意的一点事,如果要支持64位,需要在编译选项中设置,如下:
到此时,我们的framework库文件就制作完成,在xcode的window->projects中选中我们的这个项目,点击进入文件夹的小箭头:
在build->product中便可以找到我们的framework文件,我们将其赋值出来即可以使用了
一个framework就制作完成了。
测试
新建个项目,添加刚才弄好的MyFirstFramework.Framework 。这里注意一下,在下图把MyFirstFramework.Framework添加上去,不然会运行崩溃。
我们引用一下,调用方法,可以使用。
**Xcode7 制作通用静态库 **
在上面的基础上只要修改一个参数即可生成静态库。
运行一下,复制出来就可以使用了。使用静态库的话,就可以把Framework从‘Embedded Binaries’中删除了。
runtime
概述: runtime又叫运行时,是一套底层C语言API,是iOS系统的核心之一。开发者在编码过程中,可以给任意一个对象发送消息,在编译阶段只是确定了要向接受着发送这条消息,而接受者如何响应和处理这条消息,就要看运行时来决定了
C语言中,在编译器就确定要调用哪个函数,而OC的函数,属于动态调用过程,在编译器并不能真正决定调用哪个函数,只有在真正的运行时才会根据函数的名称找到对应的函数来调用。OC是一个动态语言,这意味着它不仅要一个编译器,也需要一个运行时系统来动态创建类和对象、进行消息传递和发送
1.消息转发
Runtime的特性主要是消息传递,如果消息在对象中找不到,就进行转发。Objective-C是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态创建类和对象、进行消息传递和转发。Runtime的核心是消息传递。
(1)消息传递的过程
一个对象的方法[obj test],编译器转成消息发送objc_msgSend(obj,test),Runtime执行的流程是这样的
a.首先通过obj的isa指针找到它的class
b.在class的method list找test
c.如果class中没找到test,继续往它的superclass中找
d.一旦找到test这个函数,就去执行它的IMP
由于效率问题,每个消息都遍历一次objc_method_list并不合理,所以需要把经常被调用的函数缓存下来,去提高函数查询的效率。这也就是objc_class中另一个重要的成员objc_cache做的事情。找到test之后,将test的method_name作为key,method_imp作为value。当再次收到test消息的时候,可以直接在cache里找。
类对象(objc_class)
Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。struct objc_class结构体里面定义了很多变量。结构体里保存了指向父类的指针、类的名字(name)、版本、实例变量列表(ivars)、方法列表(methodLists)、缓存(cache)、遵守的协议列表(protocols),由此可见,类对象就是一个结构体struct objc_class,这个结构体存放的数据就是元数据
理解Runtime就是理解iOS在运行时他的数据存储以及他的类、实例、类对象、元类她们之间的关系和作用。
(2)消息转发机制
归根到底,Objective-C中所有的方法调用本质就是向对象发送消息
1.类中创建方法-(void)test
2.iOS系统为这个方法创建一个编号,SEL(test)并添加到方法列表里面
3.当调用这个方法的时候系统去方法列表里查找这个方法,找到了就执行
所以,调用一个方法就会进行一次发送消息也就是在这个类的方法列表里找,如果在该类中找不到就到该类的父类里找,如果父类还找不到就一直搜索到继承树的根部,如果找不到或者消息转发不成功那就会报unrecognized selector错。
1.动态方法解析
Objective-C运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:让你有机会提供一个函数实现,如果你添加了函数并且返回YES,那么运行时就会重新启动一次消息发送的过程。如下图:
虽然没有foo:的实现函数,但是通过class_addMethod()动态添加了fooMethod函数,并执行了这个函数并且打印成功。如果reslove返回NO运行时就会移到下一步:forwardingTargetSelector
2.直接消息转发
如果目标对象实现了forwardingTargetSelector,Runtime这时就会调用这个方法,给你把这个消息转发给其他对象的机会
从图中可以看出我们通过forwardingTargetForSelector方法将当前类的方法转给Father类实现了,打印成功。
3.完整消息转发
如果在上一步还不能处理未知消息,那唯一能做的就是启动消息转发机制。首先它会发送methodSignatureForSelector消息获得函数的参数和返回值类型。如果methodSignatureForSelector返回nil,Runtime则会发出doesNotRecognizeSelector。如果返回一个签名函数,Runtime就会创建一个NSInvocation对象并发送forwardInvocation消息给目标对象。
Runtime的实际应用
1.使用Runtime交换方法
2.动态添加方法(目前不是很懂)
3.给分类添加属性
4.消息转发(热更新)解决Bug(JSPatch)
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)