一.Runtime原理
1.Runtime简称运行时.OC就是运行时机制,(就是系统在运行的时候的一些机制)其中最主要的是消息机制.对于C语言,函数的调用在编译的时候会决定调用哪个函数.对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用.
2.它是一个主要使用C和汇编写的库,为C添加了面相对象的能力并创造了Objective-C.这就是说它在类信息(Class information)中被加载,完成所有的方法分发,方法转发,等等.Objective-C runtime 创建了所有需要的结构体,让Objective-C 的面相对象编程变为可能.
3.是一套比较底层的纯C语言API,属于1个C语言库, 包含了很多底层的C语言API.在我们平时编写的OC代码中,程序运行过程时,其实最终都是转成了runtime的C语言代码,runtime算是OC的幕后工作者.
4.因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时.也就是说只有编译器是不够的,还需要一个运行时系统(runtime system)来执行编译后的代码.这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石.
5.Mac和iPhone开发者关心的有两个runtime:Modern Runtime(现代的 Runtime)和 Legacy Runtime(过时的Runtime).Modern Runtime:覆盖所有 64 位的 Mac OS X 应用和所有 iPhone OS 的应用.Legacy Runtime:覆盖其他的所有应用(所有32位的 Mac OS X 应用)Method有2种基本类型的方法.Instance Method(实例方法).Class Method(类方法)
二.Runtime相关的头文件
sr/include/objc中的头文件
三.Runtime使用
Person.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject{
@private
float _height;
}
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)int age;
@end
NS_ASSUME_NONNULL_END
Person.m
#import "Person.h"
#import <objc/runtime.h>
const char* propertiesKey = "propertiesKey";
@implementation Person
/**
应用2:NSCoding归档和解档
获取属性\成员列表另外一个重要的应用就是进行归档和解档,其原理和上面的kvc基本上一样,这里只是展示一些代码:
*/
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(self.class, &count);
for (int i = 0; i < count; i++) {
const char *cname = ivar_getName(ivars[I]);
NSString *name = [NSString stringWithUTF8String:cname];
NSString *key = [name substringFromIndex:1];
id value = [self valueForKey:key]; // 取出key对应的value
[aCoder encodeObject:value forKey:key]; // 编码
}
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(self.class, &count);
for (int i = 0; i < count; i++) {
const char *cname = ivar_getName(ivars[I]);
NSString *name = [NSString stringWithUTF8String:cname];
NSString *key = [name substringFromIndex:1];
id value = [aDecoder decodeObjectForKey:key]; // 解码
[self setValue:value forKey:key]; // 设置key对应的value
}
}
return self;
}
/**
3_3.类\对象的关联对象
关联对象不是为类\对象添加属性或者成员变量(因为在设置关联后也无法通过ivarList或者propertyList取得) ,而是为类添加一个相关的对象,通常用于存储类信息,例如存储类的属性列表数组,为将来字典转模型的方便。 例如,将属性的名称存到数组中设置关联
*/
-(void)ws_relevanceObjAction{
const char *propertiesKey = "propertiesKey";
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Person class], &count);
NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count; i++) {
// Ivar pty = ivars[I];
// printf("ivar===%p\n",pty);
const char *cname = ivar_getName(ivars[I]);
NSString *name = [NSString stringWithUTF8String:cname];
NSString *key = [name substringFromIndex:1]; // 去掉_
[arrayM addObject:key];
}
free(ivars);
objc_setAssociatedObject(self, propertiesKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
NSLog(@"%@", arrayM);//(age,height,name)
//objc_setAssociatedObject方法的参数解释:
//第一个参数id object, 当前对象
//第二个参数const void *key, 关联的key,可以是任意类型
//第三个参数id value, 被关联的对象
//第四个参数objc_AssociationPolicy policy关联引用的规则,取值有以下几种:
// enum {
// OBJC_ASSOCIATION_ASSIGN = 0,
// OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
// OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
// OBJC_ASSOCIATION_RETAIN = 01401,
// OBJC_ASSOCIATION_COPY = 01403
// };
//如果想要获取已经关联的对象,通过key取得即可
// NSArray *pList = objc_getAssociatedObject(Person, propertiesKey);
}
#pragma mark 封装可以将以上两种操作封装起来,为Person类增加一个properties类方法,封装上面的操作,用于方便获取类的属性列表。
+ (NSArray *)properties {
// 如果已经关联了,就依据key取出被关联的对象并返回
NSArray *pList = objc_getAssociatedObject(self, propertiesKey);
if (pList != nil) {
return pList;
}
// 如果没有关联,则设置关联对象,并将对象返回
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; ++i) {
// Ivar pty = ivars[I];
const char *cname = ivar_getName(ivars[I]);
NSString *name = [NSString stringWithUTF8String:cname];
NSString *key = [name substringFromIndex:1];
[arrayM addObject:key];
}
free(ivars);
objc_setAssociatedObject(self, propertiesKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
return arrayM.copy;
}
/**
resolve [ri'zɔlv] vt. 决定;溶解;使……分解;决心要做……;[主化]解析 vi. 解决;决心;分解 n. 坚决;决定要做的事
3_4.动态添加方法,拦截未实现的方法 移步Person
每个类都有一下两个类方法(来自NSObject)
+ (BOOL)resolveClassMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel
以上两个一个使用于类方法,一个适用于对象方法。在代码中调用没有实现的方法时,也就是sel标识的方法没有实现 都会现调用这两个方法中的一个(如果是类方法就调用第一个,如果是对象方法就调用第二个)拦截。 通常的做法是在resolve的内部指定sel对应的IMP,从而完成方法的动态创建和调用两个过程,也可以不指定IMP打印错误信息后直接返回。
假如在Person类中没有-sayHi这个方法,如果对象p使用[p performSelector:@selector(sayHi) withObject:nil];那么就会必须经过Person类的resolveInstanceMethod:(SEL)sel方法,在这里为-sayHi指定实现。
*/
void abc(id self, SEL _cmd){
NSLog(@"%@说了hello", [self name]);
}
//动态添加方法:在resolve中添加相应的方法,注意是类方法还是对象方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if ([NSStringFromSelector(sel) isEqualToString:@"sayHi"]) {
class_addMethod(self,sel,abc,"v@:"); //为sel指定实现为abc
// [self performSelector:@selector(ws_testAction)];
// [[[Person alloc] init] performSelector:@selector(ws_testAction)];
}
return YES;
}
-(void)ws_testAction{
NSLog(@"ws_testAction");
}
-(NSString *)description{
return [NSString stringWithFormat:@"{ name=%@, age=%d, height=%f }",self.name,self.age,self->_height];
}
@end
先导入头文件
#import "RuntimeViewController.h"
#import "Person+Runtime.h"
#import "Person.h"
#import <objc/message.h>//包含消息机制
#import <objc/runtime.h>//包含对类、成员变量、属性、方法的操作
@interface RuntimeViewController ()
@end
@implementation RuntimeViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setTitle:@"runTime"];//动态添加属性,修改属性值(类别"Person+Runtime.h")
[self exchangeAttribute];//
// [self performSelector:@selector(likePlay)];
objc_msgSend(self,@selector(likePlay));// 动态调用方法(在 LLVM 6.0 中增加了一个 OBJC_OLD_DISPATCH_PROTOTYPES,默认配置在 Apple LLVM 6.0 - Preprocessing 中的 Enable Strict Checking of objc_msgSend Calls 中为Yes 改成NO;)
[self getAttribute];// 利用runtime遍历一个类的全部成员变量
[self controlVariables];// 动态控制变量
[self addMethod];//动态添加方法
[self exchangeMethod];//动态交换方法实现
}
1.动态为Category扩展加属性
Person+Runtime.h
#import "Person.h"
@interface Person (Runtime)
@property(nonatomic,copy)NSString *height;// 身高
-(void)setHeight:(NSString *)height;
-(NSString *)height;
-(NSString *)addStr1:(NSString *)str1 str2:(NSString *)str2;
@end
Person+Runtime.m
#import "Person+Runtime.h"
#import <objc/message.h>//包含消息机制
#import <objc/runtime.h>//包含对类、成员变量、属性、方法的操作
@implementation Person (Runtime)
static char * heightKey = "heightKey";
-(void)setHeight:(NSString *)height{
objc_setAssociatedObject(self, heightKey, height, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)height{
return objc_getAssociatedObject(self, heightKey);
}
-(NSString *)addStr1:(NSString *)str1 str2:(NSString *)str2{
return [str1 stringByAppendingString:str2];
}
@end
方法实现
-(void)exchangeAttribute{
Person *p = [[Person alloc]init];
p.height = @"178";
NSLog(@"身高==%@",p.height);
}
打印结果
2016-10-24 10:45:09.003 WsBlog[11181:4616582] 身高==178
2.动态控制变量
-(void)controlVariables{
Person * p = [Person new];
p.name = @"wym";
NSLog(@"%@",[p name]);
unsigned int count;
//Ivar表示类中的实例变量。Ivar其实就是一个指向objc_ivar结构体指针,它包含了变量名(ivar_name)、变量类型(ivar_type)等信息。
Ivar *ivar = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i ++) {
Ivar var = ivar[I];
const char *varName = ivar_getName(var);
NSString *name = [NSString stringWithCString:varName encoding:NSUTF8StringEncoding];
if ([name isEqualToString:@"_name"]) {
object_setIvar(p, var, @"ws");
break;
}
}
NSLog(@"%@",p.name);
}
打印结果
2016-10-24 10:45:09.004 WsBlog[11181:4616582] wym
2016-10-24 10:45:09.004 WsBlog[11181:4616582] ws
3.利用runtime动态遍历一个类的全部成员变量
-(void)getAttribute{
//1.导入头文件 <objc/runtime.h>
unsigned int count = 0;
//获取指向该类所有属性的指针
objc_property_t *propeprties = class_copyPropertyList([Person class], &count);
for (int i = 0; i < count; i++) {
//获得
objc_property_t property = propeprties[I];
//根据objc_property_t 获取所有属性的名称--->C语言的字符串
const char *name = property_getName(property);
NSString *attributeName = [NSString stringWithUTF8String:name];
NSLog(@"%d-----%@",i,attributeName);
}
}
打印结果
2016-10-24 10:45:09.003 WsBlog[11181:4616582] 0-----height
2016-10-24 10:45:09.003 WsBlog[11181:4616582] 1-----name
2016-10-24 10:45:09.004 WsBlog[11181:4616582] 2-----gender
2016-10-24 10:45:09.004 WsBlog[11181:4616582] 3-----age
4.动态添加方法
void goHome(id self, SEL _cmd){
NSLog(@"回家");
}
- (void)addMethod
{
Person *p = [Person new];
p.name = @"ET";
class_addMethod([Person class], @selector(shise), (IMP)goHome, "v@:");
[p performSelector:@selector(shise) withObject:nil];
}
打印结果
2016-10-24 10:45:09.004 WsBlog[11181:4616582] 回家
5.动态交换方法实现
-(void)exchangeMethod{
Person *p = [[Person alloc]init];
p.name = @"ET";
[p eat];
[p play];
// 实现方法交换
Method m1 = class_getInstanceMethod([Person class], @selector(eat));
Method m2 = class_getInstanceMethod([Person class], @selector(play));
method_exchangeImplementations(m1, m2);
[p eat];
[p play];
}
打印结果
2016-10-24 10:45:09.004 WsBlog[11181:4616582] ET玩
2016-10-24 10:45:09.005 WsBlog[11181:4616582] ET吃饭
2016-10-24 10:45:09.008 WsBlog[11181:4616582] ET吃饭
2016-10-24 10:45:09.008 WsBlog[11181:4616582] ET玩
6.动态调取方法
-(void)likePlay{
NSLog(@"喜欢玩");
}
打印结果
2016-10-24 10:45:09.003 WsBlog[11181:4616582] 喜欢玩