类的结构
在上一个章节,讲到了isa,然后直接引申到类,那么究竟什么是类呢,跟源码,看结构发现,Class实际上就是objc_class这么一个结构体。
objc_class又继承自objc_object。为什么objc_class中的第一个属性ISA会被注释?这个就是提醒开发者,这是一个隐藏属性,继承自objc_object的光环,看下objc_object你就知道了。
是不是一目了然?回到objc_class,类的结构有这四个属性:
// Class ISA;Class superclass;cache_t cache; // formerly cache pointer and vtableclass_data_bits_t bits;
这四个属性拆开来看,
ISA:Class类型:typedef struct objc_class *Class;指针;
superclass:类型同上,顾名思义,父类;
cache_t:这个是一个结构体,用于缓存方法的imp,看下cache_t的结构:
class_data_bits_t:这个属性是一个struct结构体类型,我们所熟知的属性啊,成员变量就存储在这里。
类的创建时机
类的创建,这可能有点陌生。开发中一般都是创建个类的实例对象,比如创建个Person类的实例对象,然后怎样怎样。但是具体类是什么时候创建的呢?
类是在编译期就已经创建并加载到内存了。下面我们通过两种方式来验证:
lldb打印调试验证

如上图:将断点打在Person *person = [Person.class alloc];这一行,首先明确,断点到这儿的时候,这一样代码还没执行。person对象为nil。
好,开始lldb调试(这些指令和0x0000000ffffffff8的含义在这篇文章提到过):
直接x/4xg 打印Person类,发现已经可以打印出数据,通过他的第一个属性isa也已经成功找到Person这个元类。所以说在创建Person对象之前,Person这个类就已经存在于内存之中。
MachOView查看验证
如果说上面的方法运行程序了,不足以说明类是在编译期创建的,那么这个方法来看;直接command + b 编译一下项目,然后在Products文件夹下边找到这个待执行文件,拖到MachOView中,

我们仅仅command + b 编译了一下工程,就已经可以在内存中找到这个Person类,这更加明确的验证了“类是在编译期就已经创建并加载到内存了。”
objc_object和NSObject
在没有翻源码之前的认知里,万物皆对象万物皆NSObject,类就是Class。但是翻了源码发现,Class其实是个objc_class的结构体,并且继承自objc_object。那么,这一一对应的两者objc_object和NSObject是什么关系呢?
其实OC的底层是由c实现的,objc_object可以说是用c来写的,但是我们开发中用的是OC啊,所以可以认为NSObject是objc_object的一种仿写,他们的本质其实是一样的。
类定义的那些属性和成员变量
类中定义一个属性,比如title,编译器会自动生成set和get方法,并且会创建一个带下划线的_title成员变量;类中定义一个成员变量,不会生成set和get方法。下面验证一下:
编译成cpp文件查看验证
在Person.m文件中添加一个属性title和成员变量nickName:
然后,将终端进入Person.m文件所在目录,执行 clang -rewrite-objc Person.m -o Person.cpp 指令,即可在指定的目录下看到编译好的Person.cpp文件,全局搜索(command + f)Person_IMPL 看到下面这个结构体:
Person_IMPL 就是Person类通过编译之后的样子,第一个参数NSObject_IMPL我暂且称他为父类NSObject的成员变量(我猜的,所有集成NSObject的类编译完之后,第一个参数都是他),
nickName就是我们之前定义好的成员变量,_title就是编译自动生成的对应title属性的成员变量。往下看,有一个ivar_list,同样可以看到这两个成员变量:
继续往下找,method_list:
红框圈出来的正是title属性的get和set方法,至于@16@0:8的意思请参考iOS符号。
Runtime方法打印验证
下面通过runtime api 提供的方法来打印一下ivar、property和method:
先贴出来runtime的方法:
// 获取class的所有成员变量void testObjc_copyIvar(Class class){unsigned int count = 0;Ivar *ivars = class_copyIvarList(class, &count);for (unsigned int i=0; i < count; i++) {Ivar const ivar = ivars[i];//获取实例变量名const char*cName = ivar_getName(ivar);NSString *ivarName = [NSString stringWithUTF8String:cName];NSLog(@"class_copyIvarList:%@",ivarName);}free(ivars);}// 获取class的所有属性void testObjc_copyProperies(Class class){unsigned int pCount = 0;objc_property_t *properties = class_copyPropertyList(class, &pCount);for (unsigned int i=0; i < pCount; i++) {objc_property_t const property = properties[i];//获取属性名NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];//获取属性值NSLog(@"class_copyProperiesList:%@",propertyName);}free(properties);}// 获取class的所有方法void testObjc_copyMethodList(Class class){unsigned int count = 0;Method *methods = class_copyMethodList(class, &count);for (unsigned int i=0; i < count; i++) {Method const method = methods[i];//获取方法名NSString *key = NSStringFromSelector(method_getName(method));NSLog(@"Method, name: %@", key);}free(methods);}
Tips:以上函数都是在main.m文件中写的,所有函数的格式符合C语言语法,调用的时候也直接C函数调用,还有函数应该写在main函数上边,或者先在main函数上边声名再在下边实现也可以。(应该是这样,C语法记不太清)
看一下打印结果:
和上边的编译看出来的结果是一样的,很完美,后面再看一下,实例方法和类方法,分别存在哪儿。
实例方法和类方法
存在位置
上边,在testObjc_copyMethodList方法中,打印出了编译过程中,系统自动生成的get和set方法,这是在传来的class也就是Person类中通过runtime提供的class_copyMethodList方法获取的方法列表,去掉属性,分别定义一个对象方法和类方法,在通过上边testObjc_copyMethodList方法打印一下:
执行结果:
testObjc_copyMethodList方法接收的参数是Person类,打印发现,只有输出一个personA这个实例方法,那personB这个类方法呢,他为什么打印不出来?
原因:实例方法存在类中,类方法存在元类中
通过object_getClass(class)获取Person类的元类,作为参数传入testObjc_copyMethodList方法中打印:
这次,在元类Person中打印找到了personB这个类方法。
叨叨一句:之前提到的类对象,可以就看成元类的对象,而类方法存在元类中,也可以称之为元类的“实例方法”。
class_getInstanceMethod和class_getClassMethod
看下下边的案例,personA为实例方法,personB为类方法:
void testInstanceMethod_classToMetaclass(Class pClass){const char *className = class_getName(pClass);Class metaClass = objc_getMetaClass(className);Method method1 = class_getInstanceMethod(pClass, @selector(personA));Method method2 = class_getInstanceMethod(metaClass, @selector(personA));Method method3 = class_getInstanceMethod(pClass, @selector(personB));Method method4 = class_getInstanceMethod(metaClass, @selector(personB));NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);NSLog(@"%s",__func__);}void testClassMethod_classToMetaclass(Class pClass){const char *className = class_getName(pClass);Class metaClass = objc_getMetaClass(className);Method method1 = class_getClassMethod(pClass, @selector(personA));Method method2 = class_getClassMethod(metaClass, @selector(personA));Method method3 = class_getClassMethod(pClass, @selector(personB));Method method4 = class_getClassMethod(metaClass, @selector(personB));NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);NSLog(@"%s",__func__);}
如果method有值就能打印出来指针,跑一下看看:
objc_getMetaClass(className):获取元类
来解释一下,首先class_getInstanceMethod(class,sel):获取class的sel实例方法
personA是Person类的实例方法,所以前两个结果为1、0(1表示找到method,0表示没找到),personB为Person类的类方法,存在Person元类中,可以视为personB为Person元类的实例方法,所以后边两个为0、1;
再来看class_getClassMethod(class,sel):获取类方法:
看一下class_getClassMethod的源码:
getMeta()源码:
一目了然啊,里边竟然就是通过class获取到元类,然后将这个元类和sel作为参数去调用class_getInstanceMethod方法,
终极解释:personA为实例方法存在Person类中,在Person元类肯定找不到,前两个为0;personB为类方法,存储在Person元类中,在Person元类中能找到,所以后边为1,没毛病吧?
关于class_getInstanceMethod里边的实现原理,请参考方法的查找流程
