面试之概念篇

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

2020-07-03  本文已影响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 关系
验证完毕,都符合图中的指向关系。

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

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

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

end

上一篇下一篇

猜你喜欢

热点阅读