美文网首页
Object-C 方法查找原理(对象、isa和SuperClas

Object-C 方法查找原理(对象、isa和SuperClas

作者: 9d8c8692519b | 来源:发表于2019-01-24 15:05 被阅读7次

    Q:OC中对象分类

    A:总共为三类:实例对象、类对象、元类对象

    • instance对象(实例对象)
    • class对象(类对象)
    • meta-class对象(元类对象)

    实例对象(instance)

    • instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象
    Student *p1 = [[Student alloc] init];
    Student *p2 = [[Student alloc] init];
    
    
    • p1、p2是Student的instance对象(实例对象),它们是不同的两个对象,分别占据着两块不同的内存

      image

    instance对象在内存中存储的信息包括

    • isa指针
    • 其他成员变量的值
    • 实例对象不包含实现方法

    类对象(Class)

    NSObject *obj1 = [[NSObject alloc] init];
    Class objClass1 = [obj1 class];
    Class objClass2 = [NSObject class];
    //class 方法返回的一直是class对象,类对象,而不是元类对象
    Class objClass3 = [[NSObject class] class];
    Class objClass4 = object_getClass(obj1);
    NSLog(@"%p-%p-%p-%p",objClass1,objClass2,objClass3,objClass4);
    
    
    • 上述都是同一个类对象,打印出的地址相同,每个类在内存中有且只有一个class对象

    class对象在内存中存储的信息包括

    • isa指针
    • superclass指针
    • 类的属性信息(@property)
    • 类的对象方法信息(instance method)
    • 类的协议信息(protocol)
    • 类的成员变量信息(ivar)(成员变量类型、名称等等,不是成员变量的值)
    • ......

    元类对象(meta-class)

    Class metaClass = object_getClass([NSObject class]);
    
    
    • 每个类在内存中有且只有一个meta-class对象
    • meta-class对象和class对象的内存结构是一样的,但是用途不一样

    在内存中存储的信息主要包括

    • isa指针
    • superclass指针
    • 类的类方法信息(class method)
    • ......
    方法说明

    1.Class objc_getClass(const char *aClassName)

    • 1> 传入字符串类名
    • 2> 返回对应的类对象

    2.Class object_getClass(id obj)

    • 1> 传入的obj可能是instance对象、class对象、meta-class对象
    • 2> 返回值
      a) 如果是instance对象,返回class对象
      b) 如果是class对象,返回meta-class对象
      c) 如果是meta-class对象,返回NSObject(基类)的meta-class对象

    Q:实例对象、类对象isa指向

    A:instance的isa指向class,class的isa指向meta-class

    • 当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用

    • 当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用

      image

    Q:对象的isa指针指向哪里?

    A:根据不同类型的对象来回答。

    • instance对象的isa指向class对象
    • class对象的isa指向meta-class对象
    • meta-class对象的isa指向基类的meta-class对象

    Q:OC的类信息存放在哪里?

    A:类的内容分别作答。

    • 对象方法、属性、成员变量、协议信息,存放在class对象中
    • 类方法,存放在meta-class对象中
    • 成员变量的具体值,存放在instance对象

    Q问题描述:Student类继承自Person;Person继承自NSObject

    示例代码:

    @interface Person : NSObject<NSCopying>
    {
        int _age;
        int _name;
        int _bro;
    }
    - (void)personInstanceMethod;
    + (void)personClassMethod;
    @end
    
    @implementation Person
    - (void)personInstanceMethod{
    
    }
    + (void)personClassMethod{
    
    }
    - (id)copyWithZone:(NSZone *)zone{
        return nil;
    }
    @end
    
    @interface Student : Person<NSCopying>
    {
        int _dog;
        int _cat;
        int _book;
    
        int _phone;
        int _bicycle;
    
        int _hat;
        int _clothes;
        int _pants;
    }
    - (void)studentInstanceMethod;
    + (void)studentClassMethod;
    @end
    
    @implementation Student
    - (void)studentInstanceMethod{}
    + (void)studentClassMethod{}
    @end
    
    
    q1:上述代码各类对象superclass指向?
    a1:各类的superclass指向父类对象

    类对象的superclass指针指向父类对象

    image
    q2:Student实例对象调用Student对象方法的调用流程?

    示例代码:

    Student *stu = [[Student alloc] init];
    [stu studentInstanceMethod];
    
    

    a2:stu实例对象isa找到Student的类对象,调用对象方法

    image
    q3:Student实例对象调用Person对象方法的调用流程?

    示例代码:

    Student *stu = [[Student alloc] init];
    [stu personInstanceMethod];
    
    

    a3:stu实例对象isa找到Student的类对象,通过Student的superclass找到Person的类对象,最后调用Person的对象方法实现。

    image
    q4:Student实例对象调用init方法的调用流程?

    示例代码:

    Student *stu = [[Student alloc] init];
    [stu init];
    
    

    a3:调用流程

    • 1.stu实例对象isa找到Student的类对象
    • 2.通过Student的superclass找到Person的类对象
    • 3.通过Person的superclass找到NSObject的类对象
    • 4.最后调用NSObject的init方法实现。

    Q:Student类继承自Person;Person继承自NSObject,各类元类的superclass指针指向?

    A:Student元类superclass指向Person的元类,Person元类superclass指向NSObject的元类

    image

    q1:参考上述示例,[Student studentClassMethod] 调用流程
    a1:Student类isa指针找到 Student元类,然后调用类方法 studentClassMethod

    q2:参考上述示例,[Student personClassMethod] 调用流程
    a3:Student类isa指针找到 Student元类,Student元类的superclass找到Person元类,再调用personClassMethod

    q2:参考上述示例,[Student load] 调用流程
    a3:Student类isa指针找到 Student元类,Student元类的superclass找到Person元类,Person元类的superclass找到NSObject元类,再调用load

    isa、superclass 小结

    • instance的isa指向class
    • class的isa指向meta-class
    • meta-class的isa指向基类的meta-class
    • class的superclass指向父类的class,如果没有父类,superclass指针为nil
    • meta-class的superclass指向父类的meta-class;基类的meta-class的superclass指向基类的class
    • instance调用对象方法的轨迹;isa找到class,方法不存在,就通过superclass找父类
    • class调用类方法的轨迹;isa找meta-class,方法不存在,就通过superclass找父类
    image

    上述图片备注:isa指向为虚线、superclass指向为实线,Root class元类的superclass指向Root class的class(转弯处实线)。可以按照Student、Person、NSObject来理解。

    • 如果在基类中找不到方法实现,会报错unrecognized selector sent to class 0x1000011**

    Q:如果调用类方法没有实现,是否会调用同名的对象方法。

    A:会。

    调用方法实质是runtime的消息转发机制,runtime只会根据方法名寻找而不会在意是类方法还是对象方法。
    代码验证:

    #Person文件
    @interface Person : NSObject
    + (void)test;
    @end
    
    @implementation Person
    + (void)test{
        NSLog(@"+[Person test] %p",self);
    }
    @end
    
    #NSObject+Test文件
    @interface NSObject (Test)
    + (void)test;
    @end
    
    @implementation NSObject (Test)
    + (void)test{
        NSLog(@"+[NSObject test] %p",self);
    }
    @end
    
    #调用方式:
    
    NSLog(@"Person:%p",[Person class]);
    NSLog(@"NSObject:%p",[NSObject class]);
    [Person test];
    [NSObject test];
    
    
    输出结果1:

    Person:0x10e72e090
    NSObject:0x10f6d8ea8
    +[Person test] 0x10e72e090
    +[NSObject test] 0x10f6d8ea8

    输出结果2:(如果将Person的类方法注释掉,其余代码不变)
    #Person 文件
    @interface Person : NSObject
    + (void)test;
    @end
    
    @implementation Person
    //+ (void)test{
    //    NSLog(@"+[Person test] %p",self);
    //}
    @end
    
    

    输出结果2:
    Person:0x10477b050
    NSObject:0x105725ea8
    +[NSObject test] 0x10477b050
    +[NSObject test] 0x105725ea8

    因为Person里找不到test实现方法,即找父类的实现方法

    输出结果3:(如果将Person和NSObject的类方法注释掉,其余代码不变)
    #Person 文件
    @interface Person : NSObject
    + (void)test;
    @end
    
    @implementation Person
    //+ (void)test{
    //    NSLog(@"+[Person test] %p",self);
    //}
    @end
    
    #NSObject+Test文件
    @interface NSObject (Test)
    + (void)test;
    @end
    
    @implementation NSObject (Test)
    //+ (void)test{
    //    NSLog(@"+[NSObject test] %p",self);
    //}
    @end
    
    

    输出结果3:会报错
    Person:0x10766efd0
    NSObject:0x108618ea8
    +[Person test]: unrecognized selector sent to class 0x10766efd0
    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[Person test]: unrecognized selector sent to class 0x10766efd0'

    输出结果4:(如果将Person和NSObject的类方法注释掉,NSObject添加对象同名方法,其余代码不变)
    #Person 文件
    @interface Person : NSObject
    + (void)test;
    @end
    
    @implementation Person
    //+ (void)test{
    //    NSLog(@"+[Person test] %p",self);
    //}
    @end
    
    #NSObject+Test文件
    @interface NSObject (Test)
    + (void)test;
    @end
    
    @implementation NSObject (Test)
    - (void)test{
        NSLog(@"-[NSObject test] %p",self);
    }
    
    //+ (void)test{
    //    NSLog(@"+[NSObject test] %p",self);
    //}
    @end
    
    

    输出结果4:
    Person:0x101656010
    NSObject:0x102600ea8
    -[NSObject test] 0x101656010
    -[NSObject test] 0x102600ea8

    由此可见,当类方法无实现时,会调用同名的对象方法,上述辩证方法验证了isa、superclass指向图中的meta-Root class的superclass指向Root class(上图实线转弯处)。

    Q:讨论isa指针值

    从64bit开始,isa需要进行一次位运算,才能计算出真实地址
    示例代码:

    struct xbt_objc_class {
        Class isa ;
    };
    
    Person *p = [[Person alloc] init];
    Class pClass = [Person class];
    Class personMetaClass = object_getClass(pClass);
    struct xbt_objc_class *pClass1 = (__bridge struct xbt_objc_class *)pClass;
    NSLog(@"%p-%p-%p",p,pClass,personMetaClass);
    
    

    LLDB 命令行查看isa值

    (lldb) p p->isa//获取不到真正的isa指针
    (Class) $0 = Person
    (lldb) p/x (long)p->isa
    (long) $1 = 0x000001a100105311//实例对象isa指针
    (lldb) p/x pClass
    (Class) $2 = 0x0000000100105310 Person//类对象地址
    (lldb) p/x 0x000001a100105311 & 0x0000000ffffffff8//进行换算
    (long) $3 = 0x0000000100105310
    
    (lldb) p/x pClass1->isa
    (Class) $0 = 0x000001a10402d2e9//类对象isa指针值
    (lldb) p/x personMetaClass
    (Class) $1 = 0x000000010402d2e8//元类对象isa指针值
    (lldb) p/x 0x000001a10402d2e9 & 0x0000000ffffffff8//进行换算
    (long) $2 = 0x000000010402d2e8
    0x1c0036780-0x102a41310-0x102a412e8
    
    

    结果可见:
    1.实例对象isa值为0x000001a100105311
    2.类对象地址为0x0000000100105310
    3.换算结果:实例对象isa指针值 &0x0000000ffffffff8才与类对象地址值相同
    4.类对象isa指针为0x000001a10402d2e9。(类并没有暴露出isa指针值,定义了一个和类相似的结构体xbt_objc_class,进行强制转换后,才获取到类对象的isa指针)
    5.元类对象地址为0x000000010402d2e8
    6.换算结果:类对象isa指针值 &0x0000000ffffffff8才与元类对象地址值相同

    总结

    • 从64bit开始,isa需要进行一次位运算,才能计算出真实地址
    • isa指针需要 & ISA_MASK 值才是最终结果
    • runtime中ISA_MASK值:
    # if __arm64__//iPhone
    #   define ISA_MASK        0x0000000ffffffff8ULL
    # elif __x86_64__//mac
    #   define ISA_MASK        0x00007ffffffffff8ULL
    
    

    Q:讨论superclass指针值,是否会想isa指针值一样?

    A:superclass指针值与指向对象地址相同,和isa指针值不一样

    代码证明
    前提:student继承自Person,写结构体来代替类

    struct xht_objc_class {
        Class isa ;
        Class superclass ;
    };
    struct xht_objc_class *pClass = (__bridge struct xht_objc_class *)[Person class];
    struct xht_objc_class *stuClass = (__bridge struct xht_objc_class *)[Student class];
    NSLog(@"%p-%p",pClass,stuClass);
    
    

    LLDB调试

    (lldb) p/x stuClass->superclass
    (Class) $0 = 0x0000000102299340 Person//Student类的superClass值
    (lldb) p/x pClass
    (xht_objc_class *) $1 = 0x0000000102299340//Person类地址
    
    

    综上所述,Student类的superClass值 = Person类地址

    相关文章

      网友评论

          本文标题:Object-C 方法查找原理(对象、isa和SuperClas

          本文链接:https://www.haomeiwen.com/subject/ttoyjqtx.html