想成为一名优秀的iOS开发者,对底层的原理学习是必不可少的,笔者整理了一系列有关OC的底层文章,但愿能够帮助到你。这篇文章主要讲解的是类的底层结构分析。编程
2.iOS的OC对象的内存对齐缓存
3.iOS的OC的isa的底层原理bash
开始介绍类的结构以前,请问一下,你在开发的过程当中有没有想过一个问题。就是建立多个相同类型的对象的时候,那么这个对象的类是否是多个呢?带着这个问题,有了以下不一样形式获取到类的代码markdown
Class class1 = [TestJason class]; Class class2 = [TestJason alloc].class; Class class3 = object_getClass([TestJason alloc]); NSLog(@"%p====%p===%p",class1,class2,class3); ===========运行的结果=========== LGTest[1541:33345] 0x100002848====0x100002848===0x100002848 复制代码
从上面的运行结果能够知道,类在内存里面只存在一份。less
仍是使用苹果的objc4-756.2
源码来学习的,具体能够看iOS的OC对象建立的alloc原理这篇文章有介绍。经过Class
的源码post
/// An opaque type that represents an Objective-C class. typedef struct objc_class *Class; struct objc_class : objc_object { // Class ISA; Class superclass; cache_t cache; // 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(); } void setData(class_rw_t *newData) { bits.setData(newData); } .... /// Represents an instance of a class. struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; }; 复制代码
从源码能够知道Class
是一个objc_class
的结构体,而objc_class
是继承自objc_object
的,由于在面向对象编程中万物皆对象,从这里也能够知道,其实类也是对象的。从中能够知道类里面分别有从父类继承的isa
,superclass
,cache_t
和class_data_bits_t类型的bits
。由于类
在底层中最终是编译成结构体的形式,因此你是否是很好奇大部分的基类NSObject
在底层是怎样的形式呢?学习
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ROOT_CLASS OBJC_EXPORT @interface NSObject <NSObject> { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-interface-ivars" Class isa OBJC_ISA_AVAILABILITY; #pragma clang diagnostic pop } 复制代码
经过源码能够知道,NSObject
的底层是与objc_object
是同样的。在平常开发中,定义类的时候都是会有属性,变量和方法的,你是否是很好奇属性,成员变量和方法在类的底层中储存在哪里?接下来,我都会一一介绍。ui
为了方便介绍接下来的内容定义了一个TestObject
的类,下面是这个类的定义和实现,而且经过lldb
的指令来介绍。this
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface TestObject : NSObject{ NSString *nickName; } @property(nonatomic,copy) NSString *name; -(void)sayName; +(void)sayNickName; @end NS_ASSUME_NONNULL_END //实现的代码 TestObject *testObject = [TestObject alloc]; Class tClass = object_getClass(testObject); NSLog(@"%@===%p",testObject,tClass); 复制代码
经过lldb
的指令获得以下:
0x001d800100001659
是
isa
的内存值,
0x0000000100b37140
是
superclass
的,
0x00000001003da290
是
cache_t
的,那么
0x0000000000000000
就是
bits
了吗?这几个值从字面意思能够知道
superclass
是存放父类的,
cache_t
是存放一些缓存的东西(这块内存后续会出一篇文章介绍),那么
bits
应该就是存放咱们须要的属性和实例方法的了。下面是
class_rw_t
的源码
class_rw_t *data() { return bits.data(); } struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint32_t version; const class_ro_t *ro; method_array_t methods; property_array_t properties; protocol_array_t protocols; Class firstSubclass; Class nextSiblingClass; char *demangledName; 复制代码
从上面的lldb
中打印出来的0x0000000000000000
是0的这样的话就是否是说直接打印不出来呢?并非的,咱们能够经过分析类里面的各个属性占的字节大小而后经过内存偏移来找到最终bits
的内存值。
经过源码能够知道objc_class
的内部定义
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
···
}
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
···
}
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
复制代码
经过前面的文章能够知道,isa
是一个联合体(union)占8个字节,superclass
由于是一个类因此也占8个字节,由于cache
是一个结构体类型(struct
),这个大小是根据它里面的属性的容量来决定大小的,由于_buckets
是一个结构体指针
占8字节,mask_t
经过源码能够知道占4字节,因此cache
共占16字节。由isa
到cache
一共是32字节,转为16进制就是0x20
,由上图能够知道,tClass
类的内存值的起始位置是0x100001680
经过内存偏移0x20
能够获得0x1000016a0
,那么这个值就是bits
的内存值。也能够直接用x/5gx tClass
来打印出来,获得的内存值也是0x1000016a0
bits
里面的
data()
能够获得
class_rw_t
,而
class_rw_t
能够获得属性的值,因此经过
lldb
的指令能够打印
class_rw_t
里面所指向的值。如下是内存偏移到
0x1000016a0
打印出来的结果
class_rw_t
的
properties
里面,能够经过
properties
里面的
list
获得
property_list_t
的数组,最终打印了属性
name
$7
这个数组来找的时候,发现是找不到
TestObject
类的
nickName
这个成员变量的,这时候能够看一下
class_rw_t
里面的
const class_ro_t *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; method_list_t *baseMethods() const { return baseMethodList; } }; 复制代码
这个ro
里面也有method_list_t
,protocol_list_t
和property_list_t
,其中多了ivar_list_t
。经过lldb
的指令最终找到了成员变量nickName
。
TestObject
中有成员变量的数量为2个,其中第一个是
nickName
,这时候就有点奇怪了,咱们不是只在
TestObject
中只定义了一个
nickName
这个成员变量吗?多出来的一个是什么呢?原来在类中定义的属性也是会被定义为带下划线的成员变量的,经过
lldb
能够看到
而且在rw
中的method_array_t
和ro
中的method_list_t
均可以经过获得TestObject
的方法,以下是经过lldb
的指令来获取rw
中的方法
从中能够知道类TestObject
有方法数为4个,而且第一个的方法是cxx_destruct
,另外的两个方法会不会是咱们在类中定义的实例方法sayName
和类方法sayNickName
,那么仍是多出来了一个方法数量,经过lldb
打印出来的
其实就是定义的属性name
的getter和setter方法的两个方法和sayName
的实例方法以及一个系统的cxx_destruct
方法,并无sayNickName
这个类方法。这是为何呢?
注意:若是实例方法只是在类的`.h`文件声明了,可是并无在`.m`文件中实现的话,是不在`rw`和`ro`里面的
思考一个问题:为何成员变量会存在
class_ro_t
里面不存在class_rw_t
里面呢?class_rw_t
与class_ro_t
有什么不同呢?
经过上面的能够知道类方法是不存在类里面的,是存在元类
里面的,能够经过lldb
指令来查找,先找到当前的类的isa
,再经过isa
&ISA_MASK
能够获得元类
。
0x0000000100002700
就是
元类
的内存值,再经过
x/5gx
能够获得
元类
中的
class_data_bits_t
,此时就至关于走一遍上面介绍过的查找
bits
里面的方法的流程
bits
的方法中能够查找到
sayNickName
这个类方法。
从上面的介绍能够知道类在底层中的结构分别是isa
,superclass
,cache_t
和bits
,其中它们所占的大小是不同的。bits
里面的rw
有保存着实例方法
和属性
,可是并无包含成员变量
,rw
中包含着ro
。ro
中保存着实例方法
,属性
和成员变量
。成员变量
和属性
都存在ivar
里面,而且属性
是如下划线_
的形式存在的。类的类方法是存在元类的bits
的方法里面。至此,有关类的结构分析就介绍完毕了。