目录:
类与对象
类和对象的本质
对象部分
OC的类和对象本质上都是结构体,在编译时都会以结构体的形式被编译到二进制中。
我们直接来看runtime源码:
struct objc_object {
private:
isa_t isa;
public:
....省略方法部分(该部分非常多)
除去方法部分,我们可以发现成员变量只有一个isa_t类型的isa
而isa_t这个 类型只有一个成员变量,存储了对象所属类的信息[Class cls]
**总结:**对象的本质除了其本身含有的方法列表,它有一个isa指针用来存储所属类的信息。
类部分
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
objc_class继承于objc_object,换句话说,类的本质也是个对象,其中isa指针式其继承自objc_object的,所以代码里才注释了// Class ISA;
isa是结构体,其中会存储类的地址,其并不是指针
这里我们先来看一下对象、类、元类的示例图:
元类的定义:
OC对象在发送消息时,运行时库会追寻着对象的isa指针得到对象所属的类。这个类包含了能应用于这个类的所有实例方法以及指向父类的指针,以便可以找到父类的实例方法。运行时库检查这个类和其父类的方法列表,找到与消息对应的方法。 编译器会将消息转换为消息函数objc_msgSend进行调用
我们有时也会有对类发送消息的情况:
NSString *testString = [NSString stringWithFormat:@"%@",@"123"];
从此处我们可以知道,OC的类其实也是一个对象,一个对象就要有一个它属于的类,意味着类也要有一个isa指针,指向其所属的类。那么类的类是什么呢?就是我们所说的元类(MetaClass),所以,元类就是类对象的所属类。
所以从消息机制的层面来讲:
- 当你给对象发消息时,消息会寻找这个对象的类的方法列表
- 当你给类发消息时,消息是在寻找这个类的元类的方法列表
OC的类信息存放在哪里?
答:对象方法、属性、成员变量、协议信息,存放在class对象中
类方法存放在meta-class对象中(元类对象和class内存结构是一样的 但是用途不一样 主要有类方法的类信息 其他为空的)
成员变量的具体值存放在instance对象中
isa详解
isa_t结构体
这里我们来详解一下isa指针,即isa_t结构体
每个OC对象都含有一个isa指针,__arm64__之前,isa仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__架构之后,apple对isa进行了优化,变成了一个共用体(union)结构,同时使用位域来存储更多的信息。
先看一下isa_t的代码定义:
union isa_t
{
Class cls;
uintptr_t bits;
//bits的结构体
struct {
uintptr_t nonpointer : 1;//->表示使用优化的isa指针
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 deallocating : 1;//->对象是否正在销毁
uintptr_t has_sidetable_rc : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1
uintptr_t extra_rc : 19; //->存储引用计数,实际的引用计数减一,存储的是其对象以外的引用计数
};
};
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_t的底层结构,现在我们来看一下isa 的初始化过程,来探索isa_t 的底层实现,初始化过程发生在alloc方法的底层流程中的这一步:
/// 将类和指针做绑定
obj->initInstanceIsa(cls, hasCxxDtor);
此过程包含了 isa 的初始过程,要探究 isa 的底层实现,我们就从 isa 的创建开始:
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
//下方函数调用就是isa的初始过程
initIsa(cls, true, hasCxxDtor);
}
initIsa()
内容如下:
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
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;
}
// 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;
}
我们可以看到方法内创建了一个 isa_t 类型的 newisa 实例, 做了 赋值操作后,返回了 newisa。 那么,接着我们就来详细的看一下这个 isa_t 的底层实现。
下面我们来看一下runtime中的源码定义的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
是一个联合体, 有两个成员变量一个是 bits
, 还有一个 是 cls
。我们知道 联合体
中各变量是互斥的
, 它的优点是内存使用更为精细灵活。 所以,也就是说, isa_t
有两种初始化方式:
bits
被赋值,cls
没有值或者值被覆盖;cls
被赋值,bits
没有值或者值被覆盖。
ISA_BITFIELD
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:
各变量代表的意思上方已经做过讲解了。
另外,我们发现源码中经常会用到assert()函数,该函数的作用如下:
其作用是如果它的条件返回错误,则终止程序执行。
assert的作用是先计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
附加:初始化isa_t的代码为:
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
这个里面说的很明白 news.bits初始化时候只设置了nonpointer,magic两个部分,其余部分都没有进行设置,故值都为0
一个扩展:
# if __arm64__
# define ISA_MASK 0x007ffffffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
以上四行代码,是取自结构体 ISA_BITFIELD
中的内容。
这里的 ISA_MASK
是一个掩码。 我们知道 isa
的 8字节 - 64位存储空间中 在 arm64
也就是手机端 shiftcls
存储类指针的值 占用的是第4位开始33位长度的一段内存空间; 在 x86_64
也就是电脑端, shiftcls
存储类指针的值 占用的是第4位开始44位长度的一段内存空间。我们调试台打印出来一个对象的内存地址后,取第一位存储的isa
地址&
上对应的掩码后,就是对象的类指针的值。
下面,验证一下:
接着我们在类对象的地址上继续进行操作得到了其元类对象的地址:
class方法
首先Class这里需要注意几点
- Class ObjectClass = [[NSObject class] class]; 返回的还是class对象,并不是meta-class对象。
- (Class)class, +(Class)class
返回的就是类对象
//instance实例对象
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
//class对象,类对象
//objectClass1~5都是NSObject的类对象
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = [NSObject class];
Class objectClass4 = object_getClass(object1);
Class objectClass5 = object_getClass(object2);
//元类对象(将类对象当作参数传入进去)
Class objectMetaClass = object_getClass([NSObject class]);
Class objectMetaClass2 = [[NSObject class] class];
//判断是不是元类对象
NSLog(@"instance - %p %p", object1, object2);
NSLog(@"class - %p %p %p %p %p %d", objectClass1,objectClass2, objectClass3, objectClass4, objectClass5, class_isMetaClass(objectClass3 ));
NSLog(@"mateClass - %p %p %d",objectMetaClass, objectMetaClass2, class_isMetaClass(objectMetaClass));
输出情况:
instance - 0x100511920 0x10050e840
class - 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0
mateClass - 0x7fff91da20f0 0x7fff91da2118 1
无论多少次class方法得到的都还是类函数。
- 元类对象和class内存结构是一样的 但是用途不一样 主要有类方法的类信息 其他为空的
- 类对象在内存中有且仅有一个对象 主要包括 isa指针 super Class指针 类的属性信息 类的对象方法信息 类的协议信息 类的成员变量信息
我们再来看一下objc_class的源码部分
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA;
Class superclass;
cache_t cache; // 方法缓存 formerly cache pointer and vtable
class_data_bits_t bits; // 用于获取具体的类信息 class_rw_t * plus custom rr/alloc flagsflags
...
bits里面存储了类的方法列表等等的信息
这里的bits是class_data_bits_t类型的,上面objc_object的isa_t类型数据中也有一个uintptr_t类型的bits,但是这是两种结构。
我们先看bits
的数据结构 class_data_bits_t
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// Get the class's ro data, even in the presence of concurrent realization.
// fixme this isn't really safe without a compiler barrier at least
// and probably a memory barrier when realizeClass changes the data field
const class_ro_t *safe_ro() const {
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED) {
// maybe_rw is rw
return maybe_rw->ro();
} else {
// maybe_rw is actually ro
return (class_ro_t *)maybe_rw;
}
}
}
其实最最重要的是其中的2个方法。data
和 safe_ro
,两个方法分别返回class_rw_t
和class_ro_t
。
这里ro_t
的获取也是通过data
方法获取的,所有可以理解为rw_t
也在ro_t
之中。
class_rw_t和class_ro_t
bits里面有获取class_rw_t
,class_ro_t
的两个方法
class_rw_t
继续来看class_rw_t
的数据结构
上方代码省略...
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
};
在rw_t
中可以 看到有这3个方法,通过这三个方法分别能获取到类的方法、属性、协议。
class_ro_t
再看class_ro_t
的数据结构。
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic<const char *> name;
// With ptrauth, this is signed if it points to a small list, but
// may be unsigned if it points to a big list.
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
return _swiftMetadataInitializer_NEVER_USE[0];
} else {
return nil;
}
}
const char *getName() const {
return name.load(std::memory_order_acquire);
}
//methodListPointerDiscriminator是 方法列表指针鉴别器
static const uint16_t methodListPointerDiscriminator = 0xC310;
#if 0 // FIXME: enable this when we get a non-empty definition of __ptrauth_objc_method_list_pointer from ptrauth.h.
static_assert(std::is_same<
void * __ptrauth_objc_method_list_pointer *,
void * __ptrauth(ptrauth_key_method_list_pointer, 1, methodListPointerDiscriminator) *>::value,
"Method list pointer signing discriminator must match ptrauth.h");
#endif
method_list_t *baseMethods() const {
#if __has_feature(ptrauth_calls)
method_list_t *ptr = ptrauth_strip((method_list_t *)baseMethodList, ptrauth_key_method_list_pointer);
if (ptr == nullptr)
return nullptr;
// Don't auth if the class_ro and the method list are both in the shared cache.
// This is secure since they'll be read-only, and this allows the shared cache
// to cut down on the number of signed pointers it has.
bool roInSharedCache = objc::inSharedCache((uintptr_t)this);
bool listInSharedCache = objc::inSharedCache((uintptr_t)ptr);
if (roInSharedCache && listInSharedCache)
return ptr;
// Auth all other small lists.
if (ptr->isSmallList())
ptr = ptrauth_auth_data((method_list_t *)baseMethodList,
ptrauth_key_method_list_pointer,
ptrauth_blend_discriminator(&baseMethodList,
methodListPointerDiscriminator));
return ptr;
#else
return (method_list_t *)baseMethodList;
#endif
}
下方代码省略...
可以看到有方法、属性、协议和成员变量。但方法、属性、协议的命名都是base开通的。
ro 和 rw的区别
从生成时机的角度来说,
ro
编译阶段生成,rw
运行的时候生成。从存储的内容角度来讲,ro
中有方法、属性、协议和成员变量,而rw
中并没有成员变量。rw
中的方法属性协议的取值方法中,也是通过取ro
或者rwe
中的值来获得。ro
中的方法、属性、协议都是base
,也就是只有本类中的方法属性和协议。
class_rw_ext_t
class_rw_ext_t
的数据结构如下:
struct class_rw_ext_t {
DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
class_ro_t_authed_ptr<const class_ro_t> ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
};
class_ro_t
是只读的clean memory
,所以可以不用的时候进行移除存放到磁盘中,从而节省很多内存,等到用的时候再从磁盘中重新加载。然而class_rw_t
是可以运行时进行修改的dirty memory
,所以只要进程在运行,它就必须一直存在,所以dirty memory
要比clean memory
昂贵的多。但由于class_rw_t
中的类也只有大约10%
的类在运行时真正修改了它们的方法,所以就诞生了class_rw_ext_t
,class_rw_ext_t
就是把class_rw_t
中平时不用的部分拆掉并存起来,使class_rw_t
的大小减少很多,大大减轻了class_rw_t
对内存的占用。
rw_ext_t
生成条件:
- 使用分类的类
- 使用Runtime API动态修改类的结构的时候
在遇到以上2种情况的时候,类的结构(属性、协议、方法)发生改变,原有的ro(Claer Memory,便宜)已经不能继续记录类的属性、协议、方法信息了,于是系统重新生成可读可写的内存结构rw_ext(Dirty Memory, 比较贵),来存放新的类结构。
然后再取方法、属性、列表的时候,在方法实现中来做区分,如果有rw_ext的类,其列表就从那个rw_ext中获得,如果没有,从ro中读取,其代码如下:
const method_array_t methods() const {
auto v = get_ro_or_rwe();
//如果有rwe,其列表就从那个rw_ext中获
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
//如果没有,返回ro的baseMethod
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
};
这样做的目的是为了在不增加成本的情况下,提高性能。使用分类和使用Runtime改变类结构的情况一般占10%左右,这10%的类结构苹果使用比较贵的内存记录(可读可写),而没有变过的类结果,我们用便宜的内存记录(只读)。这样既保证了语言的动态性,又不会因为动态性而增加过多的成本。
具体的class_rw_t
和class_ro_t
以及class_rw_ext_t
内容可以参考大佬博客:iOS八股文(四)类对象的结构(下)
一个思考:
元类里面存放的内容只有类方法,那么为什么不把类方法存放在类对象中?或者说设计元类的目的是什么呢?
1.单一职责设计原理。实例对象存储成员变量的值,类对象存放,实例方法、协议、成员变量、属性,元类对象存放类方法,各司其职,互不影响。
2.复用msgSend
消息发送机制。
3.类方法、实例方法是在上层的定义,在底层并不区分类方法实例方法,但在runtime这一层,需要承接上层类方法和实例方法,对接到底层方法调用。使用了msgSend
,如果msgSend
的时候需要再区分类对象,实例对象,会在内部增加判读逻辑,从而降低了效率,有了元类的存在,问题迎刃而解。
cache_t
struct cache_t {
struct bucket_t *buckets() const;
mask_t mask() const;
mask_t occupied() const;
}
这里引入了bucket_t(散列表)
cache_t 哈希表结构,哈希表内部存储的bucket_t
bucket_t中存储的是SEL和IMP的键值对(函数名及其实现)
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
property
我们都只知道property会帮属性自动生成set\get方法和一个成员变量
struct property_t {
const char *name;
const char *attributes;
};
我们只储存了其name和属性关键字
一些细节的点:
导入#import "objc/runtime.h"
之后的object_getClass
就是得到其isa
指针
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
superClass得到的就是父类的指针。