美文网首页
OC中类结构分析

OC中类结构分析

作者: 半边枫叶 | 来源:发表于2019-12-22 21:16 被阅读0次

想要分析OC中类的结构,我们可以通过clang命令得到底层的实现:
clang -rewrite-objc main.m -o main.cpp
然后在源码中我们可以看到Class的实现:
typedef struct objc_class *Class;
可以得出,Class真正类型时objc_class。

接下来我们就可以研究objc_class的结构了

struct objc_class : objc_object {
    // Class ISA; // 8
    Class superclass; // 8
    cache_t cache;    // 16 不是8         // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    //......其他的省略
}

objec_class继承自objc_object。OC中objc_object是一些对象的基类。ISA指针也是继承于objc_object。
OC中的NSObject其实和底层的objc_object是对应的。
我们可以看到有个bits有个data()函数,可以猜测类中的方法和属性可能存在于bits中。

然后我们开始探索bits中的内容:

首先,我们可以打印出类对象的地址,因为objc_class是struct,所以我们可以通过地址的平移得到属性bits。
属性占用内存情况:
ISA:8字节;
superclass:8字节;
cache_t的结构如下:

struct cache_t {
    struct bucket_t *_buckets; // 8
    mask_t _mask;  // 4
    mask_t _occupied; // 4

8+4+4=16
cache:16字节;

所以可以得出bits相对比objc_object首地址的偏移为32字节。
(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da260 0x0000000000000000
(lldb) p 0x1000023d0 //首地址+32
(long) $2 = 4294976464
(lldb) p (class_data_bits_t *)0x1000023d0 //因为bits的类型是class_data_bits_t,这里需要强转一下
(class_data_bits_t *) $3 = 0x00000001000023d0
(lldb) p $3->data()
(class_rw_t *) $6 = 0x0000000100f39980
(lldb) p *$6
//这里我们得到了bits中data()的所有内容,通过探索,可以得出class的属性和方法列表都在ro中
(class_rw_t) $7 = {
  flags = 2148139008
  version = 0
  ro = 0x0000000100002308
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000100002240
        arrayAndFlag = 4294976064
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x00000001000022f0
        arrayAndFlag = 4294976240
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
  demangledName = 0x0000000000000000
}

接下里我们打印ro

(lldb) p (class_ro_t *)$7.ro
(class_ro_t *) $11 = 0x0000000100002308
(lldb) p *$11
(class_ro_t) $12 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100001f89 "\x02"
  name = 0x0000000100001f80 "LGPerson"
  baseMethodList = 0x0000000100002240
  baseProtocols = 0x0000000000000000
  ivars = 0x00000001000022a8
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000022f0
}

可以看到ro中含有baseMethodList、baseProperties 、ivars等方法和属性的列表。
先来打印方法列表

(lldb) p $12.baseMethodList
(method_list_t *) $13 = 0x0000000100002240
(lldb) p *$13
(method_list_t) $14 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4//存在4个方法
    first = {
      name = "sayHello"
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
    }
  }
}
(lldb) p $14.get(1)
(method_t) $15 = {
  name = ".cxx_destruct"
  types = 0x0000000100001f8b "v16@0:8"
  imp = 0x0000000100001c60 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}
(lldb) p $14.get(2)
(method_t) $16 = {
  name = "nickName"
  types = 0x0000000100001f93 "@16@0:8"
  imp = 0x0000000100001bf0 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
}
(lldb) p $14.get(3)
(method_t) $17 = {
  name = "setNickName:"
  types = 0x0000000100001f9b "v24@0:8@16"
  imp = 0x0000000100001c20 (LGTest`-[LGPerson setNickName:] at LGPerson.h:17)
}

再来打印属性列表

(lldb) p $12.baseProperties
(property_list_t *) $18 = 0x00000001000022f0
(lldb) p *$18
(property_list_t) $19 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1 //只有一个属性
    first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
  }
}

再来看成员变量

(lldb) p $12.ivars
(const ivar_list_t *) $20 = 0x00000001000022a8
(lldb) p *$20
(const ivar_list_t) $21 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x0000000100002378
      name = 0x0000000100001e64 "hobby"
      type = 0x0000000100001fa6 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $21.get(1)
(ivar_t) $22 = {
  offset = 0x0000000100002380
  name = 0x0000000100001e6a "_nickName"
  type = 0x0000000100001fa6 "@\"NSString\""
  alignment_raw = 3
  size = 8
}

最后我们可以得出,类的常用属性的结构基本如下:


image.png

注意:实例方法存储在类的methodList, 而类方法存储在对应元类的methodList中。

相关面试题

问题:问下面的代码打印的结果是什么

        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
        BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
        NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

        BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
        BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
        BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
        NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

正确的结果应该是:
re1: 1
re2: 0
re3: 0
re4: 0

re5: 1
re6: 1
re7: 1
re8: 1

re5、6、7、8很容易理解,是调用的对象方法。

下面我们分析一下re1:
[NSObject class] 调用类方法isKindOfClass,我们跟踪源码进去看看:

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

此处的self是NSObject类对象,然后调用object_getClass((id)self)后得到的tcls为NSObject的元类,而此时传进来的cls为NSObject类,所以第一次循环的结果 tcls不等于cls。然后继续循环判断,去取tcls->superClass。因为NSObject的元类是根元类,根元类的父类是NSObject(注:此处考察的就是这个知识点)。所以tcls成为了NSObject,然后和cls对比,正好相等,返回YES。

那么为什么re2返回为NO呢?我们来看下isMemberOfClass:的源码:

+ (BOOL)isMemberOfClass:(Class)cls {
    // 元类 VS 类
    return object_getClass((id)self) == cls;
}

此时是直接将self(NSObject类)的元类和cls(NSObject类)进行对比,直接返回NO。
同样的方法可以分析re3和re4,都是返回NO。

最后补充一下OC对象isa和superClass的流程图:


isa流程图.png

问题:
LGPerson的定义如下:


image.png

请问下面代码打印的结果是什么?

    LGPerson *person = [LGPerson alloc];
    Class pClass     = object_getClass(person);
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    IMP imp1 = class_getClassMethod(pClass, @selector(sayHello));
    IMP imp2 = class_getClassMethod(metaClass, @selector(sayHello));

    IMP imp3 = class_getClassMethod(pClass, @selector(sayHappy));
    IMP imp4 = class_getClassMethod(metaClass, @selector(sayHappy));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);

答案是:0x0-0x0-0x1000022a0-0x1000022a0
对于imp1和imp2我们都知道,sayHello是实例方案,所以获取到的都是空。而sayHappy是类方法,类方法是存在于元类中的实例方法,imp3理所当然是可以获取到的。但是imp4为什么也能获取到呢?下面我们可以跟踪class_getClassMethod:源码进去看看:

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

可以看出class_getClassMethod方法是通过获取到元类,然后取元类中的实例方法就是类方法。然后我们跟踪getMeta方法

Class getMeta() {
    if (isMetaClass()) return (Class)this;
    else return this->ISA();
}

到此可以明白了,因为我们传进来的是元类,然后直接返回自己。和传递进来类是一样的。

相关文章

  • iOS 类原理探索:类的结构分析

    OC 类原理探索 系列文章 OC 类原理探索:类的结构分析 OC 类原理探索:类结构分析补充[https://ju...

  • OC中类结构分析

    想要分析OC中类的结构,我们可以通过clang命令得到底层的实现:clang -rewrite-objc main...

  • OC 类&类结构分析

    OC底层原理学习 学习OC时,常听的就是万物皆对象,对象都有isa指针,那为什么有isa指针,isa指针到底是谁创...

  • 类结构之cache分析

    背景 前面我们在分析ios类结构过程,在oc类结构那点事(四)中我们了解到class有几个关键属性,其中bits信...

  • OC类结构分析

    类Class ,也可以称为类对象,在编译时会转成objc_class, objc_class继承自objc_obj...

  • OC类结构分析

    OC是一门面向对象语言,面向对象离不开对象,类,继承,类方法,实例方法,属性,实例变量,对于习惯了面向对象的同学来...

  • OC类结构分析

    一、概要 先要区分两个概念:1、类:一种结构体,所有对象公用一个类结构。2、对象:通过类创建出来,每个对象有独立的...

  • 类的结构分析

    类的结构分析 引子:在程序世界中,无论接触哪门语言,类是最基本一个研究点。那么在oc中,类和类的结构是如何组成的呢...

  • objc_class 中 cache 原理分析

    OC底层原理学习[https://www.jianshu.com/nb/47790292] 回顾类的结构分析中[h...

  • OC底层原理06-cache_t探究

    iOS--OC底层原理文章汇总 前言 本文主要探索cache_t * cache结构内容,分析它在类的结构中扮演了...

网友评论

      本文标题:OC中类结构分析

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