前言
OC是一个面向对象的编程语言,对象
就是我们整个编写代码的过程中,最为频繁接触到的一个东西,那么什么是对象呢?在上一篇文章iOS底层 - 结构体内存对齐中,我们了解到:对象的本质就是结构体
。那么这个结论怎么验证呢?那么下面开始一探究竟。
一.了解clang
在探究对象本质之前先介绍一下clang:
- Clang是⼀个C语⾔、C++、Objective-C语⾔的轻量级编译器
- Clang将⽀持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字
- Clang是⼀个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器
- 2013年4⽉,Clang已经全⾯⽀持C++11标准,并开始实现C++1y特性(也就是C++14,这是C++的下⼀个⼩更新版本)
Clang是⼀个C++编写、基于LLVM、发布于LLVM BSD许可证下的C/C++/Objective-C/Objective-C++编译器。
二.编译oc文件为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
-
成功生成如下文件:
三.分析c++文件
开发main.cpp文件,搜索LGPerson
:
上图所示,我们可以看到对象在底层的本质是一个结构体。
跟踪NSObject_IMPL结构类型可以看到如图下:
上图所示,可以得出objc底层调用就是objc_object
- id class = [class new] 为什么我们id 类型可以获取所有的属性类型而且不需要加,因为他的底层就是id
@property (nonatomic, strong) NSString *KCName;
- 为什么属性自带set 和get 方法 根据底层跟踪如下:
// @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
- 总结:对象本质就是一个结构体,属性成员变量实现了 get 和 set 方法.
- 流程分析 :
LGPerson
—> 找到LGPerson_IMPL
—>NSObject_IMPL
-—>Class
四.isa分析
struct NSObject_IMPL {
Class isa;
};
在上面的代码里,我们可以看到NSObject里面只有一个成员变量,那就是Class类型的isa。那么这isa是什么呢,我们就这个问题,继续探索下去,首先我们先看看他的类型Class在底层中的定义。
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
可以看到,Class实际上就是一个objc_class *类型的结构体指针。
接下来我们来看看isa
,在alloc
流程的最后一步,就是通过initIsa
方法将我们申请的内存地址和我们的Class
绑定起来。
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of 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);
};
五.结构体和联合体
在上面的代码中,我们可以看到一个之前没有接触过的结构union,我们称之为联合体,那么他到底是什么,有什么特性呢,我们用下面这个例子来说明,首先看一段代码
// 结构体 : 共存
struct XHTeacher1 {
char *name;
int age;
double height ;
};
// 联合体 : 互斥
union XHTeacher2 {
char *name;
int age;
double height ;
};
结构体(sturct)中的所有变量是“共存”的
优点:海纳百川,有容乃大。只要你来,我都给你存下来
缺点:内存空间的分配是粗放的,不管你用不用全都给你分配好位置联合体(union)中每个变量之间是“互斥”的
优点:就是不够“包容”
缺点:使用内存更为精细灵活,也节省了内存空间