Objective-C Runtime Programming

Objective-C Runtime Programming,第1张

参考资料:Objective-C Runtime Programming Guide

1、Messaging

OC 中执行方法的形式为:[receiver message],运行时绑定方法的具体实现。编译器将其转化为:objc_msgSend(receiver, selector, arg1, arg2, ...)。调用实现方法时会多传入两个隐藏参数:

_cmd: 对应方法的selectorself: 调用方法的对象

编译器为每个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

instanceclass structure可以总结如下:

为了加速消息转发,runtime system会对用到的SEL -> IMP进行缓存。首先判断当前对象是否有对应的IMP,其次向上以此判断父对象,如果都找不到,会在下面介绍的三种方法中进行动态决议。

2、Dynamic Method Resolution

通过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];
}
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];
    }
}

/*必须重写这个方法,消息转发机制使用从这个方法中获取的信息来创建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,如果找不到就认为不响应。

以上三种方法是依次执行的,越靠前解决越好,否则耗费更多的性能。

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_arrayT 表示编码类型,C表示copyN表示nonatomicV_后面就是属性的命名。

其它符号的含义可见 👉

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/993157.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存