iOS底层原理iOS面试专题

小码哥底层原理笔记:OC对象的分类(二)

2020-04-29  本文已影响0人  chilim

前面一篇简单的介绍了OC对象的三种类型,instance实例对象,class类对象,meta-class元类对象。他们都有isa指针,那么isa指针指向哪里呢?

@interface Person : NSObject<NSCopying>
{
    @public
    int _age;
}
@property (nonatomic, assign) int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end

@implementation Person

- (void)personInstanceMethod{
}
+ (void)personClassMethod{
}
- (id)copyWithZone:(NSZone *)zone{
    return nil;
}
@end

@interface Student : Person<NSCopying>
{
    @public
    int _weight;
}
@property (nonatomic, assign) int _height;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end

@implementation Student
- (void)studentInstanceMethod{
}
+ (void)studentClassMethod{
}
- (id)copyWithZone:(NSZone *)zone{
    return nil;
}
@end

以上是一段简单示例代码。
从实际写代码中方法调用去讨论

Person *person = [[Person alloc] init];
person->_age = 10;
[person personInstanceMethod];
[Person personClassMethod];
//上面两个方法调用实际上是给对象发送消息
//objc_msgSend(Person, @selector(personClassMethod))//给类对象发送消息
//objc_msgSend(person, @selector(personInstanceMethod))//给实例对象发送消息

首先看实例对象方法的调用,当我们执行这段代码的时候,之前已经说了实例对象方法是放在class类对象里面的。所以当执行[person personInstanceMethod];的时候,我们首先会通过isa指针指向的Person类对象地址找到Person类对象,然后找到里面的实例方法。同理,类方法是保存在元类对象里面的,所以当执行[Person personClassMethod];的时候,通过类对象的isa指针指向的Person元类对象地址找到Person元类,然后找到里面的类方法。

基本上我们可以知道 instance实例对象的isa指针指向对应的class类对象,class类对象的isa指针指向对应的meta-class元类对象,所有元类对象的isa指针则指向基类NSObject的元类对象

如果我们调用一个不存在的实例方法呢,比如[person test];,那么调用流程是:首先给实例对象发送消息sendMsg,然后实例对象的isa指针找到其类对象,从而看有没有这个方法,如果没有,类对象会通过superClass指针找到父类的类对象,看父类的类对象有没有这个方法,一直找到NSObject的类对象,如果NSObject的类对象都没有这个方法,那么就会报错,unrecognized method。因为NSobject没有父类,superClass指向nil
同理如果调用一个不存在的类方法,比如[Person test];,那么调用流程是:通过class类对象的isa指针找到元类对象,看自己的元类对象里面有没有该方法,如果没有,元类对象就会通过superClass指针找到父类的元类对象,看父类的元类对象有没有,如果一直到NSObject的元类对象都没有找到,那么NSObject的superClass指针找到NSObject的类对象,如果类对象里面也没有,那么就会报错,unrecognized method。

基本上我们可以知道 class类对象的superClass指针指向其对应的父类的class类对象,因为NSObject没有父类,所以NSObject类对象的superClass指向nil,元类对象的superClass指针指向其对应父类的元类对象,但是注意:NSObject的元类对象的superClass指针却指向NSObject的类对象

代码验证:

struct mj_objc_class {
    Class isa;
    Class superClass;
};


int main(int argc, char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        //类对象
        Class personClass = [Person class];
        
        Class studentClass = [Student class];
        struct mj_objc_class *studentClass2 = (__bridge struct mj_objc_class *)(studentClass);
        NSLog(@"%p", studentClass);
        //personClass的地址是0x0000000100a89c68, 那么studentClass的superClass指向的父类的类对象地址是studentClass2->superClass = 0x0000000100a89c68和personClass类对象的地址是一样的
        
        //类型转换
        struct mj_objc_class *personClass2 = (__bridge struct mj_objc_class *)(personClass);
        //元类对象
        Class personmetaClass = object_getClass(personClass);
        
        NSLog(@"%p %p %p", person, personClass, personmetaClass);
        //(lldb) p/x person->isa
        //(Class) $1 = 0x000001a100ad9c41 Person
        //(lldb) p/x personClass
        //(Class) $2 = 0x0000000100ad9c40 Person
    //本来person->isa的值应该是personClass的地址的,但是在64位系统下person->isa要进行一次位运算&(ISA_MASK= 0x0000000ffffffff8)得到的才是personClass的地址,即:0x000001a100ad9c41 & 0x0000000ffffffff8 = 0x0000000100ad9c40,类对象和元类对象也是如此
        //superClass
    }
    return 0;
}

有趣的意外发生了,看如下代码:

@interface Person : NSObject

+ (void)test;

@end

@implementation Person

//+ (void)test{
//    NSLog(@"+[Person test] - %p", self);
//}

@end

@interface NSObject (Test)

+ (void)test;

@end

@implementation NSObject (Test)
//
//+ (void)test{
//    NSLog(@"+[NSObject test] - %p", self);
//}

- (void)test{
    NSLog(@"-[NSObject test] - %p", self);
}

@end

我们有一个Person类注释掉了里面的test类方法,新建了一个NSObject的分类,里面有个test实例方法,则当我们调用[Person test];时能不能调用成功,答案是可以的,并不会报错,运行发现最终我们是调用了NSObject里面的实例方法。为什么呢?
因为我们调用[Person test];的时候首先会通过Person类对象中的isa指针找到Person的元类对象,看里面有没有test类方法,发现没有,则Person元类对象会通过superClass指针找到其父类的元类对象,即NSObject元类对象,发现还是没有,则会通过NSObject元类的superClass指针找到NSObject的类对象,哎,发现里面又个test实例方法,所以就调用了这个test实例方法。如果我们把NSObject分类里面的实例方法也注释调,那么就会报找不到方法错误。

总结发现,不管是类方法还是实例方法,其调用流程是isa-> superClass -> superClass->.....->superClass == nil

面试题

1、对象的isa指针指向哪里?
答:instance对象的isa指向class对象、class对象的isa指向metaClass对象、metaClass对象的isa指向基类的metaClass对象

2、OC的类信息存放在哪里?
答:对象方法、属性、成员变量、协议信息、存放在class对象中,类方法存放在metaClass对象中,成员变量的具体值放在instance对象里面

上一篇下一篇

猜你喜欢

热点阅读