当前位置: 首页 > news >正文

【iOS】类与对象底层探索

类与对象底层探索

  • Clang
  • 探索对象本质
  • `objc_setProperty`源码探索
  • cls与类的关联原理
    • isa的类型isa_t
    • 原理探索
  • 类&类的结构
    • 什么是元类
    • NSObject到底有几个
    • isa走位&继承关系图
    • objc_class&objc_object
  • 类结构分析
    • 计算cache类中的内存大小
    • 获取bits
      • 属性列表(property_list)&& 方法列表(methods_list)
    • rw、ro、rwe之间的关系
  • 参考文章

Clang

我们在探索OC对象的本质之前,先来了解一个编译器:Clang。我们通过终端将main.m文件输出为main.cpp,这样的目的是为了更好的观察底层的一些结构和实现逻辑,方便了解底层。

//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 

探索对象本质

打开编译好的main.cpp,找到LGPerson的定义,可以发现其在底层被编译成了struct结构体。

  • LGPerson_IMPL中的第一个属性 其实就是 isa,是继承自NSObject,属于伪继承,伪继承的方式是直接将NSObject结构体定义为LGPerson中的第一个属性,意味着LGPerson 拥有 NSObject中的所有成员变量

  • LGPerson中的第一个属性NSObject_IVARS等效于NSObject中的isa

struct LGPerson_IMPL {struct NSObject_IMPL NSObject_IVARS;//相当于Class isaNSString *_name;
};

这里提出一个问题:为什么isa的类型是class

这是为了适配 Objective - C 的对象模型、消息传递机制以及类层次结构的管理,让对象能够正确地找到所属的类并调用相应的方法。

总结:

  • OC对象的本质其实就是结构体
  • LGPerson中的isa是继承自NSObject中的isa

objc_setProperty源码探索

除了LGPerson的底层定义,我们可以发现属性name对应的setget方法,其中set方法的实现依赖的就是runtime中的objc_setProperty

static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }//gei方法
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
//set方法
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1); }

下面我们来一步步了解其底层实现:

objc4_838源码中objc_setProperty底层实现

进入reallySetProperty源码实现,这里的原理为:新值retain旧值release

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);//计算属性在对象内存中的地址,使用指针slot指向属性的存储位置、if (copy) {newValue = [newValue copyWithZone:nil];//如果copy为true,执行不可变赋值操作} else if (mutableCopy) {newValue = [newValue mutableCopyWithZone:nil];//如果mutableCopy为true,执行可变赋值操作} else {if (*slot == newValue) return;newValue = objc_retain(newValue);//新增retain}//当不复制时调用,检查指针是否等于newValue,相等直接返回;否则,调用objc_retain增加newValue的引用计数,保证设置新值的时候不会被提前释放// 如果不是原子操作,直接更新内存位置的值if (!atomic) {oldValue = *slot;*slot = newValue;//设置新值} else {// 如果是原子操作,使用锁来保证线程安全spinlock_t& slotlock = PropertyLocks[slot];//获取自旋锁slotlockslotlock.lock();//锁定自旋锁,更新属性值时不会有其他线程同时访问该属性oldValue = *slot;//将*slot复制给oldValue*slot = newValue;//设置新值        slotlock.unlock();//解锁自旋锁}objc_release(oldValue);//release旧值
}

自旋锁:一种用于多线程编程的同步机制,用于保护共享资源,防止多个线程同时访问和修改这些资源而导致的数据不一致或其他并发问题。

总结:
在这段底层探索中,这里需要进行几点说明:

  • objc_setProperty方法的目的适用于关联 上层的set方法 以及 底层的set方法,其本质就是一个接口
  • 这么设计的原因是,上层的set方法有很多,如果直接调用底层set方法中,会产生很多的临时变量,当你想查找一个sel时,会非常麻烦
  • 基于上述原因,苹果采用了适配器设计模式(即将底层接口适配为客户端需要的接口),对外提供一个接口,供上层的set方法使用,对内调用底层的set方法,使其相互不受影响,即无论上层怎么变,下层都是不变的,或者下层的变化也无法影响上层,主要是达到上下层接口隔离的目的

在这里插入图片描述

cls与类的关联原理

这里我们来了解一下initInstanceIsa如何将cls和isa关联在一起的。

isa的类型isa_t

这里先给出isa指针的类型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

通常来说,isa指针占用的内存大小为8字节,即64位

由于这里使用的是联合体,由于联合体的定义我们可以得知,这里的cls和bits这两个成员是互斥的,所以当初始化isa指针的时候,有两种初始化方法:

  • 通过cls初始化,bits无默认值
  • 通过bits初始化,cls有默认值

这里还提供了一个结构体定义的位域,用于存储类信息以及其他信息,结构体成员ISA_BITFIELD这是一个宏定义,有两个版本分别对应ios移动端以及maxOS,如下所示:

# if __arm64__//对应ios移动端
// 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__//对应macOS
#   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

下面讲解一下其中的内容表示:

  • nonpointer`表示自定义类,占1位:
    • 0:纯isa指针。
    • 1:不只是类对象地址,isa包含了类信息,对象的引用计数。
  • has_assoc表示关联对象标志,占1位:
    • 0:没有关联对象。
    • 1:存在关联对象
  • has_cxx_dtor表示该对象是否有C++/OC的析构器,占1位:
    • 如果有析构函数,就需要做析构逻辑
    • 如果没有,就可以更快的释放对象
  • shiftcls表示存储类的指针类型,即类对象
    • 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位来存储引用计数的

下面展示两种不同的平台中,isa存储情况:
在这里插入图片描述

原理探索

通过alloc --> _objc_rootAlloc --> callAlloc --> _objc_rootAllocWithZone --> _class_createInstanceFromZone方法路径,查找到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);if (!nonpointer) {newisa.setClass(cls, this);} else {ASSERT(!DisableNonpointerIsa);ASSERT(!cls->instancesRequireRawIsa());#if SUPPORT_INDEXED_ISAASSERT(cls->classArrayIndex() > 0);newisa.bits = ISA_INDEX_MAGIC_VALUE;// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUEnewisa.has_cxx_dtor = hasCxxDtor;newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#elsenewisa.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_BITnewisa.has_cxx_dtor = hasCxxDtor;
#   endifnewisa.setClass(cls, this);
#endifnewisa.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 instantiationisa = newisa;
}

这里的逻辑主要为:

  • 通过isa初始化isa
  • 通过bits初始化isa

验证isa与类的关联

  • 通过initIsa方法中的通过initIsa方法中的newisa.shiftcls = (uintptr_t)cls >> 3;验证
  • 通过isa指针地址与ISA_MSAK 的值 & 来验证
  • 通过runtime的方法object_getClass验证
  • 通过位运算验证

类&类的结构

什么是元类

这里先展示一张调试的图片:
在这里插入图片描述
这里我们需要注意一个问题:为何图中的p/x 0x00000001000081e8 & 0x00007ffffffffff8ULL以及p/x 0x00000001000081c0 & 0x00007ffffffffff8ULL中的类信息打印的结果都是CJLPerson?

  • 0x00000001000081e8是perosn对象的isa指针地址,他&后得到的结果是创建person的类CJLPerson。
  • 而0x00000001000081c0是isa中获取的类信息所指的类的isa的指针地址,就是CJLPerson类的类的isa指针地址,在Apple中我们将其简称为元类
  • 故而,我们可以了解到两个打印都是CJLPerson的根本原因是元类导致的。

在 Objective - C 的运行时环境里,0x00007ffffffffff8ULL 常作为一个掩码用于提取对象的 isa 指针中的类信息。

元类的说明:

  • 对象的isa是指向类,但是其实类也是一个对象叫做类对象,他的isa的位域只想Apple定义的元类
  • 元类是系统给的,他的定义和创建都是由编译器完成,在该过程中,类的归属来自元类。
  • 元类是类对象的类,每个类都有独一无二的元类来存储同类名的相关信息
  • 元类本身自己是没有名称的,但是由于与类相关联,故而使用同类名一样的名称

这里我们继续通过lldb命令来探索元类的走向,即isa的走位,这里我们可以得出一个关系链:对象 – 类 – 元类 – NSObject – NSObject
在这里插入图片描述
总结:

  • 对象的isa指向类
  • 类的isa指向元类
  • 元类的isa指向根元类,即NSObject
  • 根元类的isa指向其自己

NSObject到底有几个

我们从上面的图片中可以看出,NSObject类的元类也是NSObject,与上面的CJLPerson中的根元类的元类是同一个,这里我们得出结论为:内存中只存在一份根元类NSObject,根元类的元类是指向它自己的,在内存中永远只存在一份

isa走位&继承关系图

对象、类、元类、根元类的关系示意图:
在这里插入图片描述
isa走位:

  • 实例对象的isa指向类
  • 类对象的isa指向元类
  • 元类的isa指向根元类
  • 根元类的isa指向他自己本身,形成闭环,根元类即为NSObject

superclass走位

  • 类继承自父类
  • 父类继承自根类,此时根类指NSObject
  • 根类继承与nil,即根类(NSObject)可以理解为无中生有。

元类继承关系基本如上,在最后一条中,根元类继承与根类,这时的根类指NSObject。

实例对象之间没与继承关系,类之间有继承关系

objc_class&objc_object

这里我们先来理解一下对象和类为什么都有isa属性呢?这里我们需要注意的两个结构体类型为:objc_class以及objc_object

NSObject 的底层编译是NSObject_IMPL结构体:其中Class是isa指针的类型,是由objc_class定义的类型;而objc_class是一个结构体,在iOS中,所有的Class都是以objc_class为模版创建的

struct NSObject_IMPL {Class isa;
};typedef struct objc_class* Class;

下面展示一下objc_class以及objc_object在源码中的代码:

struct objc_object {Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
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 vtableclass_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

***objc_class以及objc_object有什么关系?***:

  • 结构体类型objc_class继承自objc_object类型,其中objc_object也同样是一个结构体,并且有一个isa属性,所以objc_class也拥有了isa属性。
  • 在main.cpp底层编译文件中,NSObject中的isa属性在底层是由Class定义的,其中class的底层编码来自objc_class类型,所以NSObect拥有isa属性。
  • NSObject是一个类,用它初始化一个实例对象objcobjc满足objc_object 的特性(即有isa属性),主要是因为isa是由 NSObjectobjc_class继承过来的,而objc_class继承自objc_objectobjc_objectisa属性。所以对象都有一个isaisa表示指向,来自于当前的objc_object
  • objc_object是当前的类对象,所有的对象都有这样一个特性,故拥有isa属性。

总结:

  • 所有的对象、类、元类都有isa属性
  • 所有的对象都是由objc_object继承来的
  • 所以我们可以概括为所有以objc_object为模版创建的对象都有isa属性,所有以objc_class为模版创建的对象都有isa属性。万物皆对象
  • 在结构体层面来说,我们可以理解为是上层OC和底层的对接:
    • 下层是通过结构体定义的模版,例如objc_classobjc_object
    • 上层是通过底层的模版创建的一些类型,例如CJLPerson

在这里插入图片描述

类结构分析

在这个部分中,我们来探索一下类信息中都有哪些内容,来帮助我们更好的理解该部分的内容。

这里我们先来认识一下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 vtableclass_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
//后面这里不做展示

isa: 主要指向类对象或是元类对象
superclass:指向当前类的父类
cache:方法缓存,提高调用方法的性能
bits:封装了类的其他信息,例如成员变量、方法列表、协议、属性。

元类对象结构亦是如此,只不过元类对象中存放的是类方法。

计算cache类中的内存大小

struct cache_t {
private:explicit_atomic<uintptr_t> _bucketsAndMaybeMask;union {struct {explicit_atomic<mask_t>    _maybeMask;
#if __LP64__uint16_t                   _flags;
#endifuint16_t                   _occupied;};explicit_atomic<preopt_cache_t *> _originalPreoptCache;};

上面我们先展示cache_t的定义,这段代码的核心功能是管理方法缓存,通过联合体实现了两种缓存模式的切换,节省内存并提高了灵活性。

从上面代码中我们可以看出,在LP64位的一个情况下是只有16个字节,这里我们展示一下cache_t的结构:
在这里插入图片描述

获取bits

我们可以现在objc4的源码中获取到CJLperson类对象的首地址,由于bits举例objc_class的首地址还需要偏移32字节,将这个bits强转成class_data_bits_t
在这里插入图片描述

这里我们faxing去处了bits中的值,我们可以通过看class_data_bits_t的声明,发现里面确实有bits成员:

struct class_data_bits_t {friend objc_class;// Values are the FAST_ flags above.uintptr_t bits;

在这里插入图片描述

但是这里的bits并不是我们想要的内容,我们继续向后看class_data_bits_t的源码,可以看到两个方法:data()safe_ro()方法,这两个方法一个返回class_rw_t,另一个返回class_ro_t;在64位架构CPU下,bits 的第3到第46字节存储 class_rw_tclass_rw_t 中存储flagswitnessfirstSubclassnextSiblingClass 以及class_rw_ext_t

下面我们直接探索属性列表和方法列表

属性列表(property_list)&& 方法列表(methods_list)

我们查看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};}}

这里注意一个内容,这里的存储在class_rw_t这个类是存储一个属性的,并不存储一个成员变量成员变量是存储在另一个类中:class_ro_t的这里我们对比看一下这两个类的区别:

struct class_ro_t {uint32_t flags;uint32_t instanceStart;uint32_t instanceSize;
#ifdef __LP64__uint32_t reserved;
#endifunion {const uint8_t * ivarLayout;Class nonMetaclass;};explicit_atomic<const char *> name;WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;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);}class_ro_t *duplicate() const {

这里有一个成员变量列表也就是我们这里的ivars,这个列表包含的内容不仅仅包含一个成员变量列表,除了包括在{}中定义的一个成员变量,还包括通过属性定义的成员变量.bits --> data() -->ro() --> ivars通过这个流程来获取成员变量表.

通过@property定义的属性,也会存储在bits属性中,通过bits --> data() --> properties() --> list获取属性列表,其中只包含属性

rw、ro、rwe之间的关系

rw里面有methods、properties、protocols和ro;
ro里面有ivars、baseMethods、baseProtocols、baseProperties

ro:在类第一次从磁盘被加载到内存中产生,它是一块纯净的内存clear memory(只读的),它保存了类最纯净的成员变量、实例方法、协议、属性等等。当进程内存不够时候,ro可以被移除,在需要的时候再去磁盘中加载,从而节省更多内存。
在这里插入图片描述
rw:程序运行就必须一直存在,在进程运行时类一经使用后,runtime就会把ro写入新的数据结构dirty memory(读写的),这个数据结构存储了只有在运行时才会生成的新信息。(例如创建一个新的方法缓存并从类中指向它)。故而所有类都会链接成一个树状结构,这是通过First Subclass和Next Sibling Class指针实现的,这就决定了runtime能够遍历当前使用的所有类。
在这里插入图片描述

类的方法和属性保存在ro中了,为什么还要在rw中有呢?
因为它们可以在运行时被修改,当category被加载时,它可以向类中添加新的方法,也可以通过Runtime API去动态添加和修改,因为ro是只读的,所以需要再rw去跟踪这些东西。

rwe:是在category被加载或者通过 Runtime API 对类的方法属性协议等等进行修改后产生的,它保存了原本rw中类可能会被修改的东西(Methods / Properties / Protocols / Demangled Name),它的作用是给rw瘦身。(rwe是类被修改才会产生,没有被修改的类不会产生。注意:category被加载时候产生的rwe的条件是:分类和本类是必须是非懒加载类(重写+load))

在这里插入图片描述

参考文章

Objective-C 类的底层探索
iOS-底层原理 08:类 & 类结构分析
iOS-底层原理 07:isa与类关联的原理
OC底层探索(五) 类的结构分析
类结构中的class_rw_t与class_ro_t
OC底层探索(四) ISA的结构与类的关联、ISA走位分析

相关文章:

  • 2025年- H20-Lc128-240. 搜索二维矩阵 II(矩阵)---java版
  • Qt 项目代码解释(4)
  • 【点对点协议(PPP)全解析】从原理到工程实践
  • PostgreSQL:pgJDBC 下载和安装
  • DeepSeek玄学指令大全
  • Redis TLS 加密对性能的影响分析
  • 机器学习中的学习率及其衰减方法全面解析
  • 【KWDB 创作者计划】技术解读:多模架构、高效时序数据处理与分布式实现
  • python入门
  • 浏览器 Vue 3 的 setup 中 watch 来自 props 的数组
  • Electron学习+打包
  • 云原生后端架构的优势与最佳实践
  • npm,yarn,pnpm,cnpm,nvm,npx包管理器常用命令
  • C++11新特性_委托构造函数
  • Python全流程开发实战:基于IMAP协议安全下载个人Gmail邮箱内所有PDF附件
  • 表管理(约束)实验
  • 2025年五一数学建模A题【支路车流量推测】原创论文讲解(含完整python代码)
  • Python 基于 lstm,cnn 算法的网络舆情可视化系统
  • 【Hive入门】Hive性能调优:小文件问题与动态分区合并策略详解
  • C++负载均衡远程调用学习之消息队列与线程池
  • 对谈|“对工作说不”是不接地气吗?
  • 印度宣布即日起对所有巴基斯坦航班关闭领空
  • 上海:以税务支持鼓励探索更多的创新,助力企业出海
  • 民生访谈|支持外贸企业拓内销,上海正抓紧制定便利措施
  • 白玉兰奖征片综述丨国产剧集创作的此消彼长
  • 看见“看得见的手”,看见住房与土地——读《央地之间》