运行时机制
,也就是在运行时候的一些机制,其中最主要的是消息机制。函数的调用在编译的时候会决定调用哪个函数
。动态调用过程
,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。调用任何函数
,即使这个函数并未实现,只要声明过就不会报错。未实现的函数
就会报错。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
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
控制台打印信息:
2016-04-15 10:19:19.100 Runtime(给分类添加属性)[38433:1382316] 123
5.字典转模型
由于字典转模型内容较多,新开一个blog详情请点击:http://www.cnblogs.com/wm-0818/p/5394567.html