iOS 底层原理之 类的原理分析(上)

首先推荐一下alloc流程中篇,alloc流程下篇,alloc流程上篇,这些对于本文的理解是很有帮助的哦

一.分析类对象的ISA

1.获取ISA地址

首先我们还是先自定义一个对象YCXPerson,打印他的内存地址

在这里插入图片描述

根据之前重识alloc流程下篇所提到的,ISA指针肯定是作为首地址,那么自然而然就是0x001d800100008375,然后进行位运算,去掉首尾的地址部分,最终打印结果就是YCXPerson的相关信息了,这里大家可能会疑惑为什么以0x00007ffffffffff8进行运算,这个就是重识alloc流程下篇提到的在x86_64架构下的掩码,帮助快速运算用的

#   define ISA_MASK        0x00007ffffffffff8ULL

2.获取ISA中的类信息

大家也都看到了0x0000000100008370存放的是关于类的信息,那么我再来获取以下这快地址中还有哪些东西呢?

在这里插入图片描述

还是先取ISA地址的首地址进行运算,结果发现也是YCXPerson,那么问题就来了,为什么会有2个地址不一样的YCXPerson

3.元类以及MachOView的使用

要想论证这个观点,首先得知道这两个地址到底是不是同一个类,那么我就通过几种不同的方式去打印,却发现结果都一样都是0x100008370,说明一点上面所提到的另一个地址其实并不是YCXPerson类,可能是在编译阶段做了一些处理,就像之前提到的alloc优化处理一样
在这里插入图片描述
既然这样我们就需要了解在编译的时候是不是又生成了其他的类,这是就需要用到一个新的工具,也就是MachOView,俗称烂苹果,把工程生成的可执行文件,直接拖拽到MachOView中就可以获取相关的信息

在这里插入图片描述
通过MachOView解析,找到了YCXPerson,YCXTeacherCooci,继续继续

在这里插入图片描述

在这里插入图片描述
终于在Symbol Table 终于找到了,此时此刻,掌声在哪里?果然在编译的时候生成了一个新的类_OBJC_METACLASS_YCXPerson ,而它也就是所谓的元类

3.根元类

既然有元类的存在,相信大家也会好奇,元类isa又会是什么呢?

在这里插入图片描述

感觉有点绕晕了,😄😄,不过最终发现到了根元类之后就结束了,最终都会指向NSObject,当然如果觉得使用LLDB麻烦,也可以直接代码,如下

        YCXPerson *object1 = [YCXPerson alloc];
        // NSObject类
        Class class = object_getClass(object1);
        // NSObject元类
        Class metaClass = object_getClass(class);
        // NSObject根元类
        Class rootMetaClass = object_getClass(metaClass);
        // NSObject根根元类
        Class rootRootMetaClass = object_getClass(rootMetaClass);
        NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);

最终打印结果其实也是一样的

在这里插入图片描述

4.继承链

通过上面的各种测试发现走到根元类就到头了,那么如果是父类又或者是根类(如:NSObject)呢,他们的情况又会是什么样子呢,我们继续摸索

在这里插入图片描述

通过各式各样的测试发现,根类的元类就是自己,而子类的元类的父类和父类的元类是相同的,于是乎,经典的,嗯哼哼。。学不来这腔调,难受。。

5.ISA的关系图

苹果官方提供的ISA的关联图
在这里插入图片描述

父类,根类的元类走势图
在这里插入图片描述

二.内存偏移

1.地址的平移

话不多说,上图(帅气的一批)

在这里插入图片描述
相信大家都知道内存地址都是从高到底地址去存的,通过上述打印发现,一般我们创建的局部变量,在栈中的地址是连续的,这个就不用我去计算了吧,实在不行,掰手指算😄

那么我再来看一下数组的情况

在这里插入图片描述
通过测试发现,当我用int d = c,其实也就是取了数组的首地址,然而+1,其实就是以该数组的内部数据类型所占的内存大小,作为一个单位的偏移量去计算,int类型4字节,+1就相当于指针d的地址平移4字节,然后(d),就意味着取这块地址的值,所以就有了上述的结果,可能大家会奇怪,说好的分析类,怎么突然就讲到内存偏移呢,不着急不着急,好戏在后头!!!😄

三.类的结构分析

1.cache的内存计算

在之前alloc流程下篇,里面就得出过结论,其实对象在底层的本质是结构体,而OC层面的id ,Class都是结构体指针,那么我们接下来就看看Class结构体中存放着哪些东西呢

在这里插入图片描述
通过在objc源码中搜索我们发现结构体中主要就是superclass(isa),cache,bitssuperclass(isa),cache这个单词相信都不陌生,缓存,我们点进去瞧一瞧

在这里插入图片描述
通过对cache内部结构分析,它的内存大小应该是16字节,再加上首地址isa8字节,以及superclass8字节,也就是32字节,根据前面所说的内存地址从高到低的连续性,那么就意味着我们只需要在结构体指针objc_class的首地址处进行内存偏移,就可以获取到bits的相关信息了

2.bits结构分析

老规矩,分析前,先点进去看看

在这里插入图片描述
发现了一个 class_rw_t* data(),相信大家都会对 data()有些敏感,貌似是存放数据的地方,为了验证猜想,还是进行LLDB调试一下,毕竟实践才是检验真理的唯一标准

在这里插入图片描述

根据前面的计算,从首地址偏移32字节,就可以找到bits的地址,通过打印data()发现,里面有个firstSubclass,这个是干嘛的呢?后面一一道来,通过打印data()里面的数据发现并没有类里面的相关属性,然后我打印了properties(),果然有东西,但是并没有我们想要的,那么我在源码中点进去看下

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过层层查看,发现properties()里面其实嵌套了多个数组,那么我们就通过LLDB层层剥开,获取里面真正想要的内容

在这里插入图片描述
剥开面纱,终于看到了熟悉的身影,看到了属性name,hobby,但是没有发现subject,问题1产生了

在这里插入图片描述
接下来我们再来查找方法的存放位置,再查找之前还是要了解methods()里面的数据结构

在这里插入图片描述
通过methods()properties()对比可以看出问题关在于结构体property_tmethod_t
在这里插入图片描述
在这里插入图片描述
最终发现method_t内部还有一层结构体big,而这里才是我们需要的东西

在这里插入图片描述
果然方法全都在这里了,但是似乎少了一个类方法+ (void)say666,那么这里就有问题了,为什么类方法不在类的信息里面呢,还有前面的属性subject有存放在哪呢

四.寻找类方法和ivar

1.寻找类方法

  • 思路:其实在找类方法之前首先我们要锊一下思路,类的实例对象方法存在类中,而通过之前的学习,我们又知道在编译过程中,其实是有生成类的元类的,那么我们是不是可以通过类的元类去寻找呢

开始LLDB调试喽,代码里有说明

(lldb) p/x YCXPerson.class
(Class) $0 = 0x0000000100004680 YCXPerson //类对象指针
(lldb) x/4gx $0  //获取类对象的内存分部地址 而首块地址必然就是ISA,指向元类的内存地址的指针
0x100004680: 0x00000001000046a8 0x0000000100353140
0x100004690: 0x000000010112e490 0x0002802800000003
(lldb) p/x 0x00000001000046a8+0x20 //通过元类首地址isa偏移32字节得出元类bits
(long) $1 = 0x00000001000046c8 //后面就是重复工作了
(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x00000001000046c8
(lldb) p $2->data()
(class_rw_t *) $3 = 0x000000010112e430
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2684878849
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4294984984
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff8e9fbc60
}
(lldb) p $3.methods()
(const method_array_t) $5 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100004560
      }
      arrayAndFlag = 4294985056
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->methods()
(lldb) p $5.list
(const method_list_t_authed_ptr<method_list_t>) $7 = {
  ptr = 0x0000000100004560
}
(lldb) p $7.ptr
(method_list_t *const) $8 = 0x0000000100004560
(lldb) p *$8
(method_list_t) $9 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $9.get(0).big
(method_t::big) $10 = {
  name = "say666"//一把鼻涕一把泪啊 终于出现了
  types = 0x0000000100003f77 "v16@0:8"
  imp = 0x0000000100003e00 (KCObjcBuild`+[YCXPerson say666])
}
  Fix-it applied, fixed expression was: 
    $9.get(0).big()
(lldb) 

由于调试内容过多,过程有点绕,讲真的,我测试的时候错了好多次,快晕了,还好最终还是得出了结果我就把调过代码拷贝下来,以便更好查看,解决一个问题了,那么下一个

2.寻找成员变量

在前面我们提到过properties(),里面是有成员变量的信息,那么我们在去看看相关的参数

在这里插入图片描述

在这里插入图片描述
接下来就开始手动调试

(lldb) x/6gx p1
0x10072c240: 0x011d800100004681 0x0000000000000000
0x10072c250: 0x00000001000040b0 0x0000000000000000
0x10072c260: 0x011dffff8e9f5311 0x61636f4c796c6e4f
(lldb) p (class_data_bits_t *)0x10072c260
(class_data_bits_t *) $2 = 0x000000010072c260
(lldb) p $2->data()
(class_rw_t *) $3 = 0x00007fff8e9f5310
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2392806200
  witness = 32767
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 140735586206672
    }
  }
  firstSubclass = 0x000000010072c600
  nextSiblingClass = 0x0001802000000003
}
(lldb) p $4.ro()
(const class_ro_t *) $5 = 0x00007fff8e9f83d0
(lldb) p $5->ivars
(const ivar_list_t *const) $6 = 0x0000000100353140
(lldb) p *$6
(const ivar_list_t) $7 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 3485936, count = 1)
}
(lldb) p $7.get(0)
(ivar_t) $8 = {
  offset = 0x0000000000000000
  name = 0x000000010072c680 "\364E\241z
  type = 0x0001801000000003 ""
  alignment_raw = 25182468
  size = 1
}
(lldb) 

推荐续文cache结构分析

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值