美文网首页
iOS 类的结构分析(下)

iOS 类的结构分析(下)

作者: 远方竹叶 | 来源:发表于2020-09-17 22:43 被阅读0次

在上一篇 iOS 类的结构分析(上) 分析了类的结构、isa 的走位以及类的内存分布(属性列表&实例方法列表),这里就有疑问了,在自定义的 LCPerson 类中,我们定义了成员变量 NSString *name 以及类方法 + (void)say666 它们存储在哪里?

成员变量的存储

这里首先看下 成员变量实例变量属性 是什么

  • 成员变量:在 OC 的类中 {} 中定义的变量

  • 实例变量:通过当前对象类型,具备实例化的变量,是一种特殊的成员变量。实例变量主要是判断是不是对象

  • 属性:在 OC 中是通过 @property 开头定义,相当于带下划线成员变量 + setter + getter方法的变量

探索

我们进入 objc-781 查看 objc_class 的源码定义,其中 bits 属性中存储数据的类 class_rw_t ,在查看它的源码中看到除了 methodspropertiesprotocols 方法,还有一个 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_getInstanceMethodclass_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);
}
  1. 上面的代码会输出什么呢?通过类的结构分析,我们知道对象的实例方法存储在类中,类方法存储在元类中
  • method1:在 LCPerson 类 中查找 - (void)sayHello 这样一个实例方法 ,显然是存在的

  • method2:在 LCPerson 元类 中查找- (void)sayHello 这样一个实例方法 ,是不存在的

  • method3:在 LCPerson 类 中查找 + (void)say666 这样一个类方法 ,是不存在的

  • method4:在 LCPerson 元类 中查找 + (void)say666 这样一个类方法 ,是存在的

所以最终的输出结果为:0x100002100--0x0--0x0--0x100002098

  1. 以下代码会输出什么?
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:pClassLCPerson,它不是元类,返回的是它的元类 LCPerson 元类 。在 LCPerson 元类 中查找 - (void)sayHello 这样一个类方法 ,显然是不存在的
  • method2:metaClassLCPerson 元类。 在 LCPerson 元类 中查找 - (void)sayHello 这样一个类方法 ,是不存在的
  • method3:pClassLCPerson,它不是元类,返回的是它的元类 LCPerson 元类 。在 LCPerson 元类 中查找 + (void)say666 这样一个类方法 ,是存在的
  • method4:metaClassLCPerson 元类。在 LCPerson 元类 中查找 + (void)say666 这样一个类方法 ,是存在的

由以上可知,method1 和 method2 实际上效果是一样的;method3 和 method4 一样。

在调用 class_getClassMethod 方法时,获取类方法就是获取元类中的实例方法。在获取元类的时候,如果传入的就是元类,那么会返回本身;否则才会继续查找元类。

为什么这样设计呢?

因为在现实世界中,没人会向元类对象发送消息。

相关文章

  • iOS 类的结构分析(下)

    在上一篇 iOS 类的结构分析(上) 分析了类的结构、isa 的走位以及类的内存分布(属性列表&实例方法列表),这...

  • iOS类结构:cache_t分析

    一、cache_t 内部结构分析 1.1 在iOS类的结构分析中,我们已经分析过类(Class)的本质是一个结构体...

  • iOS-底层分析之类的结构分析

    类的结构分析 本文主要分析iOS中的类以及类的结构,下面我们通过一个例子来探索类的结构 我们定义一个WPerson...

  • iOS底层之cache_t探究

    我们在iOS底层之类的结构分析分析了类的内部结构,而类的C/C++底层实际是objc_class结构体,其中包含了...

  • [iOS] 类 & 类结构分析

    1. 类的分析 1.1 元类的引入 我们可能之前已经知道类其实也是一个对象,类的 isa 指针指向的是它的元类,...

  • iOS经典面试题分析

    面试题一 在iOS类的结构分析中的探索中,我们知道了实例方法 存储在类中,类方法存储在元类中,接下来我们来分析一下...

  • iOS 类结构分析

    前言 通过本篇文章可以了解1.isa的走位2.类结构的分析3.什么是元类4.supclass的走位5.objc_c...

  • iOS类结构分析

    本文主要来探索一下iOS中类的结构,作为一个iOS开发者,我们有必要去了解关于类的底层知识。下面开始我们的探索。 ...

  • iOS - 类结构分析

    我们都知道,一个类可以创建多个不同实例对象,类自己也是对象(类对象),那么类在内存中会存在几份呢?看下面结果 得出...

  • iOS 类的结构分析

    在谈及面向对象编程的时候,总是离不开 对象 与 类 。对象 是对客观事物的抽象,类 是对 对象 的抽象。它们的关系...

网友评论

      本文标题:iOS 类的结构分析(下)

      本文链接:https://www.haomeiwen.com/subject/avsfyktx.html