前言#
runtime 是一个运行时系统,是iOS object-c的核心,使 C语言 具有了面向对象的功能。runtime 库实现了面向对象的继承链。利用 c 的结构体和指针。并且有对象模型的定义。
方法的解析(消息转发)在运行时,编译时不会对方法的实现进行校验,因此我们使用 runtime 库对运行时的对象的属性,方法进行操作。省去了 oc 层。运行 的时候对协议,属性,方法进行整合,如包括 rw 和 ro 整合。
isa 与superclass#
下图为说明:

- 图解 isa 解决对象的定义,类的定义。(对象方法的起源,类方法的起源)
- 图解 superClass 解决了继承问题。(方法调用的追溯).
对象和类对象#
OC中几乎所有的类都继承自NSObject,OC的动态性也是通过NSObject实现的。OC 类、对象和方法等都对应了 C 中的结构体,而且我们都可以在 Rutime 源码中找到它们的定义。
objc_object#
我们可以找到NSObject的定义:
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}可以看出NSObject里有一个指向Class的isa,其中对于Class的定义在objc.h:
typedef struct objc_class *Class;
*/// Represents an instance of a class.*
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};总结:objc_class代表类对象,objc_object代表实例对象,objc_object的isa指向objc_class。这里可以得出一个结论,实例对象的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() const {
return bits.data();
}
}
struct objc_object {
private:
isa_t isa;
}
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
}class_data_bits_t#
我们再回来看类中的其他属性,之前表示类的属性、方法、以及遵循的协议都放在了class_data_bits_t中,更准确的说是放在了class_rw_t。
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
}
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t version;
uint16_t witness;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
}
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;
}这里面引入了class_rw_t和class_ro_t(rw-readwrite,ro-readonly)两个结构体。可以看到class_rw_t是包含一个常量指针ro,结构体为class_ro_t。这里存储了当前类在编译期就已经确定的属性、方法以及遵循的协议。在 ObjC 运行时的时候会调用 realizeClass 方法,将class_ro_t传入class_rw_t,所以新版的动态性是通过这种方式实现的。
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_ro_t#
class_ro_t *ro 是只读的,内部直接存储的直接就是 method_list_t、protocol_list_t 、property_list_t 类型的一维数组,数组里面分别存放的是类的初始信息,以 method_list_t 为例,method_list_t 中直接存放的就是 method_t,但是是只读的
编译的类的方法,属性,成员变量属性协议等等都是存放在 class_ro_t 中的,当程序运行的时候,需要将分类中的列表跟类初始的列表合并在一起的时,就会将 class_ro_t 中的列表和分类中的列表合并起来存放在 class_rw_t 中,也就是说 class_rw_t 中有部分列表是从 class_ro_t 里面拿出来的。并且最终和分类的方法合并。
cache_t#
一些多次调用的方法会放入这个集合中。
method_t#
这是函数的结构体,里面包含3个成员变量。SEL是方法的名字name。types是类型编码,类型可参考Type Encoding。IMP是一个函数指针,指向的是函数的具体实现。在runtime中消息传递和转发的目的就是为了找到IMP,并执行函数。
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};方法被编译为 Method 结构体,同时会有一个 sel 于 imp 对应关系表。可利用 runtime 库 中的函数拿到相应的 Method,imp。
- Method 结构体包括 sel,imp,name。 sel 是方法的名字编号 imp 是方法实现的指针 name 方法的名字。
- 直接调用 imp 效率比较高。
objc_msgSend解析#
objc_msgSend#
向对象发送的消息在编译阶段被转化为 C 函数,即 objc_msgSend。objc_msgSend(receiver,selector) 在 runtime 库中有 objc_msgSend 实现。objc_msgSendSuper 的实现。
objc_msgSend 具体实现
检测 selector target 。有些 selector 会被忽略掉不做处理,如果 target 为 nil 会被忽略掉也不处理。
1.中都不满足,会根据 selector,找到对应 imp 进行执行。
根据 target isa 指针找到对应的类,先从 cache 里面找,若可以找得到就跳到对应的函数去执行。找不到会在 methodLists 查找如果 methodLists 找不到,就到超类的方法列表里寻找,一直找,直到找到 NSObject 类为止。
如果还找不到,Runtime 就提供了如下三种方法来处理:动态方法解析、消息接受者重定向、消息重定向,
这三种方法的调用关系如下图:

实际使用#
runtime 使用的话是使用里面定义好的 c 函数,如关联函数,获取 class 信息函数,反射函数。
获取 class 的信息#
属性列表,方法列表,协议列表,成员变量表 属性列表的使用
动态归档解档使用
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
const char * name = ivar_getName(ivar);
NSString *key = [[NSString alloc] initWithUTF8String:name];
id value = [self valueForKeyPath:key];
[encoder encodeObject:value forKey:key];
}
free(ivars);字典转模型使用
NSDictionary *dict =
@ { @"name" : @"lxz",
@"age" : @18,
@"gender" : @YES };
TestObject *object = [TestObject objectWithDict:dict];
@interface TestObject : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) BOOL gender;
+ (instancetype)objectWithDict:(NSDictionary *)dict;
@end
@implementation TestObject
+ (instancetype)objectWithDict:(NSDictionary *)dict
{
return([[TestObject alloc] initWithDict:dict]);
}
- (instancetype)initWithDict:(NSDictionary *)dict
{
self = [super init];
if ( self )
{
unsigned int count = 0;
objc_property_t *propertys = class_copyPropertyList( [self class], &count ); for ( int i = 0; i < count; i++ )
{
objc_property_t property = propertys[i];
const char *name = property_getName( property );
NSString *nameStr = [[NSString alloc] initWithUTF8String:name];
id value = [dict objectForKey:nameStr];
[self setValue:value forKey:nameStr];
}
free( propertys );
}
return(self);
}属性修改使用
Person *ps = [[Person alloc] init];
NSLog(@"ps-nickName: %@",[ps valueForKey:@"nickName"]); //null
// 第一步:遍历对象的所有属性
unsigned int count;
Ivar *ivarList = class_copyIvarList([ps class], &count);
for (int i= 0; i<count; i++) {
// 第二步:获取每个属性名
Ivar ivar = ivarList[i];
const char *ivarName = ivar_getName(ivar);
NSString *propertyName = [NSString stringWithUTF8String:ivarName];
if ([propertyName isEqualToString:@"_nickName"]) {
// 第三步:匹配到对应的属性,然后修改;注意属性带有下划线
object_setIvar(ps, ivar, @"燕航");
}
}
NSLog(@"ps-nickName: %@",[ps valueForKey:@"nickName"]); // 燕航关联#
runtime 提供了給我们 3 个 API 以管理关联对象(存储、获取、移除):
是一个全局关联表来管理关联的对象以及被关联者的内存。
// 关联对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
// 获取关联的对象
id objc_getAssociatedObject(id object, const void *key)
// 移除关联的对象
void objc_removeAssociatedObjects(id object)其中的参数
- id object:被关联的对象(属性的所属方)
- const void *key:关联的 key,要求唯一
- id value:关联的属性
- objc_AssociationPolicy policy:内存管理的策略
使用例子 NSObject+AssociatedObject.m
#import "NSObject+AssociatedObject.h"
#import <objc/runtime.h>
@implementation NSObject (AssociatedObject)
- (void)setAssociatedObject:(id)associatedObject
{
objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject
{
return objc_getAssociatedObject(self, _cmd);
}
@end第三方框架都有使用如 massonry,MJRefresh 等。 注意:尽管可以模拟地为分类添加“属性”,但毕竟只是模拟。在分类中@property 不会生成_变量,也不会实现 getter 和 setter 方法。我们实现的只是 getter 和 setter 方法,并没有自动生成下划线开头的变量!
操作方法#
- class_replaceMethod 替换类方法的定义
- method_exchangeImplementations 交换 2 个方法的实现
- method_setImplementation 设置 1 个方法的实现
其中 method_exchangeImplementations 比较常用,替换系统方法调用。如埋点,页面统计,崩溃保护。
交换解决崩溃(可以做崩溃保护 Method Swizzling)
@implementation NSArray (Safe)
+ (void)load
{
//注意__NSArray0 类族
[NSClassFromString(@"__NSArray0") swapMethod:@selector(objectAtIndex:)
currentMethod:@selector(ls_zeroObjectAtIndex:)];
}
- (id)ls_zeroObjectAtIndex:(NSUInteger)index
{
if (index >= self.count)
{
return nil;
}
return [self ls_zeroObjectAtIndex:index];
}
//交换两个实例方法( - 方法)
+ (void)swapMethod:(SEL)originMethod currentMethod:(SEL)currentMethod;
{
Method firstMethod = class_getInstanceMethod(self, originMethod);
Method secondMethod = class_getInstanceMethod(self, currentMethod);
method_exchangeImplementations(firstMethod, secondMethod);
}反射机制#
NSString、Class、SEL、Protocol 的转化
FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);
FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
FOUNDATION_EXPORT Class _Nullable NSClassFromString(NSString *aClassName);
FOUNDATION_EXPORT NSString *NSStringFromProtocol(Protocol *proto) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
FOUNDATION_EXPORT Protocol * _Nullable NSProtocolFromString(NSString *namestr) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0))