本文的主要目的是理解类与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文件
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以及属性编译情况,如下图
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源码截图
属性的不同关键字
是如何起作用的?
源码如下,可以看到多个函数都调用了这个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
方法,使其相互不受影响,即无论上层怎么变,下层都是不变的
,或者下层的变化也无法影响上层
,主要是达到上下层接口隔离的目的
下图是上层、隔离层、底层之间的关系
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位域的含义
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的存储情况如如图所示
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存储类信息
,实现地址指针
关联到具体的类
的过程
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 调试流程截图说明,与上面流程完全一致
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 核心源码分析
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
,不是联合体本身