iOS-Runtime原理及使用

一.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.png

三.Runtime使用

runtime-use.png
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] 喜欢玩

http://www.jianshu.com/p/e04e66908b09

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

推荐阅读更多精彩内容