前言: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的不断探索之旅,希望能有帮助到各位读者朋友。
复制代码
目录如下:
写在前面
之前我们分析了alloc底层流程和结构体的内存对齐原理。那么,今天我们来分析下对象的本质是什么。
我们都知道,OC语言是基于C和C++语言增加了一层面向对象,那么,我们就从OC的对象,在C和C++中底层的实现代码来开始今天的探索之路。
准备
- 1、Clang
Clang是一个C语言、C++、Objective-C语言的轻量级编译器。源代码发布于BSD协议下。 Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。Clang是一个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器。
其常用命令如下:
把目标文件编译成c++文件
clang -rewrite-objc main.m -o main.cpp
复制代码
xcode
安装的时候顺带安装了xcrun
命令,xcrun
命令在clang
的基础上进行了 一些封装,要更好用一些
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (模拟器)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp (手机)
复制代码
- 2、union
联合体
我们通过一个例子来认识和了解 union
以及 union
与struct
的区别
struct SMTeacher3 {
char *name;
int age;
double height ;
};
union SMTeacher4 {
char *name;
int age;
double height ;
};
复制代码
简单总结一下,
联合体 中各变量是“互斥的”,缺点是不够“包容”;优点是内存使用更为精细灵活,也节省来内存空间。 (通过打印的t4占用了8字节内存空间比结构体的24字节节省了3倍,随着第二次第三次打印 t4 的内容,可以看到每次赋值后,其他的属性就变成了脏数据,不再可读);
结构体 则是所有的变量都是“共存的”,优点是足够“包容”全面,缺点是内存的管理是粗放的,不管用不用全分配。
开始
首先,我们在新建的项目中 main.h 文件中,我们添加一个SMPerson
对象 :
@interface SMPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) NSInteger height;
@property (nonatomic, copy) NSString *like;
@end
@implementation SMPerson
@end
复制代码
接着,我们通过命令行工具,将 main.h
文件编译为c++文件 main.cpp
,命令为:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
可以看到 在当前的项目路径下生成来一个 main.cpp
文件。直接找到我们的SMPerson
类在底层是一个结构体,实现如下 :
struct SMPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
NSInteger _age;
NSInteger _height;
NSString *_like;
};
复制代码
多出来的这个 struct NSObject_IMPL NSObject_IVARS;
是什么呢?
没错他就是 isa,
typedef struct objc_class *Class;
struct NSObject_IMPL {
Class isa;
};
复制代码
下面是SMPerson
属性的set、get方法的底层实现
static NSString * _I_SMPerson_name(SMPerson * self, SEL _cmd) {
return (*(NSString **)((char *)self + OBJC_IVAR_$_SMPerson$_name));
}
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_SMPerson_setName_(SMPerson * self, SEL _cmd, NSString *name) {
objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SMPerson, _name), (id)name, 0, 1);
}
static NSInteger _I_SMPerson_age(SMPerson * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_SMPerson$_age)); }
static void _I_SMPerson_setAge_(SMPerson * self, SEL _cmd, NSInteger age) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_SMPerson$_age)) = age; }
static NSInteger _I_SMPerson_height(SMPerson * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_SMPerson$_height)); }
static void _I_SMPerson_setHeight_(SMPerson * self, SEL _cmd, NSInteger height) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_SMPerson$_height)) = height; }
static NSString * _I_SMPerson_like(SMPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SMPerson$_like)); }
static void _I_SMPerson_setLike_(SMPerson * self, SEL _cmd, NSString *like) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SMPerson, _like), (id)like, 0, 1); }
复制代码
我们通过调试台将 p1
对象的内存信息打印出来,
由此可见,p1
对象在内存中,其属性是如下所示布局的:
所以,在set、get方法的底层实现中 OBJC_IVAR_$_SMPerson$_name
就是属性对应的内存地址的偏移量, 系统通过每个属性的偏移量,来实现对其赋值和取值。
对象的本质:
对象在底层就是一个结构体
,其内部存储了类的实例变量。
对象的 NSObject_IMPL
继承自 NSObject 的 isa。
NSObject
只有一个成员变量 isa。
isa 的底层实现是什么呢?
iOS 底层原理探索之 alloc中我们探寻类 alloc 方法的底层流程。 其中在
/// 将类和指针做绑定
obj->initInstanceIsa(cls, hasCxxDtor);
复制代码
中,此过程包含了 isa 的初始过程,要探究 isa 的底层实现,我们就从 isa 的创建开始:
initIsa 内容如下:
我们可以看到 方法内创建了一个 isa_t 类型的 newisa 实例, 做了 赋值操作后,返回了 newisa。 那么,接着我们就来详细的看一下这个 isa_t 的底层实现。
isa_t 内容如下:
所以 isa_t
是一个联合体, 有两个成员变量一个是 bits
, 还有一个 是 cls
。我们知道 联合体
中各变量是互斥的
, 它的优点是内存使用更为精细灵活。 所以,也就是说, isa_t
有两种初始化方式:
bits
被赋值,cls
没有值或者值被覆盖;cls
被赋值,bits
没有值或者值被覆盖。
isa_t
中还有一个成员变量 是 结构体 ISA_BITFIELD
, 这个宏定义
对应 __arm64__
和 __x86_64__
即 iOS 和 MacOS 两个端的实现。
ISA_BITFIELD
通过位域存储信息,具体存储信息如下:
# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# define ISA_MASK 0x007ffffffffffff8ULL
# define ISA_MAGIC_MASK 0x0000000000000001ULL
# define ISA_MAGIC_VALUE 0x0000000000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 0
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t weakly_referenced : 1; \
uintptr_t shiftcls_and_sig : 52; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# endif
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
// SUPPORT_PACKED_ISA
#endif
复制代码
为来更直观的理解上面 位域 ISA_BITFIELD
存储的信息, 我们画个图来解析一下以上这段很长的代码。
arm64架构下的isa
:
x86_64架构下的isa
:
各变量代表的意思是:
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
有两种类型nonpointer
类型和 非nonpointer
类型,nonpointer
类型包含来类信息、对象引用计数等数据; 非nonpointer
类型只是一个纯指针。isa
使用联合体
加位域
来实现。 采用这种方式节省了大量的内存
(由此可见,苹果在底层实现是做了很多的优化工作的);开发中,大量使用的对象都会有一个isa
指针, 这么多的isa
会占用大量的内存,联合体
中成员变量的互斥特性,节省了部分的内存空间。再加上位域
的使用,更是在节省内存空间的同时,将对象类的信息等信息作了优化存储,这样,使得isa
指针 所占用的内存空间得到最大化的使用。
未完待续…