Objective-C Runtime 的一些理解
- 1. [ ] 表示发送消息
[receiver message] 会被编译器转化为:
objc_msgSend(receiver, selector)
如果含有参数
objc_msgSend(receiver, selector, arg1, arg2, ...)
- 2. Runtime 术语
实例对象有个isa的属性,指向Class, 而Class里也有个isa的属性, 指向meteClass. 这里就有个点, 在Objective-C中任何的类定义都是对象,他们的关系如下图:
- 获取属性列表:(指针的指针)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) // 类中 objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount) // 协议中
- 获取属性名:
const char *property_getName(objc_property_t property)
- 获取属性:
const char *property_getAttributes(objc_property_t property)
例如:
/*----------------------------------------------------------------------------------------------*/ @interface Leader : NSObject { float alone; } @property float alone; @end // 获取类中的属性列 - (void)getPropertys() { id LenderClass = objc_getClass("Leader"); unsigned int outCount; objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount); for(int i=0; i<outCount; i++) { objc_property_t property = properties[i]; fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property)); } } }
- objc_msgSend 函数
发送消息时,编译器会根据情况在下面4个函数中选一个来调用。
objc_msgSend: 消息返回值为简单值
objc_msgSend_stret: 消息返回值为 struct
objc_msgSendSuper: 消息发送给父类
objc_msgSendSuper_stret
- 消息发送步骤
1. 检测这个 selector 是不是要忽略的。比如有了垃圾回收就不理会 retain, release 这些函数了。
2. 检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。
3. 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。
4. 如果 cache 找不到就找一下方法分发表。
5. 如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。
6. 如果还找不到就要开始进入动态方法解析:
6.1 是否实现了 resolveInstanceMethod,是则执行这里定位的方法
6.2 是否实现了 forwardingTargetForSelector,是则执行这里定位的方法
6.3 是否实现了 forwardInvocation,是则执行这里定位的方法
6.4 都没有实现,返回 message not handle 消息。
动态方法解析函数执行流程如下:
- self 如何取到调用方法的对象
self 为方法中的隐含参数,self 是在方法运行时被偷偷的动态传入的。
当objc_msgSend找到方法对应的实现时,它将直接调用该方法实现,
并将消息中所有的参数都传递给方法实现,同时,它还将传递两个隐藏的参数:
1. 接收消息的对象:即 self 指向的内容
2. 方法选择器:即 _cmd 指向的内容
- 动态方法解析
我们可以通过分别重载 resolveInstanceMethod: 和 resolveClassMethod: 方法分别添加实例方法实现和类方法实现。
因为当 Runtime 系统在 Cache 和方法分发表中(包括超类)找不到要执行的方法时,Runtime 会调用 resolveInstanceMethod:
或 resolveClassMethod: 来给程序员一次动态添加方法实现的机会。我们需要用 class_addMethod 函数完成向特定类添加特定
方法实现的操作:
void dynamicMethodIMP(id self, SEL _cmd) { // implementation .... } @implementation myClass + (BOOL) resolveInstanceMethod:(SEL)aSEL { if (aSEL == @selector(resolveThisMethodDynamically)) { class_addMethod([self class], aSEL, (IMP)dynamicMethodIMP, "v@:") } } @end
- 重定向
在消息转发机制执行前,Runtime 系统会再给我们一次偷梁换柱的机会,
即通过重载- (id)forwardingTargetForSelector:(SEL)aSelector方法替换消息的接受者为其他对象
- (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(myMethod)) { return alternateObject; } return [super forwardingTargetForSelector: aSelector]; }
- 转发
通过重写 forwardInvocation: 方法实现。
1. 在 forwardInvocation: 消息发送前,Runtime系统会向对象发送 methodSignatureForSelector: 消息,
并取到返回的方法签名用于生成NSInvocation对象。所以我们在重写forwardInvocation:的同时也要重写
methodSignatureForSelector: 方法,否则会抛异常。
2. 当一个对象由于没有相应的方法实现而无法响应某消息时,运行时系统将通过 forwardInvocation: 消息通知该对象。
每个对象都从 NSObject 类中继承了 forwardInvocation: 方法。然而,NSObject 中的方法实现只是简单地调用了
doesNotRecognizeSelector:。通过实现我们自己的forwardInvocation:方法,我们可以在该方法实现中将消息转发给其它对象。
3. forwardInvocation: 方法就像一个 不能识别的消息 的分发中心,将这些消息转发给不同接收对象。
或者它也可以象一个运输站将所有的消息都发送给同一个接收对象。它可以将一个消息翻译成另外一个消息,
或者简单的”吃掉“某些消息,因此没有响应也没有错误。forwardInvocation: 方法也可以对不同的消息提供同样的响应,
这一切都取决于方法的具体实现。该方法所提供是将不同的对象链接到消息链的能力。‘
- (void)forwardInvocation:(NSInvocation *)anInvocation { if ([someOtherObject respondsToSelector:[anInvocation selector]]) { [anInvocation invokeWithTarget:someOtherObject]; } else { [super forwardInvocation: anInvocation]; } }
- 转发与继承的区别与联系
联系:
1. OC 没有多继承,利用消息转发可以实现多继承的功能。
例如:A 继承了 B 和 C,(A : B , C)那么 A 就同时拥有了 B 和 C 中的方法。
A 将消息转发给 B 和 C 也能实现同样的功能。
一个对象把消息转发出去,就好似它把另一个对象中的方法借过来或是“继承”过来一样。
区别:
1. 像 respondsToSelector: 和 isKindOfClass: 这类方法只会考虑继承体系,不会考虑转发链。
- Objective-C Associated Objects
在 OS X 10.6 之后,Runtime系统让Objc支持向对象动态添加变量。涉及到的函数有以下三个:
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); id objc_getAssociatedObject(id object, const void *key); void objc_removeAssociatedObjects(id object);
其中关联政策是一组枚举常量, 这些常量对应着引用关联值的政策,也就是 Objc 内存管理的引用计数机制。
enum { OBJC_ASSOCIATION_ASSIGN = 0, OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403 };
参考:
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/index.html
http://blog.csdn.net/uxyheaven/article/details/38113901
http://southpeak.github.io/blog/2014/10/25/objective-c-runtime-yun-xing-shi-zhi-lei-yu-dui-xiang/