概述:
objc的runtime对C语言的结构体和函数进行封装,使得开发者在运行的时候能够检查,创建或者修改类或者协议的属性,成员变量和相关函数。
除了能在运行时修改类或者协议的相关细节之外,消息机制和消息转发把对消息的相应处理延迟到运行时,这样使得我们能够对消息进行相应的拦截,能够对消息的处理进行运行时的重定义。消息机制跟其他语言的方法调用的最大的区别就是,消息的处理过程是在运行时候确定的,所以我们可以尝试给一个对象发送一个可能并不存在的消息,而在运行时对此消息进行特殊的处理。而方法调用的方法的处理过程是在编译的时候确定的,调用一个并不存在的方法,在编译的时候就没法通过。我们一般能够利用runtime做些什么呢?
1.遍历对象的属性、方法、协议、实例变量。
2.动态添加/修改属性,动态添加、修改、替换方法。
3.动态创建类、对象、协议等。
4.方法拦截调用。
(一) 在实际的编程过程中,跟runtime进行交互一般有三种情况
1 Objective_C编码:
在OC代码被编译的过程中,runtime系统做了很多的工作。运行时系统动态构建了类的结构,包括属性,协议,方法列表等。
2 NSObject的一些方法
有些方法是直接查询运行时系统的。比如isKindOfClass:、isMemberOfClass:、respondToSelector:、methodForSelector:。
3 RunTime函数
通过runtime的函数库进行交互。
(二) 关于消息机制
OC的”方法调用” [obj method],其实是消息机制,方法调用将会被转化成objc_msgSend函数。objc_msgSend(receiver, selector, arg1, arg2)
当一个对象收到一个消息的时候,将会根据他的isa直接从所属的类的dispatch table中搜索所需的方法,如果找不到就继续往这个对象的superclass中寻找。如果一直找到NSObject还是找不到就会触发消息转发机制。
(三)动态方式的解决方案
(四)消息转发机制
1. 动态方法解析:
+ resolveInstanceMethod:(Method *instanceMethod)
+ resolveClassMethod:(Method *classMethod)
2.备用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector;
3.完整消息转发
- (void)forwardingInvocation:(NSInvocation *)aInvocation;
(五)对于类信息的获取
1. 成员变量操作相关函数
Ivar *ivars = class_copyIvarList(cls, &ivarCount);
const char *name = ivar_getName(ivar)
ptrdiff_t offset = ivar_getOffset(ivar)
const char *typeEncoding = ivar_getTypeEncoding(ivar)
2. 属性操作相关函数
objc_property_t *properties = class_copyPropertyList(aClass, &outCount);
const char *name = property_getName(objc_property_t property)
objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount)
const char *typeEncoding = attrs[i].value
3. 方法操作相关函数
Method *methods = class_copyMethodList(cls, &methodCount)
SEL sel = method_getName(Method *method)
IMP imp = method_getImplementation(Method *method)
const char * encoding = method_getTypeEncoding(Method *method)
char * returnType = method_copyReturnType(Method *method)
unsigned int counter = method_getNumberOfArguments(Method *method)
char *type = method_copyArgumentType(method, i)
method_exchangeImplementations(Method m1, Method m2)
4. 类操作相关函数
Class superCls = class_getSuperclass(cls)
Bool isMeta = class_isMetaClass(cls)
Class metaCls = objc_getMetaClass(class_getName(cls))
Class cls = class_getClass(Class cls)
Method method = class_getInstanceMethod(Class cls, SEL sel)
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
5. 关联对象
/*关联对象*/
1) objc_setAssociatedObject(id obj, const void *key, objc_AssociationPolicy policy)
/*获取关联对象*/
2) id objc_getAssociatedObject(id obj, const void *key)
/*移除关联的对象*/
3) void objc_removeAssociatedObjects(id obj)
参数说明:
id object: 被关联的对象
const void *key: 关联的key,要求唯一
id value: 关联的对象
objc_AssociationPolicy policy: 内存管理的策略
objc_AssociationPolicy policy的enum值有:
当对象被释放时,会根据这个策略来决定是否释放关联的对象,当策略是RETAIN/COPY时,会释放关联的对象,当是ASSIGN,将不会释放。
值得注意的是,我们不需要主动调用removeAssociated来接触关联的对象,如果需要解除指定的对象,可以使用setAssociatedObject置nil来实现。
(六) Type Encoding
为了跟runtime系统交互,编译器对每个方法的返回值、参数还有方法选择器的本身都进行了一段字符说明,这就是它们相应的type conding string.为了获取这些对象的type encoding string,我们有很多方法来获取,一个是通过类似ivar_getTypeEncoding的runtime api。另一种是通过@encode(type)来获取,类似char *buf1 = @encode(int **), 凡是能够通过sizeOf(type)获取内存占用空间的type,都能够通过@encode进行编码的获取。
类型:
r const
n in
N inOut
o out
O copy
R refer
V oneWay
v void
B Bool
c int8/char
C uint8/
s int16
S uint16
i int32
I uint32
l int32
L uint32
q int64
Q uint64
f float
d double
D long double
# class
: SEL
* cstring/char *
^ pointer
[array type] cArray
(name=type…)union
{name=type…} struct
@ block/object
属性修饰:
V instance variable
R readonly
C copy
& retain
N noatomic
D dynamic
W weak
G getter
S setter
(七)Method Swizzling
+ (void)swizzleMethods:(Class)class originalSelector:(SEL)origSel swizzledSelector:(SEL)swizSel {
Method origMethod = class_getInstanceMethod(class, origSel);
Method swizMethod = class_getInstanceMethod(class, swizSel);
BOOL didAddMethod = class_addMethod(class, origSel, method_getImplementation(swizMethod), method_getTypeEncoding(swizMethod));
if (didAddMethod) {
class_replaceMethod(class, swizSel, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, swizMethod);
}
}
(八)消息转发流程
当向某个对象发生一条消息的时候,若该对象的方法列表和它相应的继承链上的方法列表都无法找到以该消息选择子作为key的实现方法时,就会触发消息转发流程。
http://www.cocoachina.com/ios/20151015/13769.html
1. 动态方法解析:
+ (BOOL)resolveInstanceMethod:(SEL)sel //实例方法
+ (BOOL)resolveClassMethod:(SEL)sel //类方法
消息转发流程开始,先询问这两个方法,是否要动态添加对应的选择子的实现方法。如果用户在该函数中动态添加了相应方法的实现,则跳到方法的实现部分,并将实现加入缓存。以供下次使用。
2. 备用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector
如果在消息转发的第一步没有找到对应方法的实现,当前接收者有第二次机会,可以制定一个可能处理此选择子的备用接收者对象。runtime会根据返回的对象进行查找,如果查到了就跳转到对应的实现,消息转发结束。
3. 完整消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector;
如果没有找到备用接收者,那么就来到了完整的消息转发机制。该方法可以改变消息调用目标,运行时系统根据所改变的调用目标,向调用方法列表中查询对应方法的实现并实现跳转。最后,如果消息转发第三步还没处理该选择子,nsobject将会抛出doesNotRecognizeSelector:(SEL)aSelector异常。