Runtime详解
The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.
Objective-C语言将尽可能多的决策从编译时和链接时间延迟到运行时。只要有可能,它都是动态的。这意味着该语言不仅需要编译器,还需要运行时系统来执行编译后的代码。运行时系统作为Objective-C语言的一种操作系统;它使语言工作。
来自百度翻译!!?
先来看一下什么是OC的消息机制?
OC中的方法调用其实都是转成了objc_msgSend函数的调用,给方法调用者发送了一条消息(selector方法名)。
例如:
KSPerson * kk = [[KSPerson alloc] init];
[kk print] 转成C函数就等价于 objc_msgSend(kk,@selector(print));
objc_msgSend底层的3大流程:消息发送、动态方法解析、消息转发
接下来看什么是Runtime?
说runtime之前、先聊一下C语言。
C语言是一门面向过程、抽象化的通用程序设计语言、广泛用于底层开发。C语言的函数调用、是在编译时期就决定好需要调用哪个函数。
而OC是一门动态性比较强的编程语言、允许很多操作推迟到程序运行时在进行。OC的动态性就是Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数。包括我们平时编写的一些OC代码、底层都是转换成了RUntime API进行调用。
Runtime的具体应用?
一、利用关联对象(AssociatedObject)给父类添加属性
static char objKey;
NSObject * obj = [[NSObject alloc] init];
//设置关联对象
objc_setAssociatedObject(obj, &objKey, @"asdf", OBJC_ASSOCIATION_RETAIN_NONATOMIC); //获取关联对象
NSString *string = objc_getAssociatedObject(obj, &objKey);
NSLog(@"AssociatedObject = %@", string);
二、遍历类的所有成员变量、修改UITextField的placeholder文字颜色?
// UITextField
unsigned int numIvars;//成员变量个数
Ivar *vars = class_copyIvarList(NSClassFromString(@"UITextField"), &numIvars);
NSString*key = nil;
for(int i =0; i < numIvars; i++) {
Ivar thisIvar = vars[i];
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];//成员变量的名字
NSLog(@"name :%@", key);
key = [NSString stringWithUTF8String:ivar_getTypeEncoding(thisIvar)];//成员变量数据类型
NSLog(@"type :%@", key);
}
free(vars);
通过获取到了UITextField的成员变量、可以获取到显示placeholder的是一个叫_placeholderLabel的变量、所以我们可以对他进行的相关属性进行修改。
UITextField * KSField = [[UITextField alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
KSField.placeholder = @"测试";
[self.view addSubview:KSField];
Ivar ivar = class_getInstanceVariable([KSField class], "_placeholderLabel");
id placeholderLabel = object_getIvar(KSField, ivar);
[placeholderLabel performSelector:@selector(setTextColor:) withObject:UIColor.redColor];
三、交换方法实现
#import <Foundation/Foundation.h>
@interface NSURL (KSUrl)
+ (instancetype)KS_URLWithString:(NSString *)URLString;
@end
#import "NSURL+KSUrl.h"
#import <objc/runtime.h>
@implementation NSURL (KSUrl)
+ (void)load{
Method m1 = class_getClassMethod([NSURL class], @selector(URLWithString:));
Method m2 = class_getClassMethod([NSURL class], @selector(KS_URLWithString:));
//方法交换
method_exchangeImplementations(m1, m2);
}
+ (instancetype)KS_URLWithString:(NSString *)URLString{
NSURL * url = [NSURL KS_URLWithString:URLString];
if (!url) {
NSLog(@"传入的url为空");
}else{
NSLog(@"传入的url正常");
}
return url;
}
@end
调用方法
- (void)viewDidLoad {
[super viewDidLoad];
NSURL * url = [NSURL URLWithString:@"111"];
}
打印:
2020-07-16 16:57:28.586612+0800 基础练习[2650:139632] 传入的url正常
四、方法找不到的异常处理
对于这种情况、我们有3次机会来处理。
objc_msgSend底层的3大流程:
消息发送、动态方法解析、消息转发
1.消息发送----编写代码的正确性、保证相关方法存在实现。
.h
@interface KSPerson : NSObject
- (void)print;
@end
.m
- (void)print{
NSLog(@"%s",__func__);
}
KSPerson * kk = [[KSPerson alloc] init];
[kk print];
这一步就属于调用了objc_msgSend(kk,@selector(print));
也就是所谓的消息发送机制。
2.动态方法解析----
- (void)print{
NSLog(@"%s",__func__);
}
当没有实现上边对应的方法的时候、则会进入动态方法解析阶段
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(print)) {
Method method = class_getInstanceMethod(self, @selector(test));
class_addMethod(self,
sel,
method_getImplementation(method),
method_getTypeEncoding(method));
return NO;
}
return [super resolveInstanceMethod:sel];
}
-(void)test{
NSLog(@"%s",__func__);
}

打印:
2020-07-16 17:12:13.445550+0800 基础练习[2812:147911] -[KSPerson test]
3.消息转发----
如果上边还没有处理该异常的话、就会进入到消息转发的阶段。
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(print)) {
return [KSStudent new]; //让KSStudent对象来处理print方法
}
return [super forwardingTargetForSelector:aSelector];
// return nil;//继续下一步
}
打印:
2020-07-16 17:16:17.794979+0800 基础练习[2882:151262] -[KSStudent print]
如果上边没有进行处理、而是返回nil的话,则进入
- (id)forwardingTargetForSelector:(SEL)aSelector{
// if (aSelector == @selector(print)) {
// return [KSStudent new];
// }
// return [super forwardingTargetForSelector:aSelector];
return nil;//继续下一步
}
@encode指令、可以将具体的类型表示成字符串编码
v: void
@: id
:: SEL
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if ([NSStringFromSelector(aSelector) isEqualToString:@"print"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"到我这就结束了");
}
打印:
2020-07-16 17:18:03.157596+0800 基础练习[2909:152716] 到我这就结束了