参考资料:Objective-C Runtime Programming Guide
1、Messaging
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 ,其次向上以此判断父对象,如果都找不到,会在下面介绍的三种方法中进行动态决议。
2、Dynamic Method Resolution
通过resolveInstanceMethod: and resolveClassMethod: 动态的为类/实例添加方法而不用事先声明,如果命中了SEL 并且有对应的实现,就可以保证运行时调用未声明的方法而不会崩溃。由于Xcode编辑时会检查某个类有没有实现某个方法,所以为了保证编译通过,可用performSelector 执行方法。
#include <objc/runtime.h>
void dynamicMethodIMP(id self, SEL _cmd) {
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];
}
3、Forwarding
如果在上一步没有动态添加成功,还可以通过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];
}
}
- (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 ,如果找不到就认为不响应。
以上三种方法是依次执行的,越靠前解决越好,否则耗费更多的性能。
4、Declared Properties
通过以下方法可以获取一个类的属性名称以及修饰符:
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_ 后面就是属性的命名。
其它符号的含义可见 👉
|