iOS开发中runtime介绍

一.runtime简介

  • RunTime简称运行时。OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制。
  • 对于C语言,函数的调用在编译的时候会决定调用哪个函数
  • 对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
  • 事实证明:
    • 在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。
    • 在编译阶段,C语言调用未实现的函数就会报错。
  • 使用runtime之前需要对环境进行如下配置:Enable Strict 选择No。

二.runtime的作用

1.发送消息
    •    方法调用的本质,就是让对象发送消息。
    •    objc_msgSend,只有对象才能发送消息,因此以objc开头.
    •    使用消息机制前提,必须导入#import <objc/message.h>
    •    进入文件所在路径,在终端使用clang -rewrite-objc main.m 指令可查看最终生成代码。

1     // NSObject *objc = [NSObject alloc];
2     NSObject *objc = objc_msgSend([NSObject class], @selector(alloc));
3 
4     // objc = [objc init];
5     objc = objc_msgSend(objc, @selector(init));
6     
7     NSLog(@"%@",objc);

消息机制作用:【调用已知的私有方法】

例:Person类中有两个私有方法。

 1 #import <Foundation/Foundation.h>
 2 
 3 @interface Person : NSObject
 4 
 5 @end
 6 
 7 @implementation Person
 8 
 9 - (void)run:(NSInteger)meter
10 {
11     NSLog(@"跑了%ld米",meter);
12 }
13 
14 - (void)eat
15 {
16     NSLog(@"吃东西");
17 }
18 
19 @end
 1 #import <objc/message.h>
 2 #import "Person.h"
 3 
 4 @interface ViewController ()
 5 
 6 @end
 7 
 8 @implementation ViewController
 9 
10 - (void)viewDidLoad {
11     [super viewDidLoad];
12     
13     //Person *p = [Person alloc];
14     Person *p = objc_msgSend([Person class], @selector(alloc));
15     
16     //p = [p init];
17     p = objc_msgSend(p, @selector(init));
18     
19     // 调用eat
20     //[p eat];
21     objc_msgSend(p, @selector(eat));
22     
23     // runtime
24     // 方法编号后面开始,依次就是方法参数排序
25     // objc_msgSend(id self, SEL op, ...)
26     objc_msgSend(p, @selector(run:),20);
27 }

    // 调用【类方法】的方式有两种
    // 第一种通过类名调用
    [Person eat];
    // 第二种通过类对象调用
    [[Person class] eat];
    
    // 用类名调用类方法,底层会【自动把类名转换成类对象调用】
    // 本质:让【类对象发送消息】
    objc_msgSend([Person class], @selector(eat));

说到这里,不得不问:对象如何找到对应的方法去调用?

回答这个问题,首先要清楚:方法保存到什么地方?--->对象方法保存到类中,类方法保存到元类(meta class)中。每一个类都有方法列表methodList。
    1.根据对象的isa指针去对应的类中查找方法。isa:判断去哪个类查找对应的方法 指向方法调用的类。
    2.根据传入的方法编号(SEL),才能在方法列表中找到对应方法Method(方法名)。
    3.根据方法名(函数入口)找到函数实现。

消息机制原理:对象根据【方法编号SEL】去映射表查找对应的方法实现。

 

2.交换方法

    •    开发使用场景:系统自带的方法功能不能满足需求,给系统自带的方法扩展一些功能,并且保持原有的功能。
    •    方式一:继承系统的类,重写方法。
    •    方式二:使用runtime,交换方法。

需求:给imageNamed方法提供功能,每次加载图片就判断下图片是否加载成功。

    // 步骤一:先搞个分类,定义一个能加载图片并且能打印的方法 +(UIImage *)wm_imageNamed:(NSString *)name;
    // 步骤二:交换imageNamed和wm_imageNamed的实现,就能调用imageNamed,间接调用wm_imageNamed的实现。

写一个UIImage+Image.h的分类:

1 #import <UIKit/UIKit.h>
2 
3 @interface UIImage (Image)
4 
5 // 给方法加前缀,与系统方法区分
6 // 加载图片
7 + (UIImage *)wm_imageNamed:(NSString *)name;
8 
9 @end
 1 #import "UIImage+Image.h"
 2 #import <objc/message.h>
 3 
 4 @implementation UIImage (Image)
 5 
 6 // 加载类的时候调用,肯定只会调用一次
 7 + (void)load
 8 {
 9     // 交换方法实现wm_imageNamed,imageNamed
10     
11     // 获取方法 Method:方法名
12     // 获取类方法
13     // class:获取哪个类方法
14     // SEL:方法编号
15     Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
16     
17     Method wm_imageNameMethod = class_getClassMethod(self, @selector(wm_imageNamed:));
18     
19     method_exchangeImplementations(imageNameMethod, wm_imageNameMethod);
20     
21 }
22 
23 // 加载图片
24 // 判断
25 + (UIImage *)wm_imageNamed:(NSString *)name
26 {
27    //这里调用wm_imageNamed:实际上是调用imageNamed:.
28    UIImage *image = [UIImage wm_imageNamed:name];
29     
30     if (image == nil) {
31         NSLog(@"加载失败");
32     }
33     
34     return image;
35 }
36 @end

外界使用:无需导入分类头文件,直接使用imageNamed:方法即可实现判断图片是否加载成功。

 1 #import "ViewController.h"
 2 
 3 @interface ViewController ()
 4 
 5 @end
 6 
 7 @implementation ViewController
 8 
 9 - (void)viewDidLoad {
10     [super viewDidLoad];
11 
12     [UIImage imageNamed:@"123"];
13 }

 3.动态添加方法

开发使用场景:如果一个类里面方法非常多,加载类到内存的时候比较耗费资源,需要给每个方法生成映射表。可以使用动态给某个类添加方法解决。

那么,为什么动态添加方法?

OC大多懒加载,有些方法可能很久不会调用,节省内存。例如:电商,视频,社交,收费项目:会员机制,只要会员才拥有这些功能。

ViewController导入Person类的头文件,调用run:方法。Person类并没有run:方法的声明和实现。

 1 @implementation ViewController
 2 
 3 - (void)viewDidLoad {
 4     [super viewDidLoad];
 5     
 6     // _cmd:方法编号
 7     NSLog(@"%@ %@",self,NSStringFromSelector(_cmd));
 8  
 9     Person *p = [[Person alloc] init];
10     // 默认person,没有实现run方法,可以通过performSelector调用,但是会报错。    
11     // 动态添加方法就不会报错
12     [p performSelector:@selector(run:) withObject:@20];
13     
14 }
15 @end
 1 #import <Foundation/Foundation.h>
 2 
 3 @interface Person : NSObject
 4 
 5 @end
 6 
 7 
 8 #import <objc/message.h>
 9 
10 @implementation Person
11 
12 // 定义函数
13 // 没有返回值,有参数
14 // 默认OC方法都有两个隐式参数,self,_cmd
15 void run(id self, SEL _cmd, NSNumber *meter) {
16     NSLog(@"跑了%@米",meter);
17 }
18 
19 // 什么时候调用:当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
20 // 作用:去解决没有实现方法,动态添加方法
21 + (BOOL)resolveInstanceMethod:(SEL)sel {
22     
23     // 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
24     if (sel == @selector(run:)) {
25         // 动态添加run:方法
26         
27         // 第一个参数class:给哪个类添加方法
28         // 第二个参数SEL:添加方法的方法编号
29         // 第三个参数IMP:添加方法的函数实现(函数地址)
30         // 第四个参数type:函数的类型,(返回值+参数类型) v表示void;@表示对象->self;:表示SEL->_cmd 可以传nil
31         class_addMethod(self, sel, (IMP)run, "v@:");
32         
33         return YES;
34     }
35     return [super resolveInstanceMethod:sel];
36 }
37 @end

class_addMethod说明,官方文档给出:

1.函数至少要有两个参数:self和_cmd。

2.关于第四个参数type,可以参照类型编码Type Encodings填写。

3.Type第二和第三个字符必须是@和:,第一个是函数返回值类型。(实测Type传nil也可以)

控制台打印信息:

2016-04-15 09:52:53.634 Runtime(动态添加方法)[38020:1362250] <ViewController: 0x7f9f69d21460> viewDidLoad
2016-04-15 09:52:53.634 Runtime(动态添加方法)[38020:1362250] 跑了20米

4.给分类添加属性

原理:给一个类声明属性,其实本质就是给这个类添加关联。
 
属性的本质:让属性与某个对象产生一段关联
 
使用场景:【给系统的类添加属性】

例:需求:给NSObject添加一个name属性,动态添加属性 -> runtime

新建分类NSObject+Property

 1 #import <Foundation/Foundation.h>
 2 
 3 @interface NSObject (Property)
 4 
 5 // @property在分类中作用:仅仅是生成get,set方法声明,必不会生成get,set方法实现和下划线成员属性
 6 @property NSString *name;
 7 
 8 @end
 9 
10 
11 #import <objc/message.h>
12 
13 @implementation NSObject (Property)
14 
15 - (void)setName:(NSString *)name
16 {
17 
18     // 保存name
19     // 动态添加属性 = 本质:让对象的某个属性与值产生关联
20     /*
21         object:保存到哪个对象中 
22         key:用什么属性保存 属性名
23         value:保存值
24         policy:策略,strong,weak
25      */
26     objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
27    
28 }
29 
30 - (NSString *)name
31 {
32     return objc_getAssociatedObject(self, "name");
33 }
34 @end
分类NSObject+Property
 1 #import "ViewController.h"
 2 #import "Person.h"
 3 #import "NSObject+Property.h"
 4 
 5 @interface ViewController ()
 6 
 7 @end
 8 
 9 @implementation ViewController
10 
11 - (void)viewDidLoad {
12     [super viewDidLoad];
13     
14     NSObject *objc = [[NSObject alloc] init];
15     
16     objc.name = @"123";
17     
18     NSLog(@"%@",objc.name);
19 }
20 @end
控制器ViewController

控制台打印信息:

2016-04-15 10:19:19.100 Runtime(给分类添加属性)[38433:1382316] 123

5.字典转模型

由于字典转模型内容较多,新开一个blog详情请点击:http://www.cnblogs.com/wm-0818/p/5394567.html

posted on 2016-04-14 18:32  战斗宝宝007  阅读(328)  评论(0编辑  收藏  举报

导航