前言
Runtime,俗称运行时,是iOS非常核心的东西。我们都知道OC是一门动态的语言,它的动态其实就体现在运行时而不是编译时,通俗的说,在程序没有完全运行起来时,一切都有可能发生。正是因为这种机制,为我们提供了很多黑魔法,我们可以利用它做很多事情。由于runtime是基于C层面的一套API,所以学习它我们能够清楚很多OC层面代码的本质。本文不谈理论,不谈概念,只谈runtime在工作中的常用情景,毕竟理论的东西只有付诸于实践才能发挥价值。
1.为系统的类添加属性
本质:就是让某个属性与对象产生关联.
比如为NSObject添加一个name属性
#import <UIKit/UIKit.h>NS_ASSUME_NONNULL_BEGIN@interface NSObject (Custom)@property NSString *name;@endNS_ASSUME_NONNULL_END
#import "NSObject+Custom.h"#import <objc/message.h>@implementation MSObject (Custom)- (void)setName:(NSString *)name { // 第一个参数:给哪个对象添加关联 // 第二个参数:关联的key,通过这个key获取 // 第三个参数:关联的value // 第四个参数: 关联的策略 objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY); } - (NSString *)name { // 根据关联的key,获取关联的值。 return objc_getAssociatedObject(self, @"name"); }@end
2.动态添加方法
因为OC是懒加载机制,只要实现了一个方法,就会被添加到方法列表中,占用内存。
有一些APP,比如免费版和付费版,如果用户一直使用免费版,就没有必要把付费版才有的方法添加到方法列表;
当用到这些方法的时候通过runtime为其动态添加
从而减轻内存的压力.
// 方法调用Person *person = [Person new];// 无参数[person performSelector:@selector(eat)];// 1个参数[person performSelector:@selector(drink:) withObject:@"cola"];// 2个参数[person performSelector:@selector(sleep:) withObject:@"Marry" withObject:@10];
#import "Person.h"#import <objc/message.h>void eat(id self, SEL _cmd) { NSLog(@"eat what tonight"); }void drink(id self, SEL _cmd, NSString *name) { NSLog(@"I like dring %@", name); }void sleeps(id self, SEL _cmd, NSString *name, NSNumber *hours) { NSLog(@"Sleep with %@ for %@ hours", name, hours); }@implementation Person+ (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == NSSelectorFromString(@"eat")) { class_addMethod(self, sel, (IMP)eat, "v@:"); return YES; } else if (sel == NSSelectorFromString(@"drink:")) { class_addMethod(self, sel, (IMP)drink, "v@:@"); } else if (sel == NSSelectorFromString(@"sleep:")) { class_addMethod(self, sel, (IMP)sleeps, "v@:@"); } return [super resolveInstanceMethod:sel]; }@end
3.方法互换
本质是将方法的实现进行了交换
image
#import "UIImage+Custom.h"#import <objc/message.h>@implementation UIImage (Custom)// 把类加载进内存时调用,只调用一次+ (void)load { Method imageNamedMethod = class_getClassMethod(self, sel_registerName("imageNamed:")); Method my_imageNamed = class_getClassMethod(self, @selector(my_imageNamed:)); method_exchangeImplementations(imageNamedMethod, my_imageNamed); } + (UIImage *)my_imageNamed:(NSString *)name { UIImage *image = [UIImage my_imageNamed:name]; if (image) { NSLog(@"图片赋值成功,图片名称为:%@", name); } else { NSLog(@"图片赋值失败,找不到图片名称:%@", name); } return image; }@end
4.获取成员变量内部信息
通过runtime可以查看一些没有开源的三方框架内部有哪些成员变量
unsigned int count = 0; Ivar *varList = class_copyIvarList(self.class, &count); for (int i = 0; i < count; i++) { // 获取成员变量的名称 NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(varList[i])]; // 获取成员变量的类型 NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(varList[i])]; }
5.获取私有方法列表
在代码调试的时候能够用的上.
unsigned int count = 0; Method *methodList = class_copyMethodList(UIViewController.class, &count);for (int i = 0; i < count; i++) { // 获取成员变量的名称 SEL sel = method_getName(methodList[I]); NSString *selName = NSStringFromSelector(sel); NSLog(@"%@", selName); }
6.消息处理
都知道OC是消息机制,调用方法底层的实现都是发送消息;
[receiver message];
objc_msgSend(receiver, selector)
当在相应的类以及父类中找不到类方法实现时会执行+resolveInstanceMethod:这个类方法;
该方法如果在类中不被重写的话,默认返回NO。如果返回NO就表明不做任何处理,走下一步。如果返回YES的话,就说明在该方法中对这个找不到实现的方法进行了处理;
在该方法中,我们可以为找不到实现的SEL动态的添加一个方法实现,添加完毕后,就会执行我们添加的方法实现;
这样,当一个类调用不存在的方法时,就不会崩溃了。
作者:RocKwok
链接:https://www.jianshu.com/p/83071d88bcaa