Cocoa 消息机制 (Objective-C 反射相关)
Cocoa 消息机制 (Objective-C 反射相关)
相关概念
动态语言
程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。
Object-C 是一门动态语言
Java反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
JAVA反射(放射)机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。但是JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
Cocoa 消息机制
基本反射
Class 相关的操作
- (Class)class :NSObject 类方法,获取类
- (Class)class :NSObject 协议方法,获取类
- Class NSClassFromString ( NSString *aClassName );
- NSString * NSStringFromClass ( Class aClass );
Selector 相关操作
相当于 Java 中的 Method,不过在 Mac 中,SEL是一个单例,即[Foo count]和[Bar count] 里面的count 指向的是同一个指针
- SEL NSSelectorFromString ( NSString *aSelectorName );
- NSString * NSStringFromSelector ( SEL aSelector );
- (BOOL)respondsToSelector:(SEL)aSelector 是否响应某个消息
- 一系列的performSelector: 对其发送消息
- (IMP)methodForSelector:(SEL)aSelector 根据 Selector 获取 IMP
Protocal 相关操作
- NSString * NSStringFromProtocol ( Protocol *proto );
- Protocol * NSProtocolFromString ( NSString *namestr );
- (BOOL)conformsToProtocol:(Protocol *)aProtocol 是否遵循协议
继承关系
- (BOOL)isKindOfClass:(Class)aClass 相当于 instanceOf
- (BOOL)isMemberOfClass:(Class)aClass 只判断是不是这个类
- (BOOL)isSubclassOfClass:(Class)aClass 相当于 isKindOfClass
核心结构
Object & Class
每一个类实例对象的第一个实例变量是一个指向该对象的类结构的指针,叫做isa。
[self class];(NSObject 协议) 就是返回这个 isa;
Class 是指向类结构体的指针,该类结构体含有一个指向其父类类结构的指针,该类方法的链表,该类方法的缓存以及其他必要信息。
[NSObject class];(NSObject 类) 大概返回的就是 Class 中的 isa;
objc 对象分为三层:
instance -> class -> metaclass
(箭头代表 isa 指向,最终 isa 指向 root metaclass)
当然,继承关系后两层是并行的,比如 A B 对象,A 类继承 B 类,则 A 类的 metaclass 继承 B 类的 metaclass.
/// Represents an instance of a class.
struct objc_object {
Class isa;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
struct objc_class {
Class isa;
/* 以下是 Objc 1.0代码, 可能2.0 的实现不同吧 */
Class super_class; /*父类*/
const char *name; /*类名字*/
long version; /*版本信息*/
long info; /*类信息*/
long instance_size; /*实例大小*/
struct objc_ivar_list *ivars; /*实例参数链表*/
struct objc_method_list **methodLists; /*方法链表*/
struct objc_cache *cache; /*方法缓存*/
struct objc_protocol_list *protocols; /*协议链表*/
};
/* Use `Class` instead of `struct objc_class *` */
Method & SEL & IMP
SEL在上面已经介绍了实际上他就是等价于方法的名字
Method就是方法 实际上他包含了SEL和IMP 不同于SEL它是有宿主的,并不是单例
而IMP实际就是方法的真正实现了
如果要做动态方法解析 那么就可以自己作IMP来转换SEL对于的实现
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
typedef struct objc_method *Method;
struct objc_method {
SEL method_name; /*方法名*/
char *method_types; /*各参数和返回值类型*/
IMP method_imp; /*指向该方法的具体实现的函数指针*/
}
struct objc_method_list {
struct objc_method_list *obsolete;
int method_count;
#ifdef __LP64__
int space;
#endif
/* variable length structure */
struct objc_method method_list[1];
}
typedef id (*IMP)(id, SEL, ...);
根据前面id 的定义,我们知道 id是一个指向 objc_object 结构体的指针,该结构体只有一个成员isa,所以任何继承自 NSObject 的类对象都可以用id 来指代,因为 NSObject 的第一个成员实例就是isa。
至此,我们就很清楚地知道 IMP 的含义:IMP 是一个函数指针,这个被指向的函数包含一个接收消息的对象id(self 指针), 调用方法的选标 SEL (方法名),以及不定个数的方法参数,并返回一个id。也就是说 IMP 是消息最终调用的执行代码,是方法真正的实现代码 。我们可以像在C语言里面一样使用这个函数指针。
void (*setter)(id, SEL, BOOL);
setter = (void(*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
for (int i = 0; i < 1000; i++) {
setter(targetList[i], @selector(setFilled:), YES);
}
ivar & property
ivar就是定义的变量,而property就是属性了
这里要注意的就是取出一个class的ivar/property 用到的类似函数
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
注意到它是copy的,也就是说这块内存是copy 你得自己负责最后去
消息机制
objc_msgSend
id objc_msgSend ( id self, SEL op, ... );
Sends a message with a simple return value to an instance of a class.
When it encounters a method call, the compiler generates a call to one of the functions objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, or objc_msgSendSuper_stret. Messages sent to an object’s superclass (using the super keyword) are sent using objc_msgSendSuper; other messages are sent using objc_msgSend. Methods that have data structures as return values are sent using objc_msgSendSuper_stret and objc_msgSend_stret.
该消息函数做了动态绑定所需要的一切工作:
- 它首先找到 SEL 对应的方法实现 IMP。因为不同的类对同一方法可能会有不同的实现,所以找到的方法实现依赖于消息接收者的类型。
- 然后将消息接收者对象(指向消息接收者对象的指针)以及方法中指定的参数传递给方法实现 IMP。
- 最后,将方法实现的返回值作为该函数的返回值返回。
查找 IMP 的过程
- 首先去该类的方法缓存 (在 objc_class 结构中查看cache) 中查找,如果找到了就返回它;
- 如果没有找到,就去该类的方法列表(在 objc_class 结构中查看methodLists)中查找。如果在该类的方法列表中找到了,则将 IMP返回,并将它加入方法缓存中缓存起来(根据最近使用原则,这个方法再次调用的可能性很大,缓存起来可以节省下次调用再次查找的开销)。
- 如果在该类的方法列表中没找到对应的 IMP,在通过该类结构中的 super_class指针在其父类结构的方法列表中去查找,直到在某个父类的方法列表中找到对应的IMP,返回它,并加入cache中;
- 如果在自身以及所有父类的方法列表中都没有找到对应的 IMP,则看是不是可以进行动态方法决议;
- 如果动态方法决议没能解决问题,进入消息转发流程。
动态方法决议 && 消息转发
当调用一个 NSObject 对象不存在的方法时,并不会马上抛出异常,而是会经过多层转发,层层调用对象的 -resolveInstanceMethod:, -forwardingTargetForSelector:, -methodSignatureForSelector:, -forwardInvocation: 等方法
消息转发流程图消息转发过程的关键方法
-
动态方法决议
向当前类发送resolveInstanceMethod:消息,检查是否动态向类添加了方法,如果返回YES,则系统认为方法已经被添加,则会重新发送消息。
-
快速消息转发
检查当前类是否实现forwardingTargetForSelector:方法,若实现则调用,如果方法返回值为非nil或非self的对象,则向返回的对象重新发送消息。
-
标准消息转发
Runtime发送methodSignatureForSelector:消息获取selector对应方法的签名,如果有方法签名返回,则根据方法签名创建描述消息的NSInvocation,向当前对象发送forwardInvocation:消息,如果没有方法签名返回,即返回值为nil,则向当前对象发送doesNotRecognizeSelector:消息,应用崩溃退出。
要转发消息给其它对象,forwardInvocation:方法所必须做的有:
- 决定将消息转发给谁,并且
- 将消息和原来的参数一块转发出去。
forwardInvocation:方法就像一个不能识别的消息的分发中心,将这些消息转发给不同接收对象。或者它也可以象一个运输站将所有的消息都发送给同一个接收对象。它可以将一个消息翻译成另外一个消息,或者简单的"吃掉“某些消息,因此没有响应也没有错误。forwardInvocation:方法也可以对不同的消息提供同样的响应,这一切都取决于方法的具体实现。该方法所提供是将不同的对象链接到消息链的能力。
注意:
- forwardInvocation:方法只有在消息接收对象中无法正常响应消息时才会被调用。
- 实现forwardInvocation:方法时,不用调用super forwardInvocation:方法,否则,应用仍然会崩溃。