IOS类的分析

304 阅读6分钟

IOS类的相关分析

本文章将对IOS类的相关知识点进行分析,主要是基于objc源码在macOS系统中对ios的类结构进行底层分析。分为四个部分:1.类的结构分析 2.方法的存储(get/set方法生成规则) 3.类方法的存储 4.cache的结构分析,源码方面还是使用obj4-818.在我之前的文章中可以找到。

类的结构分析

首先先上一张苹果官方的一张类结构对,针对这张图我们进行类结构分析,去验证类的结构

isa流程图.png 我们创建一个Teacher —> Person -> NSOjbect这个的一个继承结构。并且添加了属性和实现一些方法

截屏2021-09-23 下午2.09.34.png

截屏2021-09-23 下午2.09.25.png

对象在alloc申请内存的时候,会走到initIsa函数中去初始化ISA,在初始化isa的时候会调用set方法将它的类对象赋值进去。

截屏2021-09-23 下午2.20.59.png 通过代码跟踪在调用calloc的时候Class传入的就是Teacher的类对象

截屏2021-09-23 下午2.29.40.png Class是一个结构体的objc_class,而objc_class是继承了objc_object。去掉不需要关注的内容,具体的结构体内容如下:

struct objc_class : objc_object {
    isa_t isa //来自于objc_object的一个属性
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

struct objc_object {
private:
    isa_t isa;
}

第一个阶段证实Teacher对象里面的ISA指向的是Teacher类,Teacher对象是继承了Person

**(lldb) p/x LGTeacher.class
**(Class) $6 = 0x0000000100008450 LGTeacher //类对象的地址
**(lldb) p/x 0x011d800100008455 & 0x00007ffffffffff8ULL //对象的ISA与过之后获取到的值
**(unsigned long long) $7 = 0x0000000100008450
**(lldb) po 0x0000000100008450
**LGTeacher

第二阶段验证Teacher类的ISA指向元类

(lldb) x/4gx LGTeacher.class
0x100008450: 0x0000000100008478 0x00000001000084c8
0x100008460: 0x00000001003623b0 0x0000803c00000000
(lldb) p/x 0x0000000100008478 & 0x00007ffffffffff8ULL
(unsigned long long) $11 = 0x0000000100008478
(lldb) po 0x0000000100008478  -- 元类
LGTeacher

第三阶段依据objc_class的机构,isa占8个字节,下一个就是superclass,验证Teacher类的父类是Person

(lldb) x/4gx 0x0000000100008450
0x100008450: 0x0000000100008478 0x00000001000084c8
0x100008460: 0x00000001003623b0 0x0000803c00000000
(lldb) po 0x00000001000084c8
LGPerson

第四阶段,Teacher这一层验证完毕里,再验证Teacher对应的元类里面是父级的元类

(lldb) x/4gx 0x0000000100008478 --Teacher对应的元类
0x100008478: 0x000000010036a0f0 0x00000001000084a0
0x100008488: 0x0000000101304aa0 0x0005e03500000007
(lldb) p/x 0x000000010036a0f0 & 0x00007ffffffffff8ULL
(unsigned long long) $18 = 0x000000010036a0f0 -- Teacher元类的ISA是根元类
(lldb) po 0x000000010036a0f0
NSObject

(lldb) p/x NSObject.class
(Class) $21 = 0x000000010036a140 NSObject类
(lldb) x/4gx 0x000000010036a140
0x10036a140: 0x000000010036a0f0 0x0000000000000000
0x10036a150: 0x0000000100778eb0 0x0002801000000003
(lldb) po 0x000000010036a0f0  --- NSObject类对应的元类 也就是根元类
NSObject

第五阶段,验证person类的元类,person的元类指向根元类,父类指向NSObject

(lldb) p/x 0x00000001000084c8 -- LGPerson类
(long) $23 = 0x00000001000084c8
(lldb) x/4gx 0x00000001000084c8
0x1000084c8: 0x00000001000084a0 0x000000010036a140
0x1000084d8: 0x00000001003623b0 0x0000803400000000
(lldb) p/x 0x00000001000084a0 & 0x00007ffffffffff8ULL
(unsigned long long) $24 = 0x00000001000084a0 --LGPerson类的元类
(lldb) po 0x000000010036a140  -- LGPerson类的父类
NSObject
(lldb) x/4gx 0x00000001000084a0
0x1000084a0: 0x000000010036a0f0 0x000000010036a0f0
0x1000084b0: 0x000000010160eef0 0x0002e03500000003
(lldb) po 0x000000010036a0f0 -- LGPerson类的根元类
NSObject

第六阶段验证NSObject类的父类是nil

(lldb) x/4gx 0x000000010036a140 -- NSObject类
0x10036a140: 0x000000010036a0f0 0x0000000000000000
0x10036a150: 0x0000000100778eb0 0x0002801000000003

方法和属性的存储

在类的结构中有cache和bits,分别都存的时候东西?对象开辟空间所需要的属性方法都存在哪里? 带着疑问从bits开始入手.class_data_bits_t结构体里面并没有什么属性,但有个关键方法

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    class_rw_t结构中发现了class_ro_t class_rw_ext_t
    class_ro_t结构中发现了property_list_t ivar_list_t
    class_rw_ext_t结构体中发现了method_array_t

通过LLDB调试找到了类的成员变量和属性

(lldb) x/6gx LGPerson.class
0x1000084c8: 0x00000001000084a0 0x000000010036a140
0x1000084d8: 0x00000001003623b0 0x0000803400000000
0x1000084e8: 0x000000010160e4f4 0x00000001000b9980
(lldb) p/x 0x1000084e8
(long) $37 = 0x00000001000084e8
(lldb) p (class_data_bits_t *)0x00000001000084e8
(class_data_bits_t *) $38 = 0x00000001000084e8
(lldb) p $38->data()
(class_rw_t *) $39 = 0x000000010160e4f0
(lldb) p *$39
(class_rw_t) $40 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000576
    }
  }
  firstSubclass = LGTeacher
  nextSiblingClass = NSUUID
}
(lldb) p $39.ro()
(const class_ro_t *) $43 = 0x0000000100008200
  Fix-it applied, fixed expression was: 
    $39->ro()
(lldb) p *$43
(const class_ro_t) $44 = {
  flags = 388
  instanceStart = 8
  instanceSize = 40
  reserved = 0
   = {
    ivarLayout = 0x0000000100003e22 "\U00000011\U00000011"
    nonMetaclass = 0x0000000100003e22
  }
  name = {
    std::__1::atomic<const char *> = "LGPerson" {
      Value = 0x0000000100003e19 "LGPerson"
    }
  }
  baseMethodList = 0x0000000100008248
  baseProtocols = nil
  ivars = 0x0000000100008340
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000083c8
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $44.ivars
(const ivar_list_t *const) $45 = 0x0000000100008340
(lldb) p *$45
(const ivar_list_t) $46 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 4)
}
(lldb) p $46.get(0)  //拿到成员变量
(ivar_t) $47 = {
  offset = 0x0000000100008430
  name = 0x0000000100003eca "_age"
  type = 0x0000000100003f5d "i"
  alignment_raw = 2
  size = 4
}
(lldb) p $43.baseProperties  //class_ro_t结构体中的baseProperties记录了属性的描述
(property_list_t *const) $51 = 0x00000001000083c8
  Fix-it applied, fixed expression was: 
    $43->baseProperties
(lldb) p *$51
(property_list_t) $52 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 4)
}
(lldb) p $52.get(0)
(property_t) $53 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $39.properties() //class_rw_t里面的properties记录了属性的描述
(const property_array_t) $57 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x00000001000083c8
      }
      arrayAndFlag = 4295001032
    }
  }
}
  Fix-it applied, fixed expression was: 
    $39->properties()
(lldb) p $57.list 
(const RawPtr<property_list_t>) $58 = {
  ptr = 0x00000001000083c8
}
(lldb) p $58.ptr
(property_list_t *const) $59 = 0x00000001000083c8
(lldb) p *$59
(property_list_t) $60 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 4)
}
(lldb) p $60.get(0)
(property_t) $61 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")

当前属性已经找到了在类的位置,下面找方法class_rw_t里面的methods是空的。是因为method_t结构体成员变量在big结构体里面。

(lldb) p $39.methods
(const method_array_t) $62 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008248
      }
      arrayAndFlag = 4295000648
    }
  }
}
  Fix-it applied, fixed expression was: 
    $39->methods()
(lldb) p $62.list
(const method_list_t_authed_ptr<method_list_t>) $63 = {
  ptr = 0x0000000100008248
}
(lldb) p $63.ptr
(method_list_t *const) $64 = 0x0000000100008248
(lldb) p *$64
(method_list_t) $65 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 10)
}
(lldb) p $65.get(0)
(method_t) $66 = {}
(lldb) p $65.get(1)
(method_t) $67 = {}
(lldb) p $65.get(2)
(method_t) $68 = {}

(lldb) p $65.get(0).big //在big里面
(method_t::big) $69 = {
  name = "saySomething"
  types = 0x0000000100003f4a "v16@0:8"
  imp = 0x0000000100003b50 (KCObjcBuild`-[LGPerson saySomething])
}
  Fix-it applied, fixed expression was: 
    $65.get(0).big()

这里做一个总结:成员变量(实力变量)和属性的区别:属性clang优化后,就变成了_的实例变量,还有getset方法 下面用clang生成cpp看看llvm对属性方法做了怎样的处理。 发现用copy修饰的set方法是滴啊用了objc_setProperty方法。

截屏2021-09-23 下午8.17.36.png copyWithZone 最终调用的reallySetProperty方法可以看到。copy修饰的属性,会调用copyWithZone生成新的newValue。 而strong assign修饰的只做了内存平移 截屏2021-09-23 下午8.22.08.png

类方法的存储

在Person类里面并没有找到+ (void)sayStatic;类方法。不在类方法里就是在元类的method列表中。 验证方式和前面去验证对象的方法一致,在这里不在贴代码了,就贴一个结果出来。

(lldb) p $10.get(0).big
(method_t::big) $11 = {
  name = "sayStatic"
  types = 0x0000000100003f4a "v16@0:8"
  imp = 0x0000000100003b00 (KCObjcBuild`+[LGPerson sayStatic])
}
  Fix-it applied, fixed expression was: 
    $10.get(0).big()

cache的内存结构

贴上一张cache的内存结构图,核心的结构体就是buckets

截屏2021-09-23 下午8.38.19.png

//cache_t结构体
struct cache_t {
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;//8
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied; 
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
}
//找到核心的方法.
struct bucket_t *buckets() const;
(lldb) p/x LGTeacher.class  //类对象
(Class) $34 = 0x0000000100008478 LGTeacher
(lldb) x/6gx 0x0000000100008478
0x100008478: 0x00000001000084a0 0x00000001000084f0
0x100008488: 0x00000001016040e0 0x0003804400000007
0x100008498: 0x0000000100665da4 0x000000010036a0f0
(lldb) p/x (cache_t *)0x100008488
(cache_t *) $35 = 0x0000000100008488
(lldb) p/x $35.buckets  //找到buckets
(bucket_t *) $36 = 0x00000001016040e0
  Fix-it applied, fixed expression was: 
    $35->buckets()
(lldb) p/x $35.buckets()
(bucket_t *) $37 = 0x00000001016040e0
  Fix-it applied, fixed expression was: 
    $35->buckets()
(lldb) p/x $35.buckets()[1]
(bucket_t) $38 = {
  _sel = {
    std::__1::atomic<objc_selector *> = "" {
      Value = 0x00007fff7c384f71 ""
    }
  }
  _imp = {
    std::__1::atomic<unsigned long> = {
      Value = 0x0000000000347b98
    }
  }
}
  Fix-it applied, fixed expression was: 
    $35->buckets()[1]
(lldb) p $38.sel()
(SEL) $39 = "class"
(lldb) p/x $35.buckets()[0]
(bucket_t) $40 = {
  _sel = {
    std::__1::atomic<objc_selector *> = "" {
      Value = 0x0000000100003ee7 ""
    }
  }
  _imp = {
    std::__1::atomic<unsigned long> = {
      Value = 0x000000000000bf48
    }
  }
}
  Fix-it applied, fixed expression was: 
    $35->buckets()[0]
(lldb) p $40.sel()  //找到缓存的执行的方法
(SEL) $41 = "saySomething"
p $40.imp(nil,pClass)

通过对类的结构分析,可以更清晰了解到类的底层原理。如有有错误的地方,欢迎指出,交流。