OC本质底层实现转化其实都是C/C++代码。
OC对象的本质就是结构体。
NSObject底层是struct objc_object结构体 ;
struct objc_class : objc_object {
…省略无关代码
// Class ISA; //ISA(从objc_object继承过来的)
Class superclass; //指向其父类
cache_t cache; //缓存
class_data_bits_t bits; //类的数据
…省略无关代码
}
objc_class是继承objc_object的。
- 每个类的底层都会有一个
Class
类的isa
指针。 Class
底层是struct objc_class *
类型,NSObject
底层是struct objc_object
结构体,id
底层是struct objc_object *
类型。
NSObject
底层实现的结构体里只有一个成员变量isa
,又因为Class
底层是struct objc_class *
类型,所以 ***\*NSObject\****
*的本质是****\*objc_class\****
。
可以看到getter
和setter
里是通过****首地址指针+对应成员变量的地址值指针的偏移量*的方式取和存的,最终通过(*(NSString **)
还原为string
类型。取值的过程就是:*先拿到当前成员变量的地址,再去取这个地址里面所存的值****。
objc_class 和 objc_object 有什么关系?
objc_object
objc_object
是一个结构体,它代表了一个Objective-C中的对象实例。这个结构体中最重要的成员是isa
指针,它指向对象所属的类(objc_class
类型的指针)。isa
的存在使得运行时系统能够识别对象的类型,并调用正确的类方法或实例方法。
objc_class
objc_class
也是一个结构体,它描述了一个类的特性,包括其父类、方法列表、属性列表、协议列表、成员变量列表等。每个类都有一个对应的objc_class
实例,这个实例包含了该类的全部元数据和行为信息。
objc_class
继承自objc_object
,也就是说objc_class
本身也是objc_object
的一种,具有isa
指针。这意味着类本身也可以被视为对象,能够接收消息。此外,objc_class
的isa
指针指向的是它的元类(Meta-Class),而元类负责存储类方法。
关系总结
objc_object
是Objective-C中所有对象的基础结构,它允许运行时系统识别和操作对象。objc_class
描述了一个类的结构和行为,它本身也是objc_object
的一种特殊形式,意味着类也可以被当作对象来对待。- 每个
objc_object
实例都有一个isa
指针,指向其所属的objc_class
。 - 每个
objc_class
实例也有一个isa
指针,但它指向的是该类的元类,元类中包含了类方法。
通过这种方式,Objective-C实现了动态类型和动态绑定,允许在运行时根据对象的实际类型调用适当的方法。这种机制是Objective-C动态特性的基石,也是其与静态类型语言的主要区别之一。
补充:
objc_class
继承自objc_object
,objc_object
有isa属性,所以对象都有一个isa,isa 表示指向,来自于当前的objc_object
。
objc_object与对象的关系:
- 所有对象都是以
objc_object
为模板继承过来的。 - 所有对象都来自于NSObject,但是其底层是一个
objc_object
的结构体类型,所以objc_object
与对象的关系是继承关系。
类结构
上面是runtime中的类结构关系图 ;
类是一个结构体,大致如下:
struct objc_class : objc_object {...省略无关代码// Class ISA; //ISA(从objc_object继承过来的)Class superclass; //指向其父类cache_t cache; //缓存class_data_bits_t bits; //类的数据class_rw_t *data() const {return bits.data();}void setData(class_rw_t *newData) {bits.setData(newData);}...省略无关代码
}
继承自objc_object
的isa指针,不仅实例对象中有,类对象中也有,占8字节。
isa指针的指向
对于每个类创建出来的对象都会默认有一个isa
属性,保存类对象的地址,也就是class
,通过class
就可以查询到这个对象的属性和方法,协议等;
对于类来说,它的类对象就是其元类 ;
isa分两种类型(isa指针是什么含义的时候):
- 指针型isa:64位的0或者1的整体内容代表所指向的Class的地址,也就是可以通过isa的内容来获得类对象的地址。
- 非指针型isa:isa的值的部分代表Class的地址,之所以这样是因为我们在寻址过程中,只有三四十位数就可以保证我们寻找到所有Class地址了,多出来的位可以用来存储其他相关内容,来达到节省内存的目的。
isa的数据结构
每个OC对象都含有一个isa
指针,__arm64__
之前,isa
仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__
架构之后,apple对isa
进行了优化,变成了一个共用体(union)结构,同时使用位域来存储更多的信息。
union isa_t
{Class cls;uintptr_t bits;struct {uintptr_t nonpointer : 1;//->表示使用优化的isa指针uintptr_t has_assoc : 1;//->是否包含关联对象uintptr_t has_cxx_dtor : 1;//->是否设置了析构函数,如果没有,释放对象更快uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->类的指针uintptr_t magic : 6;//->固定值,用于判断是否完成初始化uintptr_t weakly_referenced : 1;//->对象是否被弱引用uintptr_t deallocating : 1;//->对象是否正在销毁uintptr_t has_sidetable_rc : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1uintptr_t extra_rc : 19; //->存储引用计数,实际的引用计数减一,存储的是其对象以外的引用计数};
};
初始化isa(其实就是上面两种不同的isa指针初始化也不一样)
- 通过
cls
初始化:
非nonpointer
,存储着Class
、Meta-Class
对象的内存地址信息。 - 通过
bits
初始化:
nonpointer
,进行一系列的初始化操作。
superclass
superclass指向该对象或该类的父类
cache
引入了bucket_t
(散列表数组),cache_t
哈希表结构,哈希表内部存储的bucket_t
,bucket_t
中存储的是SEL
和IMP
的键值对。
(这里就是消息传递刚开始查找方法所在的缓存)
bits
class_data_bits_t
作为属性bits
的类型,也是个结构体
struct class_data_bits_t {friend objc_class;//这里声明objc_class为class_data_bits_t的友元类,使得objc_class可以访问class_data_bits_t中的私有方法uintptr_t bits;......
结构体中的属性bits是无符号整数类型,能够存储指针。这通常意味着它与指针的大小相同。
这里面其实利用位域存储数据,简单来说因为这个bits跟指针大小相同,在iOS中指针大小是8个字节,也就是64位,但是通常单一数据无法用满这64位的,会有很多空位,造成空间浪费。通过位运算,把不同的数据按不同的位置存进这64位内存空间的不同位置里,这样就可以提高内存利用率。这里的bits其实就是这样的,他不只存储class_rw_t结构体指针,还存储其他的信息。所以这里的bits并不是结构体,而是一个无符号整数类型的指针 ;
对于类的方法列表,成员列表的等信息则存放在bits更深一层的class_rw_t指针里面
class_rw_t
protocols类分类中的协议
properties类分类中的属性
methods类分类中的方法
这三个数据结构是个二维数组
假如我们三个分类A、B、C,编译顺序A->B->C。这时会逆序遍历并打包成分类数组,分类C中的所有方法都在第一列竖列表中,存在二维数组的第1项。分类B中的所有方法都在第二列竖列表中,存在二维数组的第2项。分类A中的所有方法都在第三列竖列表中,存在二维数组的第3项。
总结:这个二维数组的每一项对应一个category所增加的方法 ;结合之后了解到的,也可以知道,这个二微数组中的方法都是在运行时动态添加的
下面是有关class_rw_t的源码:
struct class_rw_t {// Be warned that Symbolication knows the layout of this structure.uint32_t flags;uint16_t witness;
#if SUPPORT_INDEXED_ISAuint16_t index;
#endifexplicit_atomic<uintptr_t> ro_or_rw_ext;Class firstSubclass;Class nextSiblingClass;
private:using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;const ro_or_rw_ext_t get_ro_or_rwe() const {return ro_or_rw_ext_t{ro_or_rw_ext};}void set_ro_or_rwe(const class_ro_t *ro) {ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed);}void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {// the release barrier is so that the class_rw_ext_t::ro initialization// is visible to lockless readersrwe->ro = ro;ro_or_rw_ext_t{rwe}.storeAt(ro_or_rw_ext, memory_order_release);}class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
public:void setFlags(uint32_t set){__c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);}void clearFlags(uint32_t clear) {__c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);}// set and clear must not overlapvoid changeFlags(uint32_t set, uint32_t clear) {ASSERT((set & clear) == 0);uint32_t oldf, newf;do {oldf = flags;newf = (oldf | set) & ~clear;} while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));}class_rw_ext_t *ext() const {return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>();}class_rw_ext_t *extAllocIfNeeded() {auto v = get_ro_or_rwe();if (fastpath(v.is<class_rw_ext_t *>())) {return v.get<class_rw_ext_t *>();} else {return extAlloc(v.get<const class_ro_t *>());}}class_rw_ext_t *deepCopy(const class_ro_t *ro) {return extAlloc(ro, true);}const class_ro_t *ro() const {auto v = get_ro_or_rwe();if (slowpath(v.is<class_rw_ext_t *>())) {return v.get<class_rw_ext_t *>()->ro;}return v.get<const class_ro_t *>();}void set_ro(const class_ro_t *ro) {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {v.get<class_rw_ext_t *>()->ro = ro;} else {set_ro_or_rwe(ro);}}######const method_array_t methods() const {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {return v.get<class_rw_ext_t *>()->methods;} else {return method_array_t{v.get<const class_ro_t *>()->baseMethods()};}}const property_array_t properties() const {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {return v.get<class_rw_ext_t *>()->properties;} else {return property_array_t{v.get<const class_ro_t *>()->baseProperties};}}const protocol_array_t protocols() const {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {return v.get<class_rw_ext_t *>()->protocols;} else {return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};}}
};
对于最后三个方法,我们可以从名称大致猜到他们和保存的方法列表,属性列表和协议列表有关 ;
以const method_array_t methods()
为例,获取类的实例方法也是通过调用methods()
来获取的。比如说runtime
函数class_copyMethodList
,其代码如下:
Method *
class_copyMethodList(Class cls, unsigned int *outCount)
{unsigned int count = 0;Method *result = nil;if (!cls) {if (outCount) *outCount = 0;return nil;}mutex_locker_t lock(runtimeLock);const auto methods = cls->data()->methods(); /ASSERT(cls->isRealized());count = methods.count();if (count > 0) {result = (Method *)malloc((count + 1) * sizeof(Method));count = 0;for (auto& meth : methods) {result[count++] = &meth;}result[count] = nil;}if (outCount) *outCount = count;return result;
}
对于上面的const auto methods = cls->data()->methods();,这里调用了methods()方法获取了方法列表 ,也就是class_rw_t中的const method_array_t methods() 方法 ;但我们在观察class_rw_t时却在其结构中找不到存储方法列表的属性,从const method_array_t methods() 方法 中我们可以发现对方法列表的读取是在class_rw_ext_t和class_ro_t指针中进行的 ;
class_rw_ext_t
class_rw_ext_t
是个结构体,其数据结构如下:
struct class_rw_ext_t {const class_ro_t *ro;method_array_t methods;property_array_t properties;protocol_array_t protocols;char *demangledName;uint32_t version;
};
由此可知,结构体class_rw_ext_t
中的确有方法列表、属性列表和协议等信息,而且还有一个class_ro_t
指针,接下来继续看class_ro_t
。
class_ro_t
以下是class_ro_t
的结构体:
struct class_ro_t {uint32_t flags;uint32_t instanceStart;uint32_t instanceSize;
#ifdef __LP64__uint32_t reserved;
#endifconst 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;// This field exists only when RO_HAS_SWIFT_INITIALIZER is set._objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];_objc_swiftMetadataInitializer swiftMetadataInitializer() const {if (flags & RO_HAS_SWIFT_INITIALIZER) {return _swiftMetadataInitializer_NEVER_USE[0];} else {return nil;}}method_list_t *baseMethods() const {return baseMethodList;}class_ro_t *duplicate() const {if (flags & RO_HAS_SWIFT_INITIALIZER) {size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);class_ro_t *ro = (class_ro_t *)memdup(this, size);ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];return ro;} else {size_t size = sizeof(*this);class_ro_t *ro = (class_ro_t *)memdup(this, size);return ro;}}
};
这里面我们发现class_ro_t不仅有baseMethodList、 baseProtocols、baseProperties等信息,还有成员变量const ivar_list_t * ivars等其他信息。在了解他们的结构之后,我们从结构上简单做一下它们之间关系的分析:
-
首先class_rw_t有一个指针ro_or_rw_ext,ro_or_rw_ext指向可能是class_rw_ext_t或者是class_ro_t。ro_or_rw_ext指针的作用是,当读取类相关信息时,会优先判断是否指向class_rw_ext_t,如果是class_rw_ext_t就从它读取,没有就是class_ro_t,直接从class_ro_t读取。
那么在程序运行中到底ro_or_rw_ext什么时候是class_rw_ext_t,什么时候是class_ro_t呢?
class_ro_t & class_rw_t & class_rw_ext_t & Class之间的关系
从字面意思解读:class_ro_t
中的“ro”代表只读;class_rw_t
中的“rw”代表可读可写;class_rw_ext_t
中的“rw_ext”代表可读可写的扩展。
干净内存和脏内存:干净内存内存指的是内存一旦加载就不会被改变。反之,脏内存就是在运行时会被修改的。
Class本身是运行时加载的,在运行时会被改变,所以本身Class就是属于脏内存。那么如果想要获取Class的干净内存,也就是编译时确定的数据结构包括方法列表、成员变量等的,该怎么办?
这其实就是class_ro_t的作用。因为class_ro_t是只读,意味着 class_ro_t是从mach-o读取类的数据之后,就不会被改变。那如果我们想在运行时修改类的信息,比如添加方法,比如加载category怎么办呢?那这时候就有一个与之对应的class_rw_t结构,class_rw_t可以在运行时存储类的信息,可读可写的,可以在运行时修改。
说到这里,好像还漏掉一个结构class_rw_ext_t,这个东西又是干什么用的呢?存在的意义是什么?其实还是跟运行时有关。实际上在我们的app运行中,需要运行时修改的类是非常少的,据统计平均大概就10%左右。那也就是说大部分只需要读取class_ro_t中的数据就够了,少部分才需要修改。因此才会有class_rw_ext_t这个扩展的结构体。class_rw_ext_t的作用是这样的:当我们需要修改类结构时,比如添加方法、加载category等时,class_rw_t回去开辟一个额外的空间rwe(class_rw_ext_t),用于存储新的方法和class_ro_t中的方法等信息。这样做的目的有一个好处就是,对于绝大部分类是不需要这个开辟class_rw_ext_t这个结构体,节省内存。
创建时机
*那这几个结构分别是什么时候创建的呢?这里就设计到类的加载流程。首先类在app启动的时候会被映射到内存,这时候会先创建Class(objc_class)结构,然后把类编译时的类数据映射到class_ro_t,class_ro_t结构体指针存储到Class的bits指针中,我们前面提到类的bits中存储的是class_rw_t指针,实际上在类初始化之前这里存储的是class_ro_t,等到类初始化的时候会创建一个class_rw_t结构,然后通过data()从bits中读取class_ro_t,然后class_rw_t通过set_ro(const class_ro_t ro)把指针ro_or_rw_ext指向这个class_ro_t,然后Class通过setData()把class_rw_t指针存储到bits里面。然后在运行时根据需要,根据extAllocIfNeeded或extAlloc创建class_rw_ext_t,然后把class_ro_t关联到class_rw_ext_t
上面的解读也可以说明下面的一些问题了:
category不能添加成员变量的原因
因为category
是运行时添加的,他只能操作class_rw_t
结构,但是class_rw_t
结构没有添加成员变量的入口。成员变量是存储在class_ro_t
中的,是无法被修改的,所以category
就不能添加成员变量。
类数据的存储结构
对于上面的class_rw_t中实际上找不到对应的methods属性,所以更像是调用方法从而直接获取对应的数组,同理其他也应该是这样的 ;
继承者链
示例:
- 继承自
NSObject
的类LGPerson
: - 继承自
LGPerson
的类LGTeacher
: - 在
main
中分别用两个定义两个对象:person
和teacher
:
int main(int argc, const char * argv[]) {@autoreleasepool {LGPerson *person = [[LGPerson alloc] init];LGTeacher *teacher = [[LGTeacher alloc] init];NSLog(@"Hello, World! %@ - %@", person, teacher);}return 0;
}
- 在
main
中LGTeacher
部分加一个断点,运行程序。 - 开启
lldb
调试,调试的过程如下图所示。
0x0100000100008239是person对象的isa指针地址,其&后得到的结果是创建person的类LGPerson。
0x0000000100008210是isa中获取的类信息所指的类的isa的指针地址,即LGPerson类的类的isa指针地址,在Apple中,我们简称LGPerson类的类为元类。
所以,两个打印都是LGPerson的根本原因就是因为元类导致的。
元类是什么
- 我们都知道对象的isa是指向类,类的其实也是一个对象,可以称为类对象,其isa的位域指向苹果定义的元类。
- 元类是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自于元类。
- 元类是类对象 的类,每个类都有一个独一无二的元类用来存储类方法的相关信息。
- 元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称。
简单的总结一下,元类是没有名称的类(使用了同类名一样的名称),是类对象的isa指针所指向的类 ;
对象 --> 类 --> 元类 --> NSobject,NSObject指向自身。
对于NSobject,NSObject
(根元类)在内存中永远只存在一份,由于类的信息在内存中永远只存在一份,所以类对象只有一份。
著名的isa走位 & 继承关系图
isa走位
实例对象(Instance of Subclass)的isa指向类(class)
类对象(class)的isa指向元类(Meta class)
元类(Meta class)的isa指向根元类(Root metal class)
根元类(Root metal class)的isa指向它自己本身,形成闭环,这里的根元类就是NSObject
superclass走位
类之间的继承关系:
类(subClass)继承自父类(superClass)
父类(superClass)继承自根类(RootClass),此时的根类是指NSObject
根类继承自nil,所以根类即NSObject可以理解为万物起源,即无中生有
元类也存在继承,元类之间的继承关系如下:
子类的元类(metal SubClass)继承自父类的元类(metal SuperClass)
父类的元类(metal SuperClass)继承自根元类(Root metal Class)
根元类(Root metal Class)继承于根类(Root class),此时的根类是指NSObject
【注意】实例对象之间没有继承关系,类之间有继承关系。
对象方法,属性,成员变量,协议信息 存放在class对象中。
类方法,存放在meta-class对象中。
成员变量的具体值,存放在instance对象中。
https://blog.csdn.net/m0_55124878/article/details/125808836