iOS 通俗的理解类,父类,元类的关系
以前看网上的文章的时候,总感觉理解起来很费劲,蒙圈的感觉,这样看完马上就忘了。
最近复习这方面知识,记忆一下,so这里通过源码来捋一捋下面这个关系图。
个人理解,有误请指正。我自己学习源码的github(750有点问题,779版本可以debug
)
这张图应该是见过很多次了,类的结构这边就不说了。
通过运行时创建类的方法
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类
-
cls->initClassIsa(meta)
,这个方法将Student
的isa
指向StuMeta
,Person,NSObject
同理 ,如图,与他们的元类之前的虚线正是isa
- 判断是否有superclass.
2.1. 如果没有(NSObject
):结合源码如图:
ObjcMeta
的isa
指向了他自己,即(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();
) - 而实例对象的isa,从alloc方法里看源码,最后追踪到
_class_createInstanceFromZone
这个方法里,里面会分配内存,然后将实例对象的isa指向类。即图中student
的isa
指向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
,打印StudentSup
和Person1
的地址,PersonSup
和Object1
的地址,ObjectSup
的地址
Student元类的父类 = Person元类,Person元类的父类 = NSObject的元类,NSObject元类的父类 = NSObject类
,打印StudentMetaSup
和PersonMeta
的地址,PersonMetaSup
和ObjectMeta
的地址,ObjectMetaSup
和Object1
的地址元类的sup关系
验证isa 关系
-
先看Student的isa指向。
Student的isa关系
Student的实例isa = Student类,Student类isa = Student元类,Student元类isa = NSObject元类
,打印Student
和Student1
的地址,StudentMeta1
和StudentMeta
的地址,StudentMetaIsa
和ObjectMeta
的地址
-
再看Person的isa指向。
Person的isa关系
Person的实例isa = Person类,Person类isa = Person元类,Person元类isa = NSObject元类
,打印Person
和Person1
的地址,PersonMeta1
和PersonMeta
的地址,PersonMetaIsa
和ObjectMeta
的地址
-
最后看NSObject的isa指向
NSObject的isa关系
NSObject的实例isa = NSObject类,NSObject类isa = NSObject元类,NSObject元类isa = NSObject元类
,打印Object
和Object1
的地址,ObjectMeta1
和ObjectMeta
的地址,ObjectMetaIsa
和ObjectMeta
的地址
最后验证一下所有元类的isa 都指向根元类,打印ObjectMetaIsa,PersonMetaIsa,StudentMetaIsa
的地址
所有元类的isa指向根元类
验证完毕,都符合图中的指向关系。
这里还有一个好玩的地方,不知道大家注意没有,NSObject元类的父类
是NSObject类
。众所周知类方法是存储在元类里的,所以当你调用一个类方法,会在元类里面顺着关系链去找这个方法,如图,当在NSObject元类
里找不到类方法时,会转到NSObject类
里去找,而NSObject类
里存储的是实例方法,这意味着如果通过类方法的方式调用NSObject类
里的实例方法,也是可以的。
试一试。 NSObject里有一个实例方法methodForSelector
,给Person类也加一个实例方法-(void)sayHello;
。
可以看到编译器允许methodForSelector的调用,并能够正常运行。
而sayHello,则会编译错误。
分析就到这里,有误请大佬指正。