在上一篇 iOS 类的结构分析(上) 分析了类的结构、isa 的走位以及类的内存分布(属性列表&实例方法列表),这里就有疑问了,在自定义的 LCPerson 类中,我们定义了成员变量 NSString *name 以及类方法 + (void)say666 它们存储在哪里?
成员变量的存储
这里首先看下 成员变量、实例变量、属性 是什么
-
成员变量:在 OC 的类中 {} 中定义的变量
-
实例变量:通过当前对象类型,具备实例化的变量,是一种特殊的成员变量。实例变量主要是判断是不是对象
-
属性:在 OC 中是通过 @property 开头定义,相当于带下划线成员变量 + setter + getter方法的变量
探索
我们进入 objc-781 查看 objc_class 的源码定义,其中 bits 属性中存储数据的类 class_rw_t ,在查看它的源码中看到除了 methods、properties、protocols 方法,还有一个 ro 方法,返回值类型为 class_ro_t,定义如下
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
//省略
在这里我们看到一个很熟悉的名词 ivars,成员变量会不会就存储在这里呢,下面我们来验证下
- 获取
bits数据
- 获取
ro数据地址并打印数据信息
- 获取
ivars首地址以及打印成员列表信息
- 打印每个成员变量的信息
在打印的信息中我们看到了 name 以及 _nickname ,但是我们不是只添加了一个成员变量 name ,怎么会多出一个 _nickname?因为属性 nickname = _nickname + setter 方法 + getter 方法。
类方法的存储
在上一篇 iOS 类的结构分析(上) ,说到了元类,类对象的 isa 指向的是元类,元类是用来存储类的相关信息的,类方法应该存储在元类的 bits 中,下面我们通过 lldb 命令来验证下
- 获取
LCPerson的内存信息,打印LCPerson元类的首地址
- 通过查找实例方法的步骤,来获取
bits信息
- 获取元类
bits数据中的方法数组
- 打印数组中的方法
可以看到,打印了 name = "say666",就是我们之前定义的 类方法
总结
-
类的实例方法存储在类的
bits属性中,通过 bits --> methods() --> list 获取实例方法列表,类中的方法列表除了包括实例方法,还包括属性的 set 方法 和 get方法 -
类的类方法存储在元类的
bits属性中,通过元类 bits --> methods() --> list 获取类方法列表
面试题1分析
下面的代码会输出什么?
@implementation LCPerson : NSObject
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[LCPerson class] isKindOfClass:[LCPerson class]];
BOOL re3 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re4 = [(id)[LCPerson alloc] isKindOfClass:[LCPerson class]];
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re6 = [(id)[LCPerson class] isMemberOfClass:[LCPerson class]];
BOOL re7 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re8 = [(id)[LCPerson alloc] isMemberOfClass:[LCPerson class]];
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
那么上面的答案会是什么呢?我们先来分析下,既然是经典,那答案肯定没有那么简单了,我们通过 objc-781 源码查看他们的实现
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
判断的 当前类 的 元类 是否与 条件类 相等,如果不相等,会走 当前类的元类的 父类 继续判断是否与 条件类 相等
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
判断 当前对象所属的类 是否与 条件类 相等,如果不相等,会走 当前对象所属的类 的 父类 继续判断与 条件类 是否相等,是一个实例方法
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
判断的 当前类 的 元类 是否与 条件类 相等,是一个类方法
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
判断 当前对象所属的类 与 条件类 是否相等,是一个实例方法
现在我们来运行一下代码,查看打印结果
是的,结果与我们解析的一样,现在我们验证一下上面的逻辑,是否跟我们想的一样,走上面讲述的源码方法。
在源码中添加 断点 ,运行
调试的结果跟我们的猜想有点出入,isMemberOfClass 的 类方法 和 实例方法 是会走到源码,而 isKindOfClass 的 类方法 和 实例方法 根本没走(⚠️),这是为什么呢?
我们把断点取消,在 main 的函数添加断点,运行后进入断点,打开汇编
类方法 和 实例方法 都是走到 objc_opt_isKindOfClass 方法源码中
-
objc_opt_isKindOfClass源码如下
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->superclass) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
关于这一点,有兴趣的可以到 llvm 的源码中查看具体的流程,这里就不过多的说明了
代码解析
-
re1,调用的是 +isKindOfClass 方法
- 第一次,判断
NSObject 的元类是否与NSObject相等,结果不相等; - 第二次,
NSObject 的元类的父类是否与NSObject相等,结果相等,结束循环,返回1。因为NSObject 的元类的父类的父类(即根元类的父类)是它自己NSObject
- 第一次,判断
-
re2,调用的是 +isKindOfClass 方法
- 第一次,判断
LCPerson 的元类是否与LCPerson相等,结果不相等; - 第二次,判断
LCPerson 的元类的父类根元类(NSObject meta)是否与LCPerson相等,结果不相等; - 第三次,判断
根元类的父类根类(NSObject)是否与LCPerson相等,结果不相等,此时 NSObject 的父类指向 nil, 不满足 for 循环执行条件。结束循环,返回0
- 第一次,判断
-
re3,调用的是 -isKindOfClass 方法
- 判断
NSObject对象所属的类(NSObject)是否与NSObject相等,相等,结束循环,返回1
- 判断
-
re4,调用的是 -isKindOfClass 方法
- 判断
LCPerson对象所属的类(LCPerson)是否与LCPerson相等,结果,结束循环,返回1
- 判断
-
re5,调用的是 +isMemberOfClass 方法
- 判断
NSObject 的元类是否与NSObject相等,结果不相等,返回0
- 判断
-
re6,调用的是 +isMemberOfClass 方法
- 判断
LCPerson 的元类是否与LCPerson相等,结果不相等,返回0
- 判断
-
re7,调用的是 -isMemberOfClass 方法
- 判断
NSObject的对象所属的类(NSObject) 是否与NSObject相等,相等,结束循环,返回1
- 判断
-
re8,调用的是 -isMemberOfClass 方法
- 判断
LCPerson对象所属的类(LCPerson)是否与LCPerson相等,结果,结束循环,返回1
- 判断
面试题2分析
创建一个 Person 类,继承自 NSObject,分别添加一个实例方法和一个类方法
@interface LCPerson : NSObject
- (void)sayHello;
+ (void)say666;
@end
@implementation LCPerson
- (void)sayHello{
NSLog(@"sayHello");
}
+ (void)say666{
NSLog(@"say666");
}
@end
分别用 class_getInstanceMethod 和 class_getClassMethod 获取方法
void lgInstanceMethod_classToMetaclass(Class pClass) {
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method3 = class_getInstanceMethod(pClass, @selector(say666));
Method method4 = class_getInstanceMethod(metaClass, @selector(say666));
NSLog(@"%s - %p--%p--%p--%p",__func__,method1,method2,method3,method4);
}
- 上面的代码会输出什么呢?通过类的结构分析,我们知道对象的实例方法存储在类中,类方法存储在元类中
-
method1:在
LCPerson类 中查找 - (void)sayHello 这样一个实例方法 ,显然是存在的 -
method2:在
LCPerson 元类中查找- (void)sayHello 这样一个实例方法 ,是不存在的 -
method3:在
LCPerson类 中查找 + (void)say666 这样一个类方法 ,是不存在的 -
method4:在
LCPerson 元类中查找 + (void)say666 这样一个类方法 ,是存在的
所以最终的输出结果为:0x100002100--0x0--0x0--0x100002098
- 以下代码会输出什么?
void lgClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
Method method3 = class_getClassMethod(pClass, @selector(say666));
Method method4 = class_getClassMethod(metaClass, @selector(say666));
NSLog(@"%s-%p--%p--%p--%p",__func__,method1,method2,method3,method4);
}
输出结果为 -0x0--0x0--0x1000020a0--0x1000020a0,可能有些小伙伴一时间理解不了,为什么会出现这样的结果呢?
首先我们查看 class_getClassMethod 的源码实现
Method class_getClassMethod(Class cls, SEL sel) {
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
由底层实现可见,获取类方法就是获取元类中的实例方法,继续查看 getMeta 的源码
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
看到源码的实现后恍然大悟:
- method1:
pClass是LCPerson,它不是元类,返回的是它的元类LCPerson 元类。在LCPerson 元类中查找 - (void)sayHello 这样一个类方法 ,显然是不存在的 - method2:
metaClass为LCPerson 元类。 在LCPerson 元类中查找 - (void)sayHello 这样一个类方法 ,是不存在的 - method3:
pClass是LCPerson,它不是元类,返回的是它的元类LCPerson 元类。在LCPerson 元类中查找 + (void)say666 这样一个类方法 ,是存在的 - method4:
metaClass为LCPerson 元类。在LCPerson 元类中查找 + (void)say666 这样一个类方法 ,是存在的
由以上可知,method1 和 method2 实际上效果是一样的;method3 和 method4 一样。
在调用 class_getClassMethod 方法时,获取类方法就是获取元类中的实例方法。在获取元类的时候,如果传入的就是元类,那么会返回本身;否则才会继续查找元类。
为什么这样设计呢?
因为在现实世界中,没人会向元类对象发送消息。















网友评论