内容简介:获取方法列表:获取属性列表:
typedef struct objc_selector *SEL;
IMP :指针,指向方法的具体实现地址
typedef void (*IMP)(void /* id, SEL, ... */ );
id :表示任意的OC类型
/// 代表OC类 typedef struct objc_class *Class; /// 执行类实例的指针 typedef struct objc_object *id;
Class
是 objc_class
结构体类型的指针。 id
是 objc_object
结构体类型的指针。 id
表示对象, Class
表示类。
isa : 指向实例所属的类
struct objc_object { isa_t isa; //... };
- 实例的
isa
指向class
,当调用对象方法时,通过isa
找到对应的类,找到对应的方法进行调用。如果没有通过superclass
查找父类。 - 类的
isa
指向meta-class
,当调用类方法时,通过isa
找到对应的元类,找到对应的方法进行调用。如果没有通过superclass
查找父类。
Method
typedef struct method_t *Method; struct method_t { // 不同类的方法选择器可以是相同的 // typedef struct objc_selector *SEL; // objc_selector 未开源,猜测应该与Char型相关。 SEL name; // 函数名,类似 C语言 字符串,@selector()和 sel_registerName()获取字符串。 const char *types; //编码(返回值类型、参数类型)使用Type Encode IMP imp;//指向函数的指针(函数地址) };
获取方法列表:
// cls : 类,outCount: 方法数量 Method * class_copyMethodList(Class cls, unsigned int *outCount)
Ivar : 实例变量
typedef struct ivar_t *Ivar; struct ivar_t { int32_t *offset; const char *name; const char *type; // alignment is sometimes -1; use alignment() instead uint32_t alignment_raw; uint32_t size; };
Category :分类
typedef struct category_t *Category; // 分类结构体 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; };
objc_property_t : 实例属性
typedef struct property_t *objc_property_t; struct property_t { const char *name; const char *attributes; };
获取属性列表:
Ivar * class_copyIvarList(Class cls, unsigned int *outCount)
Cache : 缓存
// 缓存曾经调用过的方法,提高查找速率 struct cache_t { struct bucket_t *_buckets; // 散列表, SLE :IMP mask_t _mask; //散列表的长度 - 1 mask_t _occupied; // 已经缓存的方法数量,散列表的长度使大于已经缓存的数量的。 }; struct bucket_t { cache_key_t _key; //SEL作为Key IMP _imp; // 函数的内存地址 }; typedef uintptr_t cache_key_t; // ---> unsigned long 类型 typedef uint32_t mask_t;
objc_object : 对象
struct objc_object { isa_t isa; };
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(); // &FAST_DATA_MASK 获取地址值 } // ... };
继承自 objc_object
, 所以 objc_class
也是对象,有成员变量 isa
。
元类
元类是类对象的类。 objc_class
继承自 objc_object
它也包含 isa
指针,类自身也是对象,称为类对象。类对象对应的类就称为元类。实例对象的 isa
指针指向所对应的类,类的 isa
指针指向元类。
method_t
// method_t是对方法/函数的封装 struct method_t { // 不同类的方法选择器可以是相同的 // typedef struct objc_selector *SEL; // objc_selector 未开源,猜测应该与Char型相关。 SEL name; // 函数名,类似C语言字符串,@selector()和 sel_registerName()获取。 const char *types; //编码(返回值类型、参数类型)使用Type Encode IMP imp;//指向函数的指针(函数地址) //... };
-
SEL
:函数名,通过@selector()
和sel_registerName()
获取。通过sel_getName()
和NSStringFromSelector()
转成字符串。 -
types
: 表示返回值类型和参数类型,使用 Type Encodings - NSHipster ,另外,iOS提供了一个叫@encode
的指令,可以将具体的类型表示成字符串编码。
category_t
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
结构体,里面存储着分类的对象方法、类方法、属性、协议信息等。
在程序运行时,运行时系统会将分类的数据,合并到类信息中(类对象或者元类对象中)。
category
不同于 extension
,前者运行时合并信息,后者编译时就已经合并。
分类中有 load
方法吗? load
方法的调用顺序?
分类中存在 load
方法,在运行时加载类、分类的时候调用。
另外, load
方法可以继承,通常情况下系统会自动调用,无需手动。
先调用类的,按照编译顺序调用。调用子类的之前先调用父类的。在调用分类的 load
方法,按照编译顺序(先编译先调用)。
提到 load
方法,还有一个叫 initialize
方法。
initialize
方法会在类第一接收到消息时调用。先调用父类,在调用子类。
initialize
是通过 objc_msgSend()
方法实现的。
不一样的 isa
网上很多关于 isa
的资料,发现大部分都是旧版的 isa
结构。所以学习新的运行时源码整理下面的笔记。
旧版 runtime
中 isa
,在苹果提供的运行时源码 runtime.h
文件中, isa
定义如下:
typedef struct objc_class *Class; Class _Nonnull isa OBJC_ISA_AVAILABILITY;
isa
是 objc_class
结构体类型的指针。
源码文件 objc_runtime_new.h/mm
。 新版 isa
此时不再是 objc_class
结构体类型,修改成了 isa_t
类型。 这段代码是运行时系统定义的对象结构体。
struct objc_object { isa_t isa; // ... };
另外,上面也提及到类的结构体 objc_class
继承自 objc_object
结构体,那么 objc_class
也包含 isa_t
类型的变量。
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(); // &FAST_DATA_MASK 获取地址值 } // ... };
接下来我们看看 isa_t
究竟是什么?不过在此之前先复习一下共用体。
共用体
首先回顾一下 共用体
特点,通过对比结构体来理解共用体。
- 结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙)。
- 共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
上面提到共用体的特性: 共用体占用的内存等于最大的成员占用的内存 。这就说明共用体中的成员是共用一段内存,修改其中的值势必会修改其他的值。
下面通过一段代码理解共用体。
union data { int n; // 4 char ch; // 1 short f; // 2 }; int main(int argc, const char * argv[]) { union data a; printf("%lu, %lu\n", sizeof(a), sizeof(union data)); // 大小为 4 a.n = 0x40; // %X 16进制输出; %hX 16进制输出short int(2字节) printf("%X, %c, %hX\n", a.n, a.ch, a.f); // 40, @, 40 a.ch = '9'; printf("%X, %c, %hX\n", a.n, a.ch, a.f); // 39, 9, 39 a.f = 0x2059; printf("%X, %c, %hX\n", a.n, a.ch, a.f); // 2059, Y, 2059 a.n = 0x3E25AD54; printf("%X, %c, %hX\n", a.n, a.ch, a.f); // 3E25AD54, T, AD54 return 0; }
输出结果:
4, 4 40, @, 40 39, 9, 39 2059, Y, 2059 3E25AD54, T, AD54
共用体的内存结构。
在每次存入值时,共用体中成员的值都会发生改变。
isa_t
共用体
首先,运行时源代码 isa_t
的定义如下:
union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; # if __arm64__ // arm64位系统 # define ISA_MASK 0x0000000ffffffff8ULL //用来取出33位内存地址使用(&)操作 # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL struct { uintptr_t nonpointer : 1; //0:代表普通指针,1:表示优化过的,可以存储更多信息。 uintptr_t has_assoc : 1; //是否设置过关联对象。如果没设置过,释放会更快 uintptr_t has_cxx_dtor : 1; //是否有C++的析构函数 uintptr_t shiftcls : 33; // 存储着Class、Meta-Class对象的内存地址信息 uintptr_t magic : 6; //用于在调试时分辨对象是否未完成初始化 uintptr_t weakly_referenced : 1; //是否有被弱引用指向过 uintptr_t deallocating : 1; //是否正在释放 uintptr_t has_sidetable_rc : 1; //引用计数器是否过大无法存储在ISA中。如果为1,那么引用计数会存储在一个叫做SideTable的类的属性中 uintptr_t extra_rc : 19; //里面存储的值是引用计数器减1 # define RC_ONE (1ULL<<45) # define RC_HALF (1ULL<<18) }; // .... };
参考上面的注释。
获取ISA
新版的 isa
获取真实的内存地址需要进行一次位运算。
# if __arm64__ // arm64位系统 # define ISA_MASK 0x0000000ffffffff8ULL # elif __x86_64__ // arm32为系统 # define ISA_MASK 0x00007ffffffffff8ULL # endif
获取真实内存地址 (Class)(isa.bits & ISA_MASK);
验证上面结论:
NSObject *objc = [[NSObject alloc] init]; Class objcClass = [NSObject class];
设置断点,通过 LLDB
命令:
(lldb) p/x objc->isa (Class) $1 = 0x001dffff8da8a141 NSObject (lldb) p/x objcClass (Class) $2 = 0x00007fff8da8a140 NSObject
objc实例对象的isa指向的地址: 0x001dffff8da8a141
;objcClass的指向的地址值: 0x00007fff8da8a140
。 通过一次位运算 0x001dffff8da8a141 & ISA_MASK
等到 0x00007fff8da8a140
。
窥探 objc_class
结构
上面基础术语中给出了 objc_class
结构体的定义:
#define FAST_DATA_MASK 0x00007ffffffffff8UL struct objc_class : objc_object { // Class ISA; Class superclass; //父类指针 cache_t cache; // 方法缓存 class_data_bits_t bits; // 用于获取地址 class_rw_t *data() { return bits.data(); // &FAST_DATA_MASK 获取地址值 } // ... };
-
bits
获取地址,通过&FAST_DATA_MASK
运算获取。 -
superclass
: 指向父类的指针。 -
cache
: 方法缓存,后面详细讲解。 -
isa
: 指向元类。 -
data
: 指向class_rw_t
结构体类型。
class_rw_t
结构体:
struct class_rw_t { uint32_t flags; uint32_t version; const class_ro_t *ro; // 指向只读的结构体,存放类初始信息 /* 这三个都是二位数组,是可读可写的,包含了类的初始内容、分类的内容。 methods中,存储 method_list_t ----> method_t 二维数组,method_list_t --> method_t 这三个二位数组中的数据有一部分是从class_ro_t中合并过来的。 */ method_array_t methods; // 方法列表(类对象存放对象方法,元类对象存放类方法) property_array_t properties; // 属性列表 protocol_array_t protocols; //协议列表 Class firstSubclass; Class nextSiblingClass; char *demangledName; };
methods
、 properties
、 protocols
都是二维数组,包含类的初始内容、分类的内容和协议等。
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; } };
进阶部分
KVO
KVO实现原理
iOS
是如何实现对一个对象的 KVO
?
通过运行时系统动态生成一个子类,并且让实例对象的 isa
指向这个全新的子类。当实例对象的属性被修改时,通过调用 Foundation
的 NSSetXXXValueAndNotify
函数。实现如下:
willChangeValueForKey: didChangeValueForKey:
内部会调用监听器的监听方法: observeValueForKeyPath:ofObject:change:context:
。
如果想要手动触发 KVO
就需要手动调用, willChangeValueForKey:
和 didChangeValueForKey:
方法。 直接修改成员变量不会触发 KVO
,但是通过属性可以触发 KVO
。
关联对象
有时需要给分类添加成员变量,通常使用 关联对象
间接实现。
objc_setAssociatedObject objc_getAssociatedObject objc_removeAssociatedObjects
推荐用法: 使用get方法的@selector作为key。
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC) objc_getAssociatedObject(obj, @selector(getter))
实现原理
参考文章: OS底层原理总结 - 关联对象实现原理
objc_msgSend、动态方法解析、消息转发
在 OC
中调用方法,其实都是转成 objc_msgSend
方法。它的执行流程分为三步:
- 消息发送
- 动态方法解析
- 消息转发
以上所述就是小编给大家介绍的《iOS开发进阶:Runtime》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- ABP开发框架前后端开发系列---(14)基于Winform的ABP快速开发框架
- Java开发人员的Flex开发
- Java开发人员的Flex开发
- 行为驱动开发让开发做正确事
- 让开发者专注于应用开发,OpenCenter 3.0 开发者预览版发布
- 让开发者专注于应用开发,OpenCenter 3.0 开发者预览版发布
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
程序员面试笔试宝典
何昊、叶向阳、窦浩 / 2012-10 / 59.80元
《程序员面试笔试宝典》除了对传统的计算机相关知识(C/C++、数据结构与算法、操作系统、计算机网络与通信、软件工程、数据库、智力题、英语面试等)进行介绍外,还根据当前计算机技术的发展潮流,对面试笔试中常见的海量数据处理进行了详细的分析。同时,为了更具说服力,《程序员面试笔试宝典》特邀多位IT名企面试官现身说法,对面试过程中求职者存在的问题进行了深度剖析,同时《程序员面试笔试宝典》引入了一批来自于名......一起来看看 《程序员面试笔试宝典》 这本书的介绍吧!