Objective-C Runtime的介绍及在工程中的使用

class-diagram

Overview

在项目的开发过程中,一些功能使用了Runtime去实现。于是在闲暇之余决定对Runtime在项目中使用的一些案例进行整理。毕竟好记性不如烂笔头,下次使用的时候不可能去一点点翻看之前项目中的源码。

关于Runtime

1
2
3
4
5
6
The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.

This document looks at the NSObject class and how Objective-C programs interact with the runtime system. In particular, it examines the paradigms for dynamically loading new classes at runtime, and forwarding messages to other objects. It also provides information about how you can find information about objects while your program is running.

You should read this document to gain an understanding of how the Objective-C runtime system works and how you can take advantage of it. Typically, though, there should be little reason for you to need to know and understand this material to write a Cocoa application.

关于Runtime的介绍在Objective-C Runtime Programming Guide中也有详细的介绍,我摘录了一些放到上面。

Objective-C动态语言,将静态语言再编译和链接时做的决策推迟到运行时,这样它就会动态地完成任务。这就意味着Objective-C不仅需要编译器,还需要运行时系统来执行编译后的代码。而这个运行时系统就是Runtime。

在写这篇博客之前也查找了一些资料,在参考资料里可以看到。一些博客中也有关于isa、SEL、IMP、Method的介绍。如果要详细地了解Runtime,可以先明白isa、SEL、IMP、Method这些名词的概念,然后再使用Runtime实现开发中的一些功能。

为了便于后期的查阅,所以这里摘录了其他资料、博客中关于这些名词的解释,如下:

isa:OC中,类和类的实例在本质上没有区别,都是对象,任何对象都有isa指针,它指向类或元类。

SEL:SEL(选择器)是方法的selector的指针。方法的selector表示运行时方法的名字。OC在编译时,会依据每一个方法的名字、参数,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。

IMP:IMP是一个函数指针,指向方法最终实现的首地址。SEL就是为了查找方法的最终实现IMP。

Method:用于表示类定义中的方法,它的结构体中包含一个SEL和IMP,相当于在SEL和IMP之间作了一个映射。

关于Runtime的使用

在公司的几个项目中,使用Runtime实现的功能并不多。

一个就是在接收到推送的时候。需要跳转到某个ViewController并对这个ViewController的某个属性进行赋值。我实现的方法是先获取这个对象(ViewController)的属性列表,然后判断是否有服务端推送消息中包含的那个属性,如果有就使用KVC进行赋值,然后跳转。

还有一个就是处理数组和字典的时候。我们都知道如果数组不进行越界处理等等等,很容易因为数据的原因而出现问题。所以在项目中使用了IMP指针替换了系统的原生方法,对一些可能导致问题的方法进行处理。

下面是整理的一些关于Runtime的使用,代码存放再GitHub可以直接使用。而关于字典和数组的处理代码同样存放再GitHub

Runtime的使用01:获取某个实例的属性列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

#pragma mark ---
#pragma mark --- 获取某个实例的属性列表 ---
+ (NSMutableArray *)propertiesInfoWithInstance:(id)instance{

NSMutableArray *propertieAry = [NSMutableArray array];

unsigned int outCount, i;
// 获取对象里的属性列表
objc_property_t * properties = class_copyPropertyList([instance class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property =properties[i];
// 属性名转成字符串
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];

[propertieAry addObject:propertyName];

}

free(properties);

return propertieAry;

}

Runtime的使用02:获取某个类的成员变量列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

#pragma mark ---
#pragma mark --- 获取某个类的成员变量列表 ---
+ (NSMutableArray *)ivarInfoWithInstance:(id)instance{

NSMutableArray *ivarInfoAry = [NSMutableArray array];
unsigned int count, i;

Ivar *ivars = class_copyIvarList([instance class], &count);
for (i = 0 ; i < count ; i ++ ) {

NSString *name = [NSString stringWithCString:ivar_getName(ivars[i]) encoding:NSUTF8StringEncoding];
[ivarInfoAry addObject:name];
}
free(ivars);
return ivarInfoAry;

}

Runtime的使用03:获取某个类的方法列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

#pragma mark ---
#pragma mark --- 获取某个类的方法列表 ---
+ (NSMutableArray *)methodListWithInstance:(id)instance{

NSMutableArray *methodListAry = [NSMutableArray array];
unsigned int count, i;

Method *methods = class_copyMethodList([instance class], &count);
for (i = 0 ; i < count; i ++) {
SEL name = method_getName(methods[i]);
NSString *methodStr = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
[methodListAry addObject:methodStr];
}
free(methods);
return methodListAry;

}

Runtime的使用04:检测对象的某个属性是否存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

#pragma mark -
#pragma mark --- 检测对象的某个属性是否存在 ---
+ (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
{
unsigned int outCount, i;
// 获取对象里的属性列表
objc_property_t * properties = class_copyPropertyList([instance class], &outCount);

for (i = 0; i < outCount; i++) {
objc_property_t property =properties[i];
// 属性名转成字符串
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
// 判断该属性是否存在
if ([propertyName isEqualToString:verifyPropertyName]) {
free(properties);
return YES;
}
}
free(properties);
return NO;
}

Runtime的使用05:IMP指针替换系统方法

1
2
3
4
5
6
7
8
9
10
11
12

#pragma mark ---
#pragma mark --- IMP指针替换系统方法 ---
+ (void)SwizzlingMethod:(NSString *)systemMethodString systemClassString:(NSString *)systemClassString toSafeMethodString:(NSString *)safeMethodString targetClassString:(NSString *)targetClassString{
//获取系统方法IMP
Method sysMethod = class_getInstanceMethod(NSClassFromString(systemClassString), NSSelectorFromString(systemMethodString));
//自定义方法的IMP
Method safeMethod = class_getInstanceMethod(NSClassFromString(targetClassString), NSSelectorFromString(safeMethodString));
//IMP相互交换,方法的实现也就互相交换了
method_exchangeImplementations(safeMethod,sysMethod);
}

Runtime的使用06:给系统类动态添加属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

#pragma mark ---
#pragma mark --- 给系统类动态添加属性 ---

- (void)setName:(NSString *)name{
/*
objc_setAssociatedObject 将某个值跟某个对象关联起来,将某个值存储到某个对象中。
object: 给哪个对象添加属性
key:属性的名称
value: 属性值
policy:保存策略
*/
// objc_setAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>, <#id _Nullable value#>, <#objc_AssociationPolicy policy#>)
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)name{
/*
object:对象
key:属性的名称
*/
// objc_getAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>)
return objc_getAssociatedObject(self, @"name");
}


参考资料