Objective-C 2.0 中类与对象的定义
栏目: Objective-C · 发布时间: 5年前
内容简介:Objective-C 的运行时系统(Runtime)有两个版本:“Modern” 和 “Legacy”。我们目前使用的 Objective-C 2.0 采用的是现行(Modern)版本的 Runtime 系统,它只能运行于 iOS 和 macOS 10.5 之后的 64 位程序中,而 macOS 较老的 32 位程序仍采用 Objective-C 1.0 早期(Legacy)版本的 Runtime 系统。在上一篇文章需要说明的是,本文只是简单罗列出了 Objective-C 2.0 中类与对象的相关定义
Objective-C 的运行时系统(Runtime)有两个版本:“Modern” 和 “Legacy”。我们目前使用的 Objective-C 2.0 采用的是现行(Modern)版本的 Runtime 系统,它只能运行于 iOS 和 macOS 10.5 之后的 64 位程序中,而 macOS 较老的 32 位程序仍采用 Objective-C 1.0 早期(Legacy)版本的 Runtime 系统。
在上一篇文章 《Objective-C 1.0 中类与对象的定义》 中,我们介绍了早期 Objective-C 1.0 中类与对象的定义,本文以最新的 Objective-C Runtime 源码 objc4-750.1.tar.gz 进行阅读和分析 Objective-C 2.0 中类与对象的定义。
需要说明的是,本文只是简单罗列出了 Objective-C 2.0 中类与对象的相关定义,仅作为参考,并没有做深入的分析,因为这方面的文章已经有很多了,详见文末的参考文档。
注:文中的相关示意图均来自霜神(halfrost)的 这篇博文 ,在此不胜感激。
Class 和 id
在 objc-private.h 文件中:
-
Class
被定义为指向struct objc_class
的指针; -
id
被定义为指向struct objc_object
的指针;
typedef struct objc_class *Class; typedef struct objc_object *id;
其中, objc_object
结构体的定义如下:
struct objc_object { private: isa_t isa; public: // ISA() assumes this is NOT a tagged pointer object Class ISA(); // getIsa() allows this to be a tagged pointer object Class getIsa(); // 其他方法声明... }
objc_object
结构体用于初始化 Objective-C 中类的实例对象,它包含一个名为 isa
的成员变量,其类型为 union isa_t
共用体, isa_t
中有一个 Class
类型的指针 cls
,用于指向实例对象所对应的类,关于 isa
下面会详细分析。
在 objc-runtime-new.h 文件中, objc_class
结构体的定义如下:
struct objc_class : objc_object { // 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 class_rw_t *data() { return bits.data(); } // 其他方法声明... }
我们可以发现, objc_class
继承于 objc_object
,也就是说,在 Objective-C 中,类本身也是一个对象,其 “isa 指针” 指向对应的“元类”(Meta Class), superclass
指针指向父类, cache
为调用过的方法缓存,而 bits
保存着这个类的属性(成员变量)、实例方法链表、以及遵循的协议等信息,示意图如下:
cache_t
方法缓存的类型 cache_t
的定义如下:
struct cache_t { struct bucket_t *_buckets; mask_t _mask; mask_t _occupied; // 其他方法声明... }
其中, bucket_t *_buckets
是一个散列表,用来存储 Method
方法列表,而 bucket_t
结构体定义如下,包含了一个 unsigned long
类型的 _key
和一个 IMP
类型的 _imp
,存储了指针与 IMP 的键值对。 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__ MethodCacheIMP _imp; cache_key_t _key; #else cache_key_t _key; MethodCacheIMP _imp; #endif // 其他声明... }
cache_t
中另外两个变量 _mask
和 _occupied
,它们的类型为 mask_t
,定义如下,其实是一个 unsigned int
。
#if __LP64__ typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits #else typedef uint16_t mask_t; #endif
_mask
和 _occupied
对应于 vtable
:
_mask _occupied
cache
的作用主要是对方法调用的性能进行优化。通俗地讲,每当实例对象接收到一个消息时,它不会直接在其 isa
指向的类(或类的 isa
指向的父类)的方法列表中遍历查找能够响应消息的方法实现,因为这样效率太低了,而是优先在 cache
中查找。Runtime 系统会把被调用过的方法存到该类对象的 cache
中(理论上讲一个方法如果被调用,那么它有可能今后还会被调用),下次查找的时候效率更高。– 摘自这里
class_data_bits_t
objc_class
结构体中最复杂的是 bits
成员变量,其类型为 class_data_bits_t
,定义如下:
struct class_data_bits_t { // Values are the FAST_ flags above. uintptr_t bits; public: class_rw_t *data() { return (class_rw_t *)(bits & FAST_DATA_MASK); } // 其他定义... }
从 objc_class
的定义中我们可以发现, objc_class
的 data()
方法直接调用 class_data_bits_t
的 data()
方法,最终是返回 class_rw_t *
。
class_rw_t
Objective-C 的类的属性、方法、以及遵循的协议在 2.0 版本之后都放在 class_rw_t
结构体中,其定义如下:
struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint32_t version; const class_ro_t *ro; method_array_t methods; property_array_t properties; protocol_array_t protocols; Class firstSubclass; Class nextSiblingClass; char *demangledName; // 其他定义... }
class_rw_t
提供了运行时对类拓展的能力,而 class_ro_t
存储的大多是类在编译时就已经确定的信息。二者都存有类的方法、属性(成员变量)、协议等信息,不过存储它们的列表实现方式不同。
class_rw_t
中使用的 method_array_t
, property_array_t
, protocol_array_t
都继承自 list_array_tt<Element, List>
, 它可以不断扩张,因为它可以存储 list 指针,内容有三种:(1)空;(2)一个 entsize_list_tt
指针;(3) entsize_list_tt
指针数组。
class_rw_t
的内容是可以在运行时被动态修改的,可以说运行时对类的拓展大都是存储在这里的。
class_ro_t
通过源码,我们可以知道, objc_class
结构体包含了 class_data_bits_t
, class_data_bits_t
存储了 class_rw_t
的指针,而 class_rw_t
结构体又包含 class_ro_t
的指针。
在 class_rw_t
中, 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 const uint8_t * ivarLayout; const char * name; method_list_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; const uint8_t * weakIvarLayout; property_list_t *baseProperties; method_list_t *baseMethods() const { return baseMethodList; } };
其中, class_ro_t
里的 method_list_t
, ivar_list_t
, property_list_t
结构体都继承自 entsize_list_tt<Element, List, FlagMask>
,而 protocol_list_t
与前三个不同,它存储的是 protocol_t *
指针列表,实现比较简单。
entsize_list_tt
实现了 non-fragile
特性的数据结构,定义如下:
template <typename Element, typename List, uint32_t FlagMask> struct entsize_list_tt { uint32_t entsizeAndFlags; uint32_t count; Element first; // 其他方法声明 }
关于 entsize_list_tt
的分析和 Non Fragile ivars
特性,详见 这篇文章 。
假如苹果在新版本的 SDK 中向 NSObject
类增加了一些内容, NSObject
的占据的内存区域会扩大,开发者以前编译出的二进制中的子类就会与新的 NSObject
内存有重叠部分。 Non Fragile ivars
特性在编译期会给 instanceStart
和 instanceSize
赋值,确定好编译时每个类的所占内存区域起始偏移量和大小,这样只需将子类与基类的这两个变量作对比即可知道子类是否与基类有重叠,如果有,也可知道子类需要挪多少偏移量。
综上, objc_class
中 class_data_bits_t
, class_rw_t
和 class_ro_t
的关系如下:
关于 Objective-C 2.0 中方法结构的深入解析,详见 这篇文章 。
isa
在上一篇文章中,我们介绍了,对象是某一个类的实例,在 Objective-C 中,类也被设计成一个对象,它是其对应的元类(Meta Class)的实例,而元类其实也是一个对象,它的类型是根元类(Root Meta Class),根元类的类型是它自己,它们的继承关系和 isa 指向关系如下图所指示:
此外,类对象和元类对象是全局唯一的,而对象是可以在运行时创建无数个的。在 main 方法执行之前,从 dyld 到 Runtime 加载期间,类对象和元类对象在此时被创建。
在 Objective-C 1.0 中, isa
只是一个指向 Class
的指针,而在 2.0 中, objc_object
中的 isa
是一个 isa_t
类型的共用体(union),在 objc-private.h 文件中定义如下:
#include "isa.h" union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; #if defined(ISA_BITFIELD) struct { ISA_BITFIELD; // defined in isa.h }; #endif };
其中, ISA_BITFIELD
宏定义了 isa_t
在不同处理器架构( arm64
和 x86_64
)下的结构,每个字段的含义如下表:
变量名 | 含义 |
---|---|
indexed | 0 表示普通的 isa 指针,1 表示使用优化,存储引用计数 |
has_assoc | 表示该对象是否包含 associated object,如果没有,则析构时会更快 |
has_cxx_dtor | 表示该对象是否有 C++ 或 ARC 的析构函数,如果没有,则析构时更快 |
shiftcls | 类的指针 |
magic | 固定值为 0xd2,用于在调试时分辨对象是否未完成初始化。 |
weakly_referenced | 表示该对象是否有过 weak 对象,如果没有,则析构时更快 |
deallocating | 表示该对象是否正在析构 |
has_sidetable_rc | 表示该对象的引用计数值是否过大无法存储在 isa 指针 |
extra_rc | 存储引用计数值减一后的结果 |
示意图:
关于 isa
的更多分析和 Tagged Pointer
的概念,详见 这篇文章 和 这篇文章 。
SEL
在 objc.h 中,定义了 SEL
为指向 struct objc_selector
的指针:
/// An opaque type that represents a method selector. typedef struct objc_selector *SEL;
在 objc4 源码中,我们找不到 objc_selector
结构体的具体定义,我们可以把 SEL
理解为就是个映射到方法名的 C 字符串。
此外,不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器。
我们可以通过编译器命令 @selector()
或者 Runtime 系统提供的 sel_registerName
函数来获得一个 SEL
类型的方法选择器。
IMP
在 objc.h 中, IMP
的定义如下:
/// A pointer to the function of a method implementation. #if !OBJC_OLD_DISPATCH_PROTOTYPES typedef void (*IMP)(void /* id, SEL, ... */ ); #else typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); #endif
根据宏判断,我们可以知道,在 2.0 中, IMP
为:
typedef void (*IMP)(void /* id, SEL, ... */ );
IMP
是一个函数指针,指向方法最终的函数实现,它的参数与 objc_msgSend
函数的参数类型相同,关于 Objective-C 的“方法调用”与“消息转发”,我们这里不再赘述。
Method
在 objc-private.h 中, Method
被定义为一个指向 method_t
结构体的指针:
#if __OBJC2__ typedef struct method_t *Method;
method_t
结构体在 objc-runtime-new.h 中定义:
struct method_t { SEL name; const char *types; MethodListIMP imp; // 其他定义... };
其中 MethodListIMP
在 objc-ptrauth.h 中声明,可以把它理解为就是 IMP
函数指针:
#if __has_feature(ptrauth_calls) // Method lists use process-independent signature for compatibility. // Method caches use process-dependent signature for extra protection. // (fixme not yet __ptrauth(...) because of `stp` inline asm in objc-cache.mm) using MethodListIMP = IMP __ptrauth_objc_method_list_imp; using MethodCacheIMP = StorageSignedFunctionPointer<IMP, ptrauth_key_process_dependent_code>; #else using MethodListIMP = IMP; using MethodCacheIMP = IMP; #endif
所以, Method
其实存储了方法名,方法类型和方法实现:
SEL types imp
Ivar
在 objc-private.h 中, Ivar
被定义为一个指向 ivar_t
结构体的指针:
#if __OBJC2__ typedef struct ivar_t *Ivar;
ivar_t
结构体在 objc-runtime-new.h 中定义:
struct ivar_t { #if __x86_64__ // *offset was originally 64-bit on some x86_64 platforms. // We read and write only 32 bits of it. // Some metadata provides all 64 bits. This is harmless for unsigned // little-endian values. // Some code uses all 64 bits. class_addIvar() over-allocates the // offset for their benefit. #endif int32_t *offset; const char *name; const char *type; // alignment is sometimes -1; use alignment() instead uint32_t alignment_raw; uint32_t size; uint32_t alignment() const { if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT; return 1 << alignment_raw; } };
Ivar
用于表示类中实例变量(成员变量)的类型, ivar_t
中定义了实例变量的名字和类型。
Property
我们知道, @property
用于声明类中的属性,在 Runtime 系统中, objc_property_t
是一个指向 objc_property
结构体的指针,在 objc-private.h 中定义:
#if __OBJC2__ typedef struct property_t *objc_property_t;
property_t
结构体在 objc-runtime-new.h 中定义:
struct property_t { const char *name; const char *attributes; };
entsize_list_tt
此外,在 objc-runtime-new.h 中也定义了 method_list_t
, ivar_list_t
, property_list_t
,它们都继承于 entsize_list_tt
,如下:
// Two bits of entsize are used for fixup markers. struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> { bool isFixedUp() const; void setFixedUp(); uint32_t indexOfMethod(const method_t *meth) const { uint32_t i = (uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize()); assert(i < count); return i; } }; struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> { bool containsIvar(Ivar ivar) const { return (ivar >= (Ivar)&*begin() && ivar < (Ivar)&*end()); } }; struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> { };
entsize_list_tt
的声明如下:
template <typename Element, typename List, uint32_t FlagMask> struct entsize_list_tt { uint32_t entsizeAndFlags; uint32_t count; Element first; // 其他定义... }
它们将在上述提到的 class_ro_t
的结构体中使用。
Category
在 objc-private.h 中, Category
被定义为一个指向 category_t
结构体的指针:
#if __OBJC2__ typedef struct category_t *Category;
category_t
结构体在 objc-runtime-new.h 中定义:
struct category_t { const char *name; classref_t cls; struct method_list_t *instanceMethods; struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; // Fields below this point are not always present on disk. struct property_list_t *_classProperties; method_list_t *methodsForMeta(bool isMeta) { if (isMeta) return classMethods; else return instanceMethods; } property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi); };
category_t
存储了分类中可以拓展的实例方法、类方法、协议、实例属性和类属性。在 App 启动时,Runtime 加载完类后,会通过调用 attachCategories
函数进行向类中添加 Category
的工作。原理就是向 class_rw_t
中的 method_array_t
, property_array_t
, protocol_array_t
数组中分别添加 method_list_t
, property_list_t
, protocol_list_t
指针。
Protocol
在 objc-runtime-new.h 中定义了 protocol_t
结构体:
struct protocol_t : objc_object { const char *mangledName; struct protocol_list_t *protocols; method_list_t *instanceMethods; method_list_t *classMethods; method_list_t *optionalInstanceMethods; method_list_t *optionalClassMethods; property_list_t *instanceProperties; uint32_t size; // sizeof(protocol_t) uint32_t flags; // Fields below this point are not always present on disk. const char **_extendedMethodTypes; const char *_demangledName; property_list_t *_classProperties; // 其它定义... }
protocol_t
继承于 objc_object
,其中定义了协议中声明的实例方法,类方法,可选实例方法,可选类方法,实例属性和类属性等。此外,由于 Swift 还支持 Protocol
多继承,所以需要 protocols
数组来做兼容。
protocol_list_t
定义如下:
struct protocol_list_t { // count is 64-bit by accident. uintptr_t count; protocol_ref_t list[0]; // variable-size // 其它定义... }
总结
通过两篇文章的整理,我们可以用如下两张图( 来源 )来分别表示 Objective-C 1.0 和 2.0 版本中类和对象的定义,及相关数据结构的关系:
参考
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- kubernetes自定义资源对象高级功能
- kubernetes自定义资源对象高级功能
- Python 进阶:自定义对象实现切片功能
- Objective-C 1.0 中类与对象的定义
- powershell – 如何向自定义对象添加更多属性值
- Bean定义对象BeanDefinition注册到IOC容器中
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。