iOS开发iOS学习

谈谈Runtime

2017-08-04  本文已影响19人  kamto

何为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)

“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)

添加关联对象 objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

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理解更为深刻。

上一篇下一篇

猜你喜欢

热点阅读