前言:
最初对于runtime的了解其实只停留在,知道这是一组C的方法,知道消息机制中会把方法调用转成objc_msgSend(theObject,@selector(objectMethod))。随后有一个具体的了解得益于一次尝试,使用runtime解决按钮连续点击限制;这个例子网上有一堆,就不再赘述,但是不得不说,runtime的基本使用都很巧妙的包含其中。也因此,我对runtime的理解更深入了;随后看了runtime的官方文档,知道了原来方法还有很多,但至此,依然不能灵活的运用。真正让我震撼的,是在敲MJExtension的时候runtime的使用灵活而随性,在我看来已经是出神入化(也可能我还没有理解的那么深刻),确实从中学习到很多。
需要知道的概念:
isa指针:
isa指针是每一个对象都会有的,指向这个对象所对应的类,在此要能够区分类和对象的不同;
SEL:
SEL相当于对方法的进一步封装,也可以当做是一个描述,每一个方法都有一个与之对应的SEL类型的数据,根据一个SEL可以找到方法地址,进而调用方法;
其类型定义为:typedef struct object_selector *SEL;
相关使用:SEL sel_1 = @selector(myMethodName:); //把一个方法名包装为一个SEL对象
NSString *str = NSStringFromSelector(sel_1); //得到一个SEL对象的名字;
[object performSelector:sel_1]; //performSelector中其实一直都在使用SEL对象;
Method:
Method是在方法链表中的存在形式,其中有一个SEL类型的selector和一个IMP类型的address,具体来说,就是一个对象的isa指针,指向对象所对应的类结构,而类结构中的参数包括父类、类名、版本信息、类信息、实例大小、实例参数链表、方法链表、方法缓存、协议链表;在方法链表中存储的就是Method;
struct objc_class { Class isa OBJC_ISA_AVAILABILITY; //isa指针 #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; //父类 const char *name OBJC2_UNAVAILABLE; //类名 long version OBJC2_UNAVAILABLE;//版本信息 long info OBJC2_UNAVAILABLE;//类信息 long instance_size OBJC2_UNAVAILABLE;//实例大小 struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;//实例参数链表 struct objc_method_list **methodLists OBJC2_UNAVAILABLE;//方法链表 struct objc_cache *cache OBJC2_UNAVAILABLE;//方法缓存 struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;//协议链表 #endif } OBJC2_UNAVAILABLE;
相关方法:class_getInstanceMethod(Class cls, SEL name) //返回SEL对应的Method对象
IMP:
IMP是指implementation的缩写,是指向方法地址的指针,每一个方法都有一个IMP地址,要注意,并不是只有属性才有地址,方法也有;
相关方法:method_getImplementation(Method m); //runtime中的方法,返回该方法的IMP类型的方法实现地址;
[object methodForSelector:object_SEL]; //对象可以调用这个封装好的方法直接获得IMP指针;
[class instanceMethodForSelector:object_SEL]; //上一个方法是实例方法,遍历实例的方法列表返回对应IMP,这个方法是一个类方法,区别在于遍历的是类的方法列表;其实具体实现是一样的;都是通过class_getInstanceMethod进行;
class cache:
分发表:
下面列出一些基本使用;
1、动态操作属性、方法;
属性:
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
object:表示给那个对象添加属性;
key:属性对应的key值,再添加之后使用这个key来获取属性;
value: 属性值;
policy:策略枚举;
理解:这个方法可以在你的复制方法中使用,赋值的时候直接关联一个新的属性,使用runtime写在方法内部,但是从表面来看抽象出的方法依然只完成了对于属性赋值,只不过内部的实现不再是 class.aaa = value 这样的语句而已;同时,如果在添加过属性关联之后,想要断开某个属性的关联,直接value传nil即可;
objc_getAssociatedObject(id object, const void *key)
object: 要取的是哪个对象的属性;
key:属性对应的key值;
理解:同上,和普通取值有所不同,但抽象之后依然只是取值,值得注意的是,返回值为id类型;
objc_removeAssociatedObjects(id object)
object:断开所有属性关联的对象;
理解:其实从方法名也可以理解,这个方法用于断开所有关联的属性,会把对象恢复到原始状态;
runtime属性的总结:其实动态的操作属性,就是在进行关联,关联本身就是runtime实现的一部分;我不需要修改类的相关定义,在运行过程中,基于关键字的方式给这个对象增加存储空间;
方法:
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
cls:给哪个类添加方法;
name: 原有方法的SEL,注意类型为SEL,你希望调用的方法名
imp:新的方法的实现地址,当调用了这个name的方法时,你希望他执行哪一个方法地址;
types:方法描述;
理解:向一个类增加一个方法实现,调用的时候其实还是老的方法名,但是执行的是新的方法实现过程,返回一个BOOL值,表示是否添加成功,其中最后一个参数表示方法的参数以及返回值描述;可使用method_getTypeEncoding(Method m)获得;其实这个方法包含了很多信息,有一个思考点,我们在代码中写了一个方法,但是内部是如何保存的呢?为什么就知道这个方法需要什么参数、什么样的返回值呢?其实这个方法已经比较隐含的表示了这个问题。
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
理解:替换方法,参数类型同上;
method_exchangeImplementations(Method m1, Method m2)
理解:直接交换两个方法的实现地址,即,当你调用方法m1是,其实对应的是m2的智行过程,反之依然,简单粗暴而有效;
runtime方法小结:方法的使用一般都放在category里,另外OC中runtime会自动加载load方法,但在Swift中没有实现load方法,可以使用初始化方法;
转载请注明出处,有问题欢迎指出。