参考资料:Objective-C Runtime Programming Guide
OC 中执行方法的形式为:[receiver message]
,运行时绑定方法的具体实现。编译器将其转化为:objc_msgSend(receiver, selector, arg1, arg2, ...)
。调用实现方法时会多传入两个隐藏参数:
_cmd
: 对应方法的selector
self
: 调用方法的对象编译器为每个class & object
生成:
A pointer to the superclass.
A class dispatch table.
每项的内容是selector
以及对应的address
。利用- (IMP)methodForSelector:(SEL)aSelector
可以获得函数指针,typedef id (*IMP)(id, SEL, ...);
,在返回结果之后加()
可以执行。当一个实例对象被创建时,变量也会被初始化,在变量之上会有一个pointer
指向class structure
。
instance
和class structure
可以总结如下:
为了加速消息转发,runtime system
会对用到的SEL -> IMP
进行缓存。首先判断当前对象是否有对应的IMP
,其次向上以此判断父对象,如果都找不到,会在下面介绍的三种方法中进行动态决议。
通过resolveInstanceMethod: and resolveClassMethod:
动态的为类/实例添加方法而不用事先声明,如果命中了SEL
并且有对应的实现,就可以保证运行时调用未声明的方法而不会崩溃。由于Xcode编辑时会检查某个类有没有实现某个方法,所以为了保证编译通过,可用performSelector
执行方法。
#include
///
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
NSLog(@"%@", NSStringFromSelector(_cmd));
}
///
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(xxx)) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
如果在上一步没有动态添加成功,还可以通过forwardingTargetForSelector
将消息转发给其它对象执行,类似于多继承。如果返回nil
,则此方法行不通。切记不可放回self
,否则会造成死循环。
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%@ %@", self, NSStringFromSelector(_cmd));
return [xxx new];
}
上边的方法执行失败后,还有最后一种方法可以挽回局面。
-(void)forwardInvocation:(NSInvocation*)anInvocation {
NSLog(@"%@ %@", self, NSStringFromSelector(_cmd));
if ([xxx instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:[xxx new]];
} else {
[super forwardInvocation:anInvocation];
}
}
/*必须重写这个方法,消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象 返回nil上面方法不执行*/
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%@ %@", self, NSStringFromSelector(_cmd));
NSMethodSignature*signature = [super methodSignatureForSelector:aSelector];
if(!signature){
if ([xxx instancesRespondToSelector:aSelector]){
signature = [xxx instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
这种方法非常灵活,可以动态改变方法的target、selector、arguments、return
,当然也是最耗费性能的。
值得注意的是如果调用if ([xxx respondsToSelector:@selector(xxx)])
进行判断是否响应某消息,最多进行到Dynamic Method Resolution
,如果找不到就认为不响应。
以上三种方法是依次执行的,越靠前解决越好,否则耗费更多的性能。
通过以下方法可以获取一个类的属性名称以及修饰符:
id XXXClass = objc_getClass("XXX");
unsigned int outCount, i;
// 类似于数组指针的用法
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}
其中property_getAttributes
获取到的结果是用符号来表示的,比如@property (nonatomic, copy) NSArray *array;
,运行的结果是:
T@"NSArray",C,N,V_array
,T
表示编码类型,C
表示copy
,N
表示nonatomic
,V_
后面就是属性的命名。
其它符号的含义可见