美文网首页面试之概念篇
iOS 通俗的理解类,父类,元类的关系

iOS 通俗的理解类,父类,元类的关系

作者: 水煮杰尼龟 | 来源:发表于2020-07-03 16:35 被阅读0次

      以前看网上的文章的时候,总感觉理解起来很费劲,蒙圈的感觉,这样看完马上就忘了。
    最近复习这方面知识,记忆一下,so这里通过源码来捋一捋下面这个关系图。
    个人理解,有误请指正。我自己学习源码的github750有点问题,779版本可以debug

    didi
    这张图应该是见过很多次了,类的结构这边就不说了。
    通过运行时创建类的方法 objc_allocateClassPair 来看看他们的关系。
    Class objc_allocateClassPair(Class superclass, const char *name, 
                                 size_t extraBytes)
    {
        Class cls, meta;
    
        // Fail if the class name is in use.
        if (look_up_class(name, NO, NO)) return nil;
    
        mutex_locker_t lock(runtimeLock);
    
        // Fail if the class name is in use.
        // Fail if the superclass isn't kosher.
        if (getClassExceptSomeSwift(name)  ||
            !verifySuperclass(superclass, true/*rootOK*/))
        {
            return nil;
        }
    
        // Allocate new classes.
        cls  = alloc_class_for_subclass(superclass, extraBytes);
        meta = alloc_class_for_subclass(superclass, extraBytes);
    
        // fixme mangle the name if it looks swift-y?
        objc_initializeClassPair_internal(superclass, name, cls, meta);
    
        return cls;
    }
    

    可以看到这个方法里是有一个cls,还有一个meta的,实际是有2个class的,而返回的只有cls.
    再往下看objc_initializeClassPair_internal 初始化,我只挑出部分代码来看,完整的可以自己去看源码。

    // Connect to superclasses and metaclasses
        cls->initClassIsa(meta);
    
        if (superclass) {
            meta->initClassIsa(superclass->ISA()->ISA());
            cls->superclass = superclass;
            meta->superclass = superclass->ISA();
            addSubclass(superclass, cls);
            addSubclass(superclass->ISA(), meta);
        } else {
            meta->initClassIsa(meta);
            cls->superclass = Nil;
            meta->superclass = cls;
            addRootClass(cls);
            addSubclass(cls, meta);
        }
    

    看上面的注释就知道 这里的处理是连接到父类和元类。这里举个例子(修改了一下关系图):


    栗子
    假如创建一个Student类
    1. cls->initClassIsa(meta),这个方法将Studentisa指向StuMetaPerson,NSObject同理 ,如图,与他们的元类之前的虚线正是isa
    2. 判断是否有superclass.
      2.1. 如果没有(NSObject):结合源码如图:
      ObjcMetaisa指向了他自己,即(meta->initClassIsa(meta);)
      NSObject的父类=Nil,即(cls->superclass = Nil;)
      ObjcMeta的父类=NSObject。即(meta->superclass = cls;)。跟源码的逻辑一致
      2.2. 如果有父类(以Student为例):
      StuMeta指向ObjcMeta(即meta->initClassIsa(superclass->ISA()->ISA());)
      Student的父类 = Person,(即cls->superclass = superclass;)
      StuMeta的父类=Person的isa,即PerMeta 。(meta->superclass = superclass->ISA();)
    3. 而实例对象的isa,从alloc方法里看源码,最后追踪到_class_createInstanceFromZone这个方法里,里面会分配内存,然后将实例对象的isa指向类。即图中studentisa 指向Student
      截取部分代码,最终都会调用initIsa
    if (!zone && fast) {
            obj->initInstanceIsa(cls, hasCxxDtor);
        } else {
            // Use raw pointer isa on the assumption that they might be
            // doing something weird with the zone or RR.
            obj->initIsa(cls);
        }
    

    至此通过源码捋完了。

    接下来 我们创建一个2个类的文件Person,Student,再来通过clang捋一捋。

    用命令clang -rewrite-objc Person.m,clang -rewrite-objc Student.m 转成cpp文件。
    打开Person.cpp.翻到最下面 ,你会看到一个初始化函数:

    static void OBJC_CLASS_SETUP_$_Person(void ) {
        OBJC_METACLASS_$_Person.isa = &OBJC_METACLASS_$_NSObject;
        OBJC_METACLASS_$_Person.superclass = &OBJC_METACLASS_$_NSObject;
        OBJC_METACLASS_$_Person.cache = &_objc_empty_cache;
        OBJC_CLASS_$_Person.isa = &OBJC_METACLASS_$_Person;
        OBJC_CLASS_$_Person.superclass = &OBJC_CLASS_$_NSObject;
        OBJC_CLASS_$_Person.cache = &_objc_empty_cache;
    }
    

    从这里可以捋一下isa和父类的指向关系:(cache先不看)
    Person元类isa指向NSObject的元类
    Person元类父类指向NSObject的元类
    Person类isa指向Person元类
    Person类父类指向NSObject类
    与上面源码分析的结果及图中的指向 一致。

    再看看Student.cpp
    static void OBJC_CLASS_SETUP_$_Student(void ) {
        OBJC_METACLASS_$_Student.isa = &OBJC_METACLASS_$_NSObject;
        OBJC_METACLASS_$_Student.superclass = &OBJC_METACLASS_$_Person;
        OBJC_METACLASS_$_Student.cache = &_objc_empty_cache;
        OBJC_CLASS_$_Student.isa = &OBJC_METACLASS_$_Student;
        OBJC_CLASS_$_Student.superclass = &OBJC_CLASS_$_Person;
        OBJC_CLASS_$_Student.cache = &_objc_empty_cache;
    }
    

    同样捋一下指向关系:
    Student元类isa指向NSObject元类
    Student元类父类指向Person元类
    Student类isa指向Student元类
    Student类父类指向Person类
    与上面源码分析的结果及图中的指向 也是一致的。

    最后我们通过实际代码来验证一下这个指向关系。
    /// 实例化
        NSObject *obj = [[NSObject alloc]init];
        Person *person = [[Person alloc]init];
        Student *student = [[Student alloc]init];
        /// 获取实例对象的isa。即类
        Class Object = object_getClass(obj);
        Class Person = object_getClass(person);
        Class Student = object_getClass(student);
        /// 通过名字获取类
        Class Object1 = objc_getClass("NSObject");
        Class Person1 = objc_getClass("Person");
        Class Student1 = objc_getClass("Student");
        /// 获取类的父类
        Class ObjectSup = class_getSuperclass(Object1);
        Class PersonSup = class_getSuperclass(Person1);
        Class StudentSup = class_getSuperclass(Student1);
        /// 获取类的元类
        Class ObjectMeta = objc_getMetaClass("NSObject");
        Class PersonMeta = objc_getMetaClass("Person");
        Class StudentMeta = objc_getMetaClass("Student");
        /// 获取元类的父类
        Class ObjectMetaSup = class_getSuperclass(ObjectMeta);
        Class PersonMetaSup = class_getSuperclass(PersonMeta);
        Class StudentMetaSup = class_getSuperclass(StudentMeta);
        /// 通过类的isa获取Class ,实际就是元类,所以PersonMeta1的地址和PersonMeta是一样的,StudentMeta1的地址和StudentMeta是一样的。
        Class ObjectMeta1 = object_getClass(Object1);
        Class PersonMeta1 = object_getClass(Person1);
        Class StudentMeta1 = object_getClass(Student1);
        /// 通过元类的isa获取Class, 实际都是根元类,所以PersonMetaIsa和StudentMetaIsa,ObjectMetaIsa的地址是一样的
        Class ObjectMetaIsa = object_getClass(ObjectMeta);
        Class PersonMetaIsa = object_getClass(PersonMeta);
        Class StudentMetaIsa = object_getClass(StudentMeta);
    

    撸上以上代码,在最后打上断点,我们来打印一下地址看看是不是如同上面分析的指向。

    先验证一下父类的关系:

    Student类的父类 = Person类,Person类的父类 = NSObject,NSObject的父类= nil,打印StudentSupPerson1的地址,PersonSupObject1的地址,ObjectSup的地址

    类的sup关系
    Student元类的父类 = Person元类,Person元类的父类 = NSObject的元类,NSObject元类的父类 = NSObject类,打印StudentMetaSupPersonMeta的地址,PersonMetaSupObjectMeta的地址,ObjectMetaSupObject1的地址
    元类的sup关系
    验证isa 关系
    • 先看Student的isa指向
      Student的实例isa = Student类,Student类isa = Student元类,Student元类isa = NSObject元类,打印StudentStudent1的地址,StudentMeta1StudentMeta的地址,StudentMetaIsaObjectMeta的地址

      Student的isa关系
    • 再看Person的isa指向
      Person的实例isa = Person类,Person类isa = Person元类,Person元类isa = NSObject元类,打印PersonPerson1的地址,PersonMeta1PersonMeta的地址,PersonMetaIsaObjectMeta的地址

      Person的isa关系
    • 最后看NSObject的isa指向
      NSObject的实例isa = NSObject类,NSObject类isa = NSObject元类,NSObject元类isa = NSObject元类,打印ObjectObject1的地址,ObjectMeta1ObjectMeta的地址,ObjectMetaIsaObjectMeta的地址

      NSObject的isa关系
      最后验证一下所有元类的isa 都指向根元类,打印ObjectMetaIsa,PersonMetaIsa,StudentMetaIsa的地址
      所有元类的isa指向根元类
    验证完毕,都符合图中的指向关系。

    这里还有一个好玩的地方,不知道大家注意没有,NSObject元类的父类NSObject类。众所周知类方法是存储在元类里的,所以当你调用一个类方法,会在元类里面顺着关系链去找这个方法,如图,当在NSObject元类里找不到类方法时,会转到NSObject类里去找,而NSObject类里存储的是实例方法,这意味着如果通过类方法的方式调用NSObject类里的实例方法,也是可以的。
    试一试。 NSObject里有一个实例方法methodForSelector,给Person类也加一个实例方法-(void)sayHello;

    11
    可以看到编译器允许methodForSelector的调用,并能够正常运行。
    而sayHello,则会编译错误。

    分析就到这里,有误请大佬指正。

    end

    相关文章

      网友评论

        本文标题:iOS 通俗的理解类,父类,元类的关系

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