基础知识
联合体
1.联合体可以定义多个不同类型的成员,联合体的内存大小由其中最大的成员的大小决定。
2.联合体中修改其中的某个变量会覆盖其他变量的值。
3.联合体所有的变量公用一块内存,变量之间互斥
联合体优缺点
优点:内存使用更为灵活,节省内存。
缺点:不够包容。
位域(Bit field)
有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。就可以用位域![图片[1]-ios 底层原理 02-对象的本质&isa关联类-一一网](https://www.proyy.com/skycj/data/images/2021-06-22/67242665c9df039e38ad2d620f43a74f.jpg)
总结:Car1的内存大小是4字节,Car2的内存大小是1字节。1字节包含8位(bit),Car2中所有变量都是按 位(bit)存在这1字节中,具体的存放格式是从右往左往 0000 1111 依次存放front 、back、left 、right
clang 是什么
Clang 是一个C语言、C++、Objective-C语言的轻量级编译器,是由Apple主导编写的
Clang 主要用于把源文件编译成底层文件,比如把main.m 文件编译成main.cpp、main.o或者可执行文件。便于观察底层的逻辑结构,便于我们探究底层。
clang 常见命令
clang -rewrite-objc main.m -o main.cpp
//UIKit报错
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
// xcrun命令基于clang基础上进行了封装更好用
//3、模拟器编译
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
//4、真机编译
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp
复制代码
对象的本质
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
// 对象在底层的本质就是结构体
@interface LGPerson : NSObject
@property (nonatomic, strong) NSString *KCName;
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
复制代码
用clang命令把 main.m 文件编译成 main.cpp
重要的源码如下
// NSObject
// objc : objc_object
#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_kcName;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS; // ISA
NSString *_KCName;
};
// @property (nonatomic, strong) NSString *kcName;
/* @end */
// @implementation LGPerson
// 方法 getter
static NSString * _I_LGPerson_kcName(LGPerson * self, SEL _cmd) {
return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_kcName))
}
static void _I_LGPerson_setKcName_(LGPerson * self, SEL _cmd, NSString *kcName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_kcName)) = kcName; }
// @end
复制代码
源码分析:LGPerson底层是结构体,LGPerson_IMPL结构体中有2个变量,其中KCName是自定义的属性,NSObject_IVARS就是 NSObject中isa。LGPerson 继承于NSObject,意味着LGPerson也有NSObject所有的成员变量。
NSObject的底层是NSObject_IMPL里面只有一个成员变量是Class isa。通常叫isa叫做isa指针,那么这里的Class应该是个指针类型,在main.cpp文件中全局搜索*Class。代码如下
//对象的底层实现
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
typedef struct objc_object *id;
typedef struct objc_selector *SEL;
};
复制代码
源码分析:
Class本质上是一个 objc_class类型的结构体指针,objc_class是所有类的底层实现。所以isa跟类信息应该有一些具体的关联
NSObject的底层实现和对象的底层实现有什么区别?两者成员变量的结构体都是Class isa, 所有对象的底层都是继承objc_object,在OC中基本上所有的对象都是继承NSObject,但是真正的底层实现是objc_object的结构体类型。
对象的本质补充
我们看到上面有 id 跟 SEL常用的id原来是一个objc_object结构体指针,这就解释了id修饰变量和作为返回值的时候为什么不加*,意料之外的是SEL也是结构体指针。
在前面的代码里面有
// 方法 getter
static NSString * _I_LGPerson_kcName(LGPerson * self, SEL _cmd) {
return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_kcName))
}
// 方法 setter
static void _I_LGPerson_setKcName_(LGPerson * self, SEL _cmd, NSString *kcName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_kcName)) = kcName; }
// @end
复制代码
(char *)self + OBJC_IVAR_$_LGPerson$_kcName获取当前变量的位置代码,怎么去获取当前的变量呢?底层实现就是当前对象的首地址+变量的偏移值代码中只定义了一个局部变量 kcName,但是底层代码仅添加了一个变量。而定义的属性,底层自动添加了带_变量以及get和set方法的实现
isa 结构
通过alloc流程 alloc –> _objc_rootAlloc –> callAlloc –> _objc_rootAllocWithZone –> _class_createInstanceFromZone,断点在 obj->initInstanceIsa,进入obj->initInstanceIsa
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
复制代码
进入initIsa
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0); // isa初始化
if (!nonpointer) {
newisa.setClass(cls, this);//如果是纯指针 isa被直接cls赋值
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
isa = newisa;
}
复制代码
进入isa_t
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
复制代码
源码分析:isa_t是联合体。isa_t有两个变量 一个是bits,一个是cls。通过上面分析联合体是互斥的,那就意味着初始化isa有两种方式:
bits被赋值,cls 没有值或者被覆盖
cls 被赋值,bits没有值或者被覆盖isa_t中还有一个结构体成员变量ISA_BITFIELD。这是一个宏对应有两个端,一个是__arm64__(iOS),一个是__x86_64__(macOS)。ISA_BITFIELD通过位域存储信息

| 变量名 | 值的含义 |
|---|---|
| nonpointer | 表示是否对isa指针开启指针优化 。0:纯isa指针;1:不只是类对象地址,isa中包含了类信息,对象的引用计数等等。 |
| has_assoc | 关联对象标志位 0:没有;1:存在。 |
| has_cxx_dtor | 该对象是否有C++或者Objc的析构器,如果有析构函数,则需要坐析构逻辑,如果没有,则可以更快的释放对象。 |
| shiftcls | 存储类指针的值。开启指针优化的情况下,在arm64架构中,有33位用于存储类指针。 |
| magic | 用于调试器判断当前对象是真的对象,还是没有初始化的空间。 |
| weakly_referenced | 用于表示对象是否被指向或曾将指向一个ARC的若变量,没有弱引用的对象可以更快释放。 |
| deallocating | 标志对象是否正在释放内存。 |
| has_sidetable_rc | 当对象引用计数大于10的时候,则需要家用该变量存储进位。 |
| extra_rc | 表示该对象的引用计数数值,实际上是引用计数值减1。例如:如果对象的引用计数为10,那么extra_rc为9;如果引用计数大于10,则需要用到上面的has_sidetable_rc。 |
isa结构总结
isa结构分析总结
isa分为nonpointer类型和非nonpointer。非nonpointer类型只是一个纯指针,nonpointer还包含了类的信息
isa是联合体+位域的方式存储信息的。采用这种方式的有点就是节省大量内存。万物皆对象,只要是对象就有isa指针,大量的isa就占用了很多内存,联合体公用一块内存节省了部分内存,而位域更是在节省内存的基础上存储了信息,可以说isa指针的内存得到了充分的利用。
isa与Class地址的关系,如何关联上类的
在继续走一遍流程 首先定义一个LGPerson类,初始化[LGPerson alloc],流程如下:alloc –> _objc_rootAlloc –> callAlloc –> _objc_rootAllocWithZone –> _class_createInstanceFromZone–> obj->initInstanceIsa –>initIsa,现在探究isa到底怎么和类关联起来
![图片[2]-ios 底层原理 02-对象的本质&isa关联类-一一网](https://www.proyy.com/skycj/data/images/2021-06-22/2686803b50b483ea7142e5ad2f49a321.jpg)

图中显示 newisa.bits = ISA_MAGIC_VALUE, ISA_MAGIC_VALUE 是一个宏 ISA_MAGIC_VALUE = 0x001d800000000001,变量值改变的有bits = 8303511812964353 ,cls = 0x001d800000000001,nonpointer = 1,magic = 59。我们恢复下赋值的过程。如下图

cls = 0x001d800000000001是因为给bits赋值的时候覆盖了cls,isa_t是联合体
第0位的1等于 nonpointer = 1
magic的值怎么算呢,按位计算从47位开始,长度是6位 结果就是111011,从第0个位置开始算111011的10进制就是59.
复制代码























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)