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

  以前看网上的文章的时候,总感觉理解起来很费劲,蒙圈的感觉,这样看完马上就忘了。
最近复习这方面知识,记忆一下,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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355