Runtime简介
- Runtime简称运行时. OC就是运行时机制, 也就是在运行的时候的一些机制, 其中最主要的是消息机制
- 对于C语言, 函数的调用在编译的时候会决定调用哪个函数
- 对于OC的函数, 属于动态调用过程, 在编译的时候并不能决定真正调用哪个函数, 只有在真正运行的时候才会根据函数的名字来调用
- 事实证明
- 在编译阶段, OC可以调用任何函数, 即使这个函数并未实现, 只要声明过就不会报错
- 在编译阶段, C语言调用未实现的函数就会报错
Runtime各种方法使用
Runtime消息机制
- 介绍
- 方法调用本质: 就是发送一个消息, 用Runtime发送消息, OC底层是通过Runtime实现
导入头文件 `#import <objc/message.h>`
id objc = [NSObject alloc];
objc = [objc init];
/**
* 最终生成消息机制, 编辑器做的事情
* 最终代码, 需要把当前代码重新编译, 用xcode编译器, clang
* clang -rewrite-objc main.m 查看最终生成代码
* Runtime都有个前缀, 谁的事情就使用谁
*/
/**
* id: 谁发送消息
* SEL: 发送什么消息
*/
/**
* xcode6之前, 苹果可以使用objc_msgSend, 而且有参数提示
* xcode6之后不推荐使用Runtime
* 解决方法: 找到build setting -> 搜索msg, 改成NO, 不用严格检查
这样Runtime就正常了
*/
/**
* id objc = [NSObject alloc];
* objc = [objc init];.
* 用Runtime写这两句
*太麻烦 id objc = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)([NSObject class], @selector(alloc));
*/
id objc = objc_msgSend([NSObject class], @selector(alloc));
objc = objc_msgSend(objc, @selector(init));
******************
//TODO: 什么时候调用Runtime, 方法调用流程
/** 最底层写法 */
Person *per = [Person alloc];
objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
per = [per init];
objc_msgSend(per, sel_registerName("init"));
/** 调用对象方法 */
objc_msgSend(per, @selector(eat));
什么时候使用runtime
不得不用runtime消息机制, 可以调用私有方法, 因为正常的没有声明的方法, oc不能使用
//TODO: 什么时候调用Runtime, 方法调用流程
/** 最底层写法 */
Person *per = [Person alloc];
objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
per = [per init];
objc_msgSend(per, sel_registerName("init"));
/** 调用对象方法 */
objc_msgSend(per, @selector(eat));
objc_msgSend(per, @selector(run:), 20);
开发中使用场景
Runtime的方法都是有前缀的, 谁的事情谁开头
方法调用流程
怎么去调用eat方法, 对象方法: 类对象的方法列表中 类方法: 元类中方法列表
-
1.通过isa去对应的类中查找方法
2.注册方法编号
3.根据方法编号去查找对应方法
4.找到的只是最终函数实现地址, 根据地址去方法区调用对应函数 -
内存的5大区
- 栈区 -> 不需要手动管理内存, 自动管理
- 堆 -> 需要手动管理内存, 根据地址去方法去调用
- 静态区
- 常量区
- 方法区
二 交换方法
案例
/** 把类记载进内存的时候只调用一次 */
+ (void)load
{
/** 获取方法
* imageNamed
* wdy_imageNamed
*/
/** 获取那个类方法, 获取那个方法 */
Method imageNamedMethod = class_getInstanceMethod(self, @selector(imageNamed:));
Method wdy_imagenameMethod = class_getInstanceMethod(self, @selector(wdy_imageNamed:));
/** Runtime交换方法 */
method_exchangeImplementations(imageNamedMethod, wdy_imagenameMethod);
}
/** 调用多次 */
+ (void)initialize
{
}
/** 加载图片 */
/** 判断是否加载成功 */
+ (UIImage *)wdy_imageNamed:(NSString *)name
{
UIImage *image = [self wdy_imageNamed:name];
if (image) {
NSLog(@"加载成功");
} else {
NSLog(@"加载失败");
}
return image;
}
/**
* Runtime(交换方法):
* 需求:让UIImage加载图片, 告诉我是否成功
解决方法:
1.自定义UIimage
弊端:
*.每次使用, 都需要导入
*.项目大了, 没办法实现
2.用UIimage方法
给系统imageNamed添加功能, 只能用Runtime
解决方法:
1.给系统添加方法分类
2.自己实现一个带有扩展功能的方法
3.交换方法, 只需要交换一次
*
*/
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
/** 麻烦的方法 */
UIImage *image = [UIImage imageNamed:@"1.png"];
}
三 动态添加方法
- 描述: 添加一个类, 想要在适当的时候添加一个方法, 例如当用户成为会员的时候, 进行一些操作
- 问题: Runtime动态添加方法:OC中都是懒加载机制, 只要有个方法实现了, 就立马添加到方法列表中
美团有个面试题 有没有使用过 self performSelector:<#(SEL)#>, 什么时候使用?动态添加方法的时候使用过.
怎么动态添加方法?用Runtime添加. 为什么要动态添加?
*/
-
准备: 在ViewDidLoad中创建一个类, 并且添加一个方法
- (void)viewDidLoad { [super viewDidLoad]; Person *per = [[Person alloc] init]; [per performSelector:@selector(eat)]; [per performSelector:@selector(run:) withObject:@10]; }
在类中添加一个方法
- 这个方法在一个地方调用了未实现的方法就会调用:
+ (BOOL)resolveInstanceMethod:(SEL)sel
/** 没有返回值没有参数 */
void aaa(id self, SEL _cmd) {
NSLog(@"吃东西啊啊 啊啊啊啊!");
}
void run(id self, SEL _cmd, NSNumber *num) {
NSLog(@"跑了%@", num);
}
/**
* 解决添加的方法
* 什么时候调用方法: 只要一个对象调用了一个未实现的方法就会调用这个方法, 进行处理
* 作用: 动态添加方法, 处理未实现
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
/** 任何方法都有两个隐士:sel, cmd->方法编号 */
if (sel == NSSelectorFromString(@"eat")) {
/**
* 给谁添加方法
* SEL: 添加那个方法
* IMP: 方法实现
* type: 方法类型
*/
class_addMethod(self, sel, (IMP)aaa, "v@:");
}
if (sel == NSSelectorFromString(@"run:")) {
class_addMethod(self, sel, (IMP)run, "v@:@");
}
return [super resolveInstanceMethod:sel];
}