谈谈Runtime
何为runmtime,字面意思是运行时刻,是指一个程序在运行或者在被执行的状态。也就是说,当你打开一个程序使它在电脑上运行的时候,那个程序就是处于运行时刻。
runtime实质是一套底层的 C 语言 API,Objective-C是一门动态语言,runtime在其中扮演至关重要的角色,程序运行时Objective-C的代码会被转化成runtime的形式执行。
举个例子:
[receiver message]; 底层运行时会被编译器转化为: objc_msgSend(receiver, @selector(message))
作用:
能获得某个类的所有成员变量
能获得某个类的所有属性
能获得某个类的所有方法
能获得某一个类的遵循的所有协议
能动态添加一个成员变量或属性
能动态添加一个方法
能动态添加一个协议
能交换方法
能访问私有变量并赋值
能注册一个协议
...
等等
1.获取成员变量
ivar是一个成员变量
相关函数
获取所有成员变量:class_copyIvarList
获取成员变量名:ivar_getName
获取成员变量类型编码:ivar_getTypeEncoding
获取指定名称的成员变量 :class_getInstanceVariable
设置指定名称的成员变量 : object_setInstanceVariable,在ARC下不可用
获取某个对象成员变量的值: object_getIvar
设置某个对象成员变量的值:object_setIvar
例子:
创建一个继承于Person的Student类
//.h文件
#import "Person.h"
@interface Student : Person{
NSString * cardId;
}
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) NSString *myName;
@end
//.m文件
#import "Student.h"
@interface Student ()
{
NSArray *classmates;
}
@end
@implementation Student
@end
来看看Student的成员变量
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([Student class], &outCount);
for (int i = 0; i < outCount; i ++) {
// 取出i位置对应的成员变量
// Ivar ivar = *(ivars+i);
Ivar ivar = ivars[i];
// 获得成员变量的名字
const char * name = ivar_getName(ivar);
// 获取成员变量类型编码
const char * type = ivar_getTypeEncoding(ivar);
NSLog(@"Student拥有的成员变量的类型为%s,名字为 %s ",type, name);
}
free(ivars);
//如果函数名中包含了copy\new\retain\create等字眼,那么这个函数返回的数据就需要手动释放
打印:
Student拥有的成员变量的类型为@"NSString",名字为 cardId
Student拥有的成员变量的类型为@"NSArray",名字为 classmates
Student拥有的成员变量的类型为q,名字为 _age
Student拥有的成员变量的类型为@"NSString",名字为 _myName
获取指定名称的成员变量
Ivar ivar = class_getInstanceVariable([Student class], "_myName");
const char * name = ivar_getName(ivar);
const char * type = ivar_getTypeEncoding(ivar);
NSLog(@"Student拥有的成员变量的类型为%s,名字为 %s ",type, name);
打印:
Student拥有的成员变量的类型为@"NSString",名字为 _myName
为指定成员变量赋值
Ivar ivar = class_getInstanceVariable([_student class], "_age");
object_setIvar(_student, ivar, @"13");
NSLog(@"%s %@",ivar_getName(ivar),object_getIvar(_student, ivar));
打印:_age 13
2. 获取属性
获取所有属性 class_copyPropertyList
注意:class_copyPropertyList 不仅可以获取@property的属性,也可以获取类扩展上的@property属性
获取属性名 property_getName
获取属性特性描述字符串 property_getAttributes
例子,还是拿上面那个Student类来说明:
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([Student class], &count);
for (int i = 0; i < count; i++) {
objc_property_t property = propertyList[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
NSString *type = [NSString stringWithUTF8String:property_getAttributes(property)];
NSLog(@“类型:%@====名字:%@“,type,name);
}
free(propertyList); //立即释放propertyList指向的内存
打印:
类型:Tq,N,V_age====名字:age
类型:T@“NSString",&,N,V_myName====名字:myName
3.获取方法
获取实例方法的信息 class_getInstanceMethod
获取类方法的信息 class_getClassMethod
获取方法名 SEL method_getName ( Method m )
例子:
在student.h中添加一个方法
- (void)test:(int)a;
+ (void)test2:(int)a;
Method method1 = class_getInstanceMethod([Student class], @selector(test:));
const char * methodName1 = sel_getName(method_getName(method1));
unsigned int num1 = method_getNumberOfArguments(method1);
NSLog(@“该方法为:%s,有%d个参数:”,methodName1,num1);
Method method2 = class_getClassMethod([Student class], @selector(test2:));
const char * methodName2 = sel_getName(method_getName(method2));
unsigned int num2 = method_getNumberOfArguments(method2);
NSLog(@“该方法为:%s,有%d个参数",methodName2,num2);
打印:
该方法为:test:,有3个参数
该方法为:test2:,有3个参数
疑问:估计参数数量多出来的2个是调用的对象和selector
获取方法具体实现:
class_getMethodImplementation(Class cls, SEL name)
class_getMethodImplementation_stret(Class cls, SEL name)
例子:
IMP imp = class_getMethodImplementation([Student class], @selector(test2:));
取得IMP后,我们就获得了执行这个test2:方法代码的入口点,通过取得IMP,我们可以跳过Runtime的消息传递机制,直接执行IMP指向的函数实现
获取方法列表:class_copyMethodList
例子:
unsigned int count;
Method *methodList = class_copyMethodList([Student class],&count);
for (int i = 0; i < count2; i++) {
Method method = methodList[i];
NSLog(@"%s",sel_getName(method_getName(method)));
}
free(methodList);
打印:
test:
age
setAge:
myName
setMyName:
.cxx_destruct
由此可知,除了类方法外,声明的实例方法和属性的set、get方法和.m中没有声明的实例方法都会获取到,也可以获取到当前类的分类中的方法
得到方法的返回值 method_copyReturnType
无返回值void 对应v,有返回值对应@
例子:
Method method = class_getInstanceMethod([Student class], @selector(test:));
char *returnType = method_copyReturnType(method);
NSLog(@"返回的类型是%s",returnType);
打印:返回的类型是v
方法描述 method_getDescription
例子:
Method method = class_getInstanceMethod([Student class], @selector(test:));
struct objc_method_description *description = method_getDescription(method);
NSLog(@“%@"NSStringFromSelector(description->name));
打印:test:
返回指定选择器指定的方法的名称 sel_getName
例子:
const char *selName = sel_getName(@selector(test:));
NSLog(@“%s",selName);
打印:test:
比较两个方法选择器是否相等 sel_isEqual(sel1, sel2)
4.获取协议
获取类遵循的协议的列表 class_copyProtocolList
例子:
Student 遵循了三个协议,Protocol1,Protocol2,Protocol3
@interface Student : Person<Protocol1,Protocol2,Protocol3>
unsigned int count;
Protocol * __unsafe_unretained *protocolList = class_copyProtocolList([_student class],&count);
for (int i = 0; i < count; i++) {
Protocol *protocol = protocolList[i];
NSLog(@"%s",protocol_getName(protocol));
}
free(protocolList);
打印:
Protocol1
Protocol2
Protocol3
获取协议信息 protocol_getName
例子:
const char *name = protocol_getName(protocol1);
Protocol *protocol = NSProtocolFromString([NSString stringWithUTF8String:“NewProtocol’])
创建一个新的协议实例 objc_allocateProtocol
例子
Protocol *protocol = objc_allocateProtocol(“NewProtocol’);
检查该协议是否已经注册 class_conformsToProtocol(class, protocol)
注册协议 objc_registerProtocol(protocol);
比较两个协议是否相等 protocol_isEqual(Protocol *proto, Protocol *other)
5.添加属性、方法、协议
添加属性 class_addProperty(__unsafe_unretained Class , const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
比如为Student类添加一个school属性
objc_property_attribute_t type = { "T", "@\"NSString\"" };
objc_property_attribute_t ownership = { "&", "N" };
objc_property_attribute_t backingivar = { "V", "_" };
objc_property_attribute_t attrs[] = { type, ownership, backingivar };
// objc_property_attribute_t attrs[] = { { "T", "@\"NSString\"" }, { "&", "N" }, { "V", “_" } };
BOOL add = class_addProperty([_student class], “country”, attrs, 3)
注意:只能添加新的成员属性,不包括当前类、分类以及父类的已有属性
objc_property_attribute_t 是一个包含name和value的结构体
常见格式:T@“NSString",&,N,V_myName
属性类型 name值:T value:变化
编码类型 name值:C(copy) &(strong) W(weak)空(assign) 等 value:无
非/原子性 name值:空(atomic) N(Nonatomic) value:无
变量名称 name值:V value:变化
添加方法 BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
- Class cls 给哪个类添加方法
- SEL name 添加的方法
- IMP imp 方法的实现
- const char * types 方法类型
“v@:@“方法的签名,表时有一个参数的无返回值的方法类型。
“@@:” 表示一个返回值的方法类型
v 表示void @ 表示一个类型
例子:
比如我们为Student添加一个test2 方法
class_addMethod([Student class], NSSelectorFromString(@"test2"), nil, “@@:”);
此时再来看看student类的实例方法列表:
unsigned int count;
Method *methodList = class_copyMethodList([Student class],&count);
for (int i = 0; i < count2; i++) {
Method method = methodList[i];
NSLog(@"%s",sel_getName(method_getName(method)));
}
free(methodList);
打印:
test2
test:
age
setAge:
myName
setMyName:
.cxx_destruct
添加协议 class_addProtocol(class, protocol)
例子:
为Student添加一个NewProtocol协议
BOOL addSuccess = class_addProtocol([Student class], @protocol(NewProtocol));
if (addSuccess) {
unsigned int count5;
Protocol * __unsafe_unretained *protocolList = class_copyProtocolList([_student class],&count5);
for (int i = 0; i < count5; i++) {
Protocol *protocol = protocolList[i];
NSLog(@"%s",protocol_getName(protocol));
}
free(protocolList);
}
打印:NewProtocol
6.关联对象
获取关联对象 objc_getAssociatedObject(id object, const void *key)
- id object 获取谁的关联对象
- const void *key 根据这个唯一的key获取关联对象,这个跟添加关联对象时的参数 key一致
添加关联对象 objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
- id object给谁设置关联对象。
- const void *key关联对象唯一的key,获取时会用到。
- id value关联对象。
- objc_AssociationPolicy关联策略,有以下几种策略:
enum {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
};
拿一个为分类添加属性的例子
例子:
为Student一个分类上添加一个name属性
@interface Student (test)
@property (nonatomic, strong) NSString *name;
@end
@implementation Student (test)
//添加关联对象
-(void)setName:(NSString *)name{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//获取关联对象
-(NSString *)name{
return objc_getAssociatedObject(self, _cmd)
};
@end
Student *student = [[Student alloc]init];
student.name = @“李四”;
NSLog@(@“%@”, student.name);
这里面我们把@selector(name)的地址作为唯一的key,_cmd代表当前调用方法的地址。当然这个key可以是字符串等,注意的是这两个key保持一致就可以了。
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC) 的意思就是 给当前这个类(self)添加一个 叫name的 关联属性,而且属性的唯一Id叫 @selector(property)
移除所有关联 objc_removeAssociatedObjects(id object)
6、交换方法
method_exchangeImplementations(Method m1, Method m2)
例子:
+ (void)print1{
NSLog(@"print 类方法");
}
- (void)print2{
NSLog(@"print 实例方法");
}
Method method1 = class_getClassMethod([self class], @selector(print1));
Method method2 = class_getInstanceMethod([self class], @selector(print2));
method_exchangeImplementations(method1, method2);
[self print1];
NSLog(@"==========”);
[self print2];
打印:
print 实例方法
==========
print 实例方法
7.其他
获取类的名称 const char *class_getName(Class cls)
获取类的父类 Class class_getSuperclass(Class cls)
常见的应用场景
使用runtime去解析json来给Model赋值
例子:
创建一个Person类
Person.h
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
+(instancetype)modelWithDict:(NSDictionary *)dict;
- (void)print;
@end
Person.m
#import “Person.h"
#import <objc/runtime.h>
@interface Person ()
@property (nonatomic, strong) NSString *country;
@end
@implementation Person
+ (instancetype)modelWithDict:(NSDictionary *)dict{
Person *objc = [[Person alloc] init];
NSMutableArray *keys = [NSMutableArray array];
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList(self, &count);
for (int i = 0; i < count; i++) {
objc_property_t property = propertyList[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
[keys addObject:name];
}
free(propertyList);
for (NSString * key in keys) {
if ([dict valueForKey:key] == nil) continue;
[objc setValue:[dict valueForKey:key] forKey:key];
}
return objc;
}
- (void)print{
NSLog(@"name:%@ country:%@",_name,_country);
}
@end
Person *person = [Person modelWithDict :@{@“name":@"tom",@"cardId":@"8888",@"age":@"18",@"country":@"China"}];
[person print];
打印:name:tom country:China
当然这个例子根据runtime获取类的属性列表赋值也有很多不足,当类的属性有其他类或者字典等嵌套时,就需要再作其他逻辑判断了
交换方法这个也会用到,设想这样一个场景,当项目中大多地方多用了method1,忽然要改用method2,此时使用runtime交换方法,就不必一个一个在项目里改了,这样就省下了很多功夫,当然runtime还有很多用途,可能实际项目也很少用到,但多了解底层原理和思想可以对Objective-C理解更为深刻。