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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,541评论 33 466
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,170评论 0 7
  • 文中的实验代码我放在了这个项目中。 以下内容是我通过整理[这篇博客] (http://yulingtianxia....
    茗涙阅读 909评论 0 6
  • 很少有app或网站是只采用一种推荐方式的,大部分都是采用混合的推荐机制,应用得比较多的是基于内容的推荐+协同过滤推...
    聪明的真真阅读 2,344评论 2 6