本文的主要目的是理解类与isa是如何关联的
在介绍正文之前,首先需要理解一个概念:OC对象的本质是什么?
1.对象的本质
在探索OC对象本质前,先了解一个编译器:clang
1.Clang
Clang
是一个由Apple主导变现,基于LLVM的C/C++/Object-C编译器
主要是用于底层编译,将一些文件输出
成c++文件
,例如将main.m
输出成main.cpp
,其目的是为了更好的观察底层
的一些结构
及实现逻辑
,方便理解底层原理。
2.main.m里自定义LGPerson类,两个属性name和title
#import <Cocoa/Cocoa.h>
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString * name;
@property (nonatomic, copy) NSString * title;
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
}
return NSApplicationMain(argc, argv);
}
3.clang的几种终端命令
//1、将 main.m 编译成 main.cpp
clang -rewrite-objc main.m -o main.cpp
//2、将 ViewController.m 编译成 ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m
//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//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
4.通过终端进入文件夹执行clang指令,这里用的第一行指令
clang -rewrite-objc main.m -o main.cpp
文件夹里多出来一个main.cpp文件
![](https://img.haomeiwen.com/i11053641/e8afb50ba468f61a.jpeg)
5.打开main.cpp文件,找到LGPerson相关的部分
#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$_name;
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_title;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS; //isa
NSString *_name;
NSString *_title;
};
// @property (nonatomic, copy) NSString *name;
// @property (nonatomic, copy) NSString *title;
/* @end */
// @implementation LGPerson
// getter方法
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
// setter方法 objc_setProperty()公共函数,统一接口,工厂设计模式,适配器原则,
// 所有setter方法必然走到 objc_setProperty
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) {
objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1);
}
static NSString * _I_LGPerson_title(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_title)); }
static void _I_LGPerson_setTitle_(LGPerson * self, SEL _cmd, NSString *title) {
objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _title), (id)title, 0, 1);
}
// @end
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return NSApplicationMain(argc, argv);
}
6.对象的本质
可以看到LGPerson被编译成了结构体,两个属性被编译成了setter和getter方法,通过clang指令
编译成cpp文件
后,可以得出对象的本质
就是结构体
7.LGPerson与NSObject的关系,我们都知道OC里所有类的根类是NSObject,看看编译后的情况,可以看到NSObject_IMPL
和LGPerson_IMPL
第一个成员变量都是struct NSObject_IMPL
结构体,也就是Class isa
// 源码里NSObject的定义
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
// NSObject底层编译
struct NSObject_IMPL {
Class isa;
};
// LGPerson底层编译
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS; //isa 等价于 Class isa // c++里结构体继承会带过来所有的成员变量
NSString *_name;
NSString *_title;
};
LGPerson以及属性编译情况,如下图
![](https://img.haomeiwen.com/i11053641/6e5a65ab9515abc7.jpeg)
8.编译后属性对应的函数objc_setProperty()具体实现如下,新值retain、旧值release、atomic、copy处理
objc_setProperty
---> reallySetProperty
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue); // 设置isa
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) { // copy特殊处理
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) { // mutableCopy特殊处理
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue); // objc_retain() ---> objc_retain(newValue) ---> 新值retain
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock(); // atomic原子性,加锁解锁特殊处理
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue); objc_release() ---> objc_release(oldValue) ---> 旧值release
}
objc_setProperty源码截图
![](https://img.haomeiwen.com/i11053641/50c3b210c8a101cb.jpeg)
属性的不同关键字
是如何起作用的?
源码如下,可以看到多个函数都调用了这个reallySetProperty()
函数,不同的属性关键字
通过不同的参数
传递进来进行不同的处理
源码里区分了四种情况
objc_setProperty_atomic
objc_setProperty_nonatomic
objc_setProperty_atomic_copy
objc_setProperty_nonatomic_copy
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}
void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}
void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, true, true, false);
}
void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}
总结
通过对objc_setProperty
的底层源码探索,有以下几点说明:
objc_setProperty
方法的目的适用于关联 上层
的set
方法 以及 底层
的set
方法,其本质就是一个接口
这么设计的原因
是,上层
的set
方法有很多,如果直接调用底层set方法
中,会产生很多的临时变量,当你想查找一个sel时,会非常麻烦
基于上述原因,苹果采用了适配器设计模式(即将底层接口适配为客户端需要的接口
,对外
提供一个接口
,供上层的set方法使用,对内
调用底层的set
方法,使其相互不受影响,即无论上层怎么变,下层都是不变的
,或者下层的变化也无法影响上层
,主要是达到上下层接口隔离的目的
下图是上层、隔离层、底层之间的关系
![](https://img.haomeiwen.com/i11053641/ea737d9d818bc0c3.png)
9.总结
OC对象的本质
其实就是结构体
LGPerson
中的isa
是继承自NSObject
中的isa
2.isa与类的关联原理
今天来探索alloc
,三个核心方法initInstanceIsa()
函数是如何将isa
和类信息
关联起来的,即calloc()
开辟的地址指针
如何通过isa关联到对应的类
alloc
三个核心方法,源码如下,下面来探讨这个initInstanceIsa()
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
// 1.计算需要开辟的内存空间大小
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// 2.向系统申请开辟内存,并返回指针
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
// 3.指针关联cls,设置isa指针
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
isa的类型isa_t
以下是源码里isa
指针的类型isa_t
的定义,从定义中可以看出是通过联合体(union)
定义的
union isa_t { // 联合体
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits; //bits typedef unsigned long uintptr_t;
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 //位域 类似联合体位域里面的_direction
};
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_BITFIELD
宏定义的具体情况,源码(M1芯片下,objc4-818.2版本
,如无特殊说iOS底层系列博客源码都是来自于此版本,iOS不同版本会做调整,但是基本思路原理是不变的)如下,可以出大的区分是arm64
和x86_64
两种架构,arm64
架构又细分出了两种情况
无论有哪些分支思路是一样的,比如uintptr_t nonpointer : 1;
类型、变量名、占用bit数量,从低位到高位依次类推
# 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位域的含义
![](https://img.haomeiwen.com/i11053641/20a8858ba8224245.jpeg)
nonpointer
有两个值,表示自定义的类等,占1
位
0
:纯isa指针
1
:不只是类对象地址,isa中包含了类信息
、对象的引用计数
等
has_assoc
表示关联对象标志位,占1
位
0
:没有关联
对象
1
:存在关联
对象
has_cxx_dtor
表示该对象是否有C++/OC的析构器
(类似于dealloc
),占1
位
如果有
析构函数,则需要做析构
逻辑
如果没有
,则可以更快的释放
对象
shiftcls
表示存储类的指针的值
(类的地址), 即类信息
,即arm64
架构中cls
转换为二进制33位
的数据值
arm64
中占 33
位,开启指针优化的情况下,在arm64
架构中有33
位用来存储类指针
x86_64
中占 44
位
magic
用于调试器判断当前对象是真的对象
还是 没有初始化的空间
,占6
位
weakly_refrenced
是 指对象是否被指向
或者 曾经指向一个ARC的弱变量
没有弱引用的对象可以更快释放
deallocating
标志对象是是否正在释放
内存
has_sidetable_rc
表示 当对象引用计数大于10
时,则需要借用该变量存储进位
extra_rc
(额外的引用计数) ,表示该对象的引用计数值,实际上是引用计数值减1
如果对象的引用计数为10
,那么extra_rc为9
(这个仅为举例说明),实际上iPhone 真机
上的 extra_rc
是使用 19
位来存储引用计数的
针对iOS和Mac两种不同的平台,其isa的存储情况如如图所示
![](https://img.haomeiwen.com/i11053641/38a98f356b2c85a3.png)
initInstanceIsa()源码
流程
通过alloc --> _objc_rootAlloc --> callAlloc --> _objc_rootAllocWithZone --> _class_createInstanceFromZone
方法路径,查找到initInstanceIsa() --> initIsa() --> setClass()
,相关源码如下
首先在_class_createInstanceFromZone
函数里打印cls转换为16进制值
,看看有哪些特征,其实整个流程都可以打印cls 转换的16进制数值的
特征
iOS系统类
都是占用4个字节
和4个bit
,也就是8 * 4 + 4
= 36bits
,并且最后的4bit
不是8
就是0
8
: ---> 1000
0
: ---> 0000
也就是36位
二进制数,后3
位都是0
那么如果我想存储这个cls转换
过来的十六进制数值
,先去掉后面的3个0
,存储有效的33位
即可,读取的时候后面再补上3个0
就完全恢复了cls的真实数值
,鉴于此来分析一下地址指针
通过isa存储类信息
,实现地址指针
关联到具体的类
的过程
![](https://img.haomeiwen.com/i11053641/b073b15dc20b7634.jpeg)
initInstanceIsa()
核心逻辑
initIsa()
函数调用
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
initIsa()
核心逻辑
isa_t newisa(0);
初始化isa
newisa.bits = ISA_MAGIC_VALUE;
//bits
赋初值 这里是 0x000001a000000001ULL
newisa.setClass(cls, this)
函数调用,赋值shiftcls
保存类信息
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
// 初始化isa,全部赋0,什么事情都没做
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA // !nonpointer 执行流程,即is通过cls定义
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 // bits执行流程 大部分都是nopointer isa
newisa.bits = ISA_MAGIC_VALUE; //bits赋初值
// 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); // 核心函数,赋值shiftcls
#endif
newisa.extra_rc = 1;
}
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
setClass()
核心逻辑
uintptr_t signedCls = (uintptr_t)newCls;
cls
转换为uintptr_t
类型的数值
shiftcls = (uintptr_t)newCls >> 3;
cls转换的数值右移3位
,对shiftcls
赋值,保存类信息,为什么是右移3位
?上面已经分析了,这里shiftcls
是33
位,要存储的是36
位且后3
位都是0
,所以,存储有效的33
位,读取的时候后面补齐3个0
即可
inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
// Match the conditional in isa.h.
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE
// No signing, just use the raw pointer.
uintptr_t signedCls = (uintptr_t)newCls;
# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT
// 符合条件下重新设置obj内存地址和newCls(cls)的关系,通过uintptr_t类型的数据表示
// We're only signing Swift classes. Non-Swift classes just use
// the raw pointer
uintptr_t signedCls = (uintptr_t)newCls;
if (newCls->isSwiftStable())
signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
// 符合条件下重新设置obj内存地址和newCls(cls)的关系,通过uintptr_t类型的数据表示
// We're signing everything
uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
# else
# error Unknown isa signing mode.
# endif
// cls转换为uintptr_t类型的数据值,右移3位,存储类的有效信息
shiftcls_and_sig = signedCls >> 3;
#elif SUPPORT_INDEXED_ISA
// Indexed isa only uses this method to set a raw pointer class.
// Setting an indexed class is handled separately.
cls = newCls;
#else // Nonpointer isa, no ptrauth
// cls转换为uintptr_t类型的数据值,右移3位,存储类的有效信息
shiftcls = (uintptr_t)newCls >> 3;
#endif
}
总结
isa
保存类信息
的核心逻辑
isa_t newisa(0);
---> 初始化isa
newisa.bits = ISA_MAGIC_VALUE;
---> bits
赋初值 这里是 0x000001a000000001ULL
uintptr_t signedCls = (uintptr_t)newCls;
---> cls
转换为uintptr_t
类型的数值
shiftcls = (uintptr_t)newCls >> 3;
--->cls转换的数值右移3位
,对shiftcls
赋值,保存类信息
由此可以看出cls
转换uintptr_t数值类型
的规则与shiftcls存储位数
是一一对应的关系。右移3位并不是因为shiftcls
本身从第3位
开始的
2.下面通过打印调试来看看具体的isa存储值的过程
函数流程
_class_createInstanceFromZone ---> initInstanceIsa ---> initIsa --->setClass ---> _class_createInstanceFromZone
LLDB调试流程
主要流程
初始化newisa
---> newisa 赋初值
(bits与cls赋初值,一致的
) ---> setClass()
函数 ---> shiftcls赋值(newCls16进制右移3位,存储有效的33位)
---> bits更新值
,cls = LGPerson
关联类
验证方式
1.bits
按位与 ISA_MASK
,等于cls的16进制转换的数值
2.bits
位移后,取出shiftcls
的值,等于cls的16进制转换的数值
3.shiftcls
补齐后面3
个0
,等于cls的16进制转换的数值
4.LGPerson
内存段第一个8
字节是 isa
按位与 ISA_MASK
,等于cls的16进制转换的数值
5.LGPerson
内存段第一个8
字节是 isa
位移后,取出shiftcls
的值,等于cls的16进制转换的数值
6.LGPerson
内存段第一个8
字节是 isa
等于bits
的数值
7.cls = LGPerson
KCObjcBuild was compiled with optimization - stepping may behave oddly; variables may not be available.
(lldb) p/x cls
(Class) $0 = 0x00000001000081c8 LGPerson //initIsa里打印cls的16进制数值,等于0x00000001000081c8
(lldb) p newisa // 初始化isa,对应源码isa_t newisa(0);,全部为0或者nil
(isa_t) $1 = {
bits = 0
cls = nil
= {
nonpointer = 0
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 0
magic = 0
weakly_referenced = 0
unused = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
(lldb) p newisa // newisa bits赋初始值 define ISA_MAGIC_VALUE 0x000001a000000001ULL
(isa_t) $2 = {
bits = 1786706395137 // bits = cls 1786706395137 = 0x1a000000001
cls = 0x000001a000000001
= {
nonpointer = 1 // nonpointer isa
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 0
magic = 26 // 0x1a = 26
weakly_referenced = 0
unused = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
(lldb) p/x newCls // 进入setClass函数
(Class) $3 = 0x00000001000081c8 LGPerson // LGPerson
(lldb) p 0x00000001000081c8 >> 3 // 打印newCls16进制,并且右移3位
(long) $4 = 536875065 // newCls 10进制数值为536875065,存储到shiftcls
(lldb) p newisa
(isa_t) $5 = {
bits = 36975373484489
cls = LGPerson
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 536875065 // 536875065,存储到shiftcls
magic = 26
weakly_referenced = 0
unused = 0
has_sidetable_rc = 0
extra_rc = 1
}
}
(lldb) p/x 36975373484489 // 验证开始
(long) $6 = 0x000021a1000081c9
(lldb) p/x 36975373484489 & 0x0000000ffffffff8ULL // bits按位与上 0x0000000ffffffff8ULL --> ISA_MASK
(unsigned long long) $7 = 0x00000001000081c8 // 结果与0x00000001000081c8 LGPerson一致
(lldb) p/x cls // 返回 initIsa函数 打印 cls
(Class) $8 = 0x00000001000081c8 LGPerson
(lldb) p/x 536875065 // 16进制打印shiftcls
(int) $9 = 0x20001039
(lldb) p/t 0x20001039
(int) $10 = 0b00100000000000000001000000111001 2进制打印shiftcls
(lldb) p/x 0b0000000000000000000000000000000100000000000000001000000111001000 // 补齐后3个0和64位,16进制 打印补齐后的shiftcls,结果与0x00000001000081c8 LGPerson一致
(long) $11 = 0x00000001000081c8
(lldb) x/4gx obj // 返回到_class_createInstanceFromZone函数,打印4段内存数据,0x000021a1000081c9 是isa
0x10160ab80: 0x000021a1000081c9 0x0000000000000000
0x10160ab90: 0x0000000000000000 0x0000000000000000
(lldb) p 0x000021a1000081c9
(long) $13 = 36975373484489 // 10进制打印isa,与 bits = 36975373484489一致
(lldb) p/x 0x000021a1000081c9 & 0x0000000ffffffff8ULL
(unsigned long long) $14 = 0x00000001000081c8 // isa与按位与上 0x0000000ffffffff8ULL与0x00000001000081c8 LGPerson一致
(lldb) p/x 0b00100000000000000001000000111001000
(long) $15 = 0x00000001000081c8 // shiftcls直接后面补3个零,16进制打印cls与0x00000001000081c8 LGPerson一致
(lldb) p/x 0x000021a1000081c9 >> 3 // 右移3位抹掉,后3位
(long) $16 = 0x0000043420001039
(lldb) p/x 0x0000043420001039 << 31 3 // 左侧移31位抹掉,28+3,抹掉前28
(long) $17 = 0x1000081c80000000
(lldb) p/x 0x1000081c80000000 >> 28 // 右移28位复原
(long) $18 = 0x00000001000081c8 // 位移后与16进制打印cls与0x00000001000081c8 LGPerson一致
(lldb)
Xcode 调试流程截图说明,与上面流程完全一致
![](https://img.haomeiwen.com/i11053641/b36f48fe5ce42094.jpeg)
![](https://img.haomeiwen.com/i11053641/8007ac0a32dcbe29.jpeg)
3.通过 object_getClass
通过查看object_getClass 源码实现,同样可以验证isa与类关联的原理
导入#import <objc/runtime.h>
源码如下
核心逻辑
联合体isa_t
里面的bits
,按位与ISA_MASK
clsbits &= ISA_MASK;
,按位与ISA_MASK
return (Class)clsbits;
,强制转换为Class
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA();
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
inline Class
objc_object::ISA(bool authenticated)
{
ASSERT(!isTaggedPointer());
return isa.getDecodedClass(authenticated);
}
inline Class
isa_t::getDecodedClass(bool authenticated) {
#if SUPPORT_INDEXED_ISA
if (nonpointer) {
return classForIndex(indexcls);
}
return (Class)cls;
#else
return getClass(authenticated);
#endif
}
inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
return cls;
#else
uintptr_t clsbits = bits;
# if __has_feature(ptrauth_calls)
# if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
// Most callers aren't security critical, so skip the
// authentication unless they ask for it. Message sending and
// cache filling are protected by the auth code in msgSend.
if (authenticated) {
// Mask off all bits besides the class pointer and signature.
clsbits &= ISA_MASK; // 按位与ISA_MASK
if (clsbits == 0)
return Nil;
clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR)); // clsbits 获取clsbits
} else {
// If not authenticating, strip using the precomputed class mask.
clsbits &= objc_debug_isa_class_mask; // clsbits 获取clsbits
}
# else
// If not authenticating, strip using the precomputed class mask.
clsbits &= objc_debug_isa_class_mask; // clsbits 获取clsbits
# endif
# else
clsbits &= ISA_MASK; // 按位与ISA_MASK
# endif
return (Class)clsbits; // clsbits 墙砖为Class
#endif
}
Xcode截图object_getClass 核心源码分析
![](https://img.haomeiwen.com/i11053641/5bb97a46439f6415.jpeg)
1.总结
1.对象
的本质是结构体
2.对象
指针所指向的第一个8字节
是isa
指针,isa
指针是64位
,其中的shiftcls
存储类信息的16进制转换数值
,这里是占用33
位,具体架构下见对应的宏定义
4.isa_t
结构体里面的cls 存储类名
5.shiftcls = (uintptr_t)newCls >> 3
;
右移3
位不是因为存储位置是从低位3位
开始的,而是(uintptr_t)newCls
有效位是33
位,后3
位是0
,由此可以看出cls
转换uintptr_t数值类型
的规则与shiftcls存储位数
是一一对应的关系。右移3位并不是因为shiftcls
本身从第3位
开始的
6.联合体isa_t
里面的bits
,按位与ISA_MASK
,不是联合体本身
网友评论