基础知识
联合体
1.联合体可以定义多个不同类型的成员,联合体的内存大小
由其中最大的成员的大小
决定。
2.联合体中修改其中的某个变量会覆盖
其他变量的值。
3.联合体所有的变量公用一块内存
,变量之间互斥
联合体优缺点
优点:内存使用更为灵活,节省内存。
缺点:不够包容。
位域(Bit field)
有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。就可以用位域
总结: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到底怎么和类关联起来
图中显示 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.
复制代码