Runtime
基本是用C和汇编写的
Runtime 涉及三个点,面向对象,消息分发,消息转发
面向对象
Objective-C 的对象是基于 Runtime 创建的结构体
消息分发
// 创建person对象
Person *p = [[Person alloc] init];
// 调用对象方法
[p eat];
// 本质:让对象发送消息
objc_msgSend(p, @selector(eat));
// 调用类方法的方式:两种
// 第一种通过类名调用
[Person eat];
// 第二种通过类对象调用
[[Person class] eat];
// 用类名调用类方法,底层会自动把类名转换成类对象调用
// 本质:让类对象发送消息
objc_msgSend([Person class], @selector(eat));
消息转发
完整的消息转发流程图:
- 1、动态方法解析
- +(BOOL)resolveInstanceMethod:(SEL)sel;
当接受到未能识别的SEL时,运行时系统会调用该函数用以给对象一次机会来添加相应的方法实现,如果用户在该函数中动态添加了相应方法的实现,则跳转到方法的实现部分,并将该实现存入缓存中,以供下次调用。
- 2、备援接收者
- -(id)forwardingTargetForSelector:(SEL)aSelector;
如果运行时在消息转发的第一步中未找到所调用方法的实现,那么当前接收者还有第二次机会进行未知SEL的处理。这时运行期系统会调用上述方法,并将未知SEL作为参数传入,该方法可以返回一个能处理该选择子的对象,运行时系统会根据返回的对象进行查找,若找到则跳转到相应方法的实现,则消息转发结束。
- 3、完整的消息转发
- -(void)forwardInvocation:(NSInvocation *)anInvocation;
当运行时系统检测到第二步中用户未返回能处理相应选择子的对象时,那么来到这一步就要启动完整的消息转发机制了。该方法可以改变消息调用目标,运行时系统根据所改变的调用目标,向调用目标方法列表中查询对应方法的实现并实现跳转,这种方式和第二步的操作非常相似。当然你也可以修改方法的选择子,亦或者向所调用方法中追加一个参数等来跳转到相关方法的实现。
最后,如果消息转发的第三步还未能处理该未知选择子的话,那么最终会调用NSObject类的如下方法用以异常的抛出,表明该选择子最终未能处理。
- -(void)doesNotRecognizeSelector:(SEL)aSelector;
常用方式
替换系统方法
获得某个类的类方法
Method class_getClassMethod(Class cls , SEL name)
获得某个类的实例对象方法
Method class_getInstanceMethod(Class cls , SEL name)
交换两个方法的实现
void method_exchangeImplementations(Method m1 , Method m2)
拦截系统方法
1.建一个分类(UIImage+Category)
2.分类中实现一个自定义方法,方法中写要在系统方法中加入的语句
+ (UIImage *)xh_imageNamed:(NSString *)name {
double version = [[UIDevice currentDevice].systemVersion doubleValue];
if (version >= 7.0) {
// 如果系统版本是7.0以上,使用另外一套文件名结尾是‘_os7’的扁平化图片
name = [name stringByAppendingString:@"_os7"];
}
return [UIImage xh_imageNamed:name];
}
3.分类中重写UIImage的load方法,实现方法的交换
+ (void)load {
// 获取两个类的类方法
Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
Method m2 = class_getClassMethod([UIImage class], @selector(xh_imageNamed:));
// 开始交换方法实现
method_exchangeImplementations(m1, m2);
}
实现分类增加属性(关联对象)
.h
@property(nonatomic,copy)NSString *name;
.m
static NSString * const nameKey = @"nameKey";
- (void)setName:(NSString *)name {
// 将某个值跟某个对象关联起来,将某个值存储到某个对象中
objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, &nameKey);
}
自动归档解档
自定义对象不能用于writeToFile保存,需要用于归档来进行保存
NSObject+Extension
- (void)encodeWithCoder:(NSCoder *)encoder
{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Movie class], &count);
for (int i = 0; i
字典、Json、Model转换
//字典数据转Model
User *user = [[User alloc] init];
//获取当前model所有属性
unsigned int propertiesCount;
objc_property_t *properties = class_copyPropertyList([user class], &propertiesCount);
for (NSUInteger i = 0; i < propertiesCount; i++){
//获取property Name
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
id value = [json valueForKey:@(propertyName)];
//使用KVC形式进行对象赋值
[user performSelector:@selector(setValue:forKey:) withObject:value withObject:@(propertyName)];
}
上述代码仅仅可以做到NSDictionary转Model,并且Model不能包含自定义对象
完整代码可能需要单独来介绍---->Runtime 字典、Json、Model(含内嵌对象)转换
动态增加方法
- 开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解
- 经典面试题:怎么调用类的私有方法?有没有使用performSelector,其实主要想问你有没有动态添加过方法。
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
//还可以调用私有方法
[p performSelector:@selector(run:) withObject:@10];
}
@end
#import "Person.h"
#import
@implementation Person
// 没有返回值,也没有参数 --> void,(id,SEL)
void aaa(id self, SEL _cmd, NSNumber *meter) {
NSLog(@"跑了%@", meter);
}
// 任何方法默认都有两个隐式参数,self,_cmd(_cmd代表方法编号,打印结果为当前执行的方法名)
// 什么时候调用:只要一个对象调用了一个未实现的方法就会调用这个方法,进行处理
// 作用:动态添加方法,处理未实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == NSSelectorFromString(@"run:")) {
//aaa不会生成方法列表
class_addMethod(self, sel, (IMP)aaa, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
动态变量控制
在程序中,Person的私有属性age是10,使用runtime变成了20。
Person *p = [[Person alloc] init];
Ivar *ivar = class_copyIvarList([p class], &count);
const char *varName = ivar_getName(var);
object_setIvar(p, var, @"20");
实现万能调整(Router)
- (void)push:(NSString *)classFromString{
Class actionClass = NSClassFromString(classFromString);
id target = [[actionClass alloc] init];
if(!target){
NSLog(@"没有当前类");
}
if (![(NSObject *) target isKindOfClass:[UIViewController class]]) {
NSLog(@"当前不是UIViewController");
}
UINavigationController *navigationCtr = [self getActiveNavigationController];
if ([navigationCtr isKindOfClass:[UINavigationController class]]) {
[navigationCtr pushViewController:(UIViewController *)target animated:YES];
}
}
- (UINavigationController *)getActiveNavigationController
{
if ([[UIApplication sharedApplication] delegate].window.isKeyWindow)
{
UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
if ([rootViewController isKindOfClass:[UITabBarController class]])
{
UINavigationController *navigationController = ((UITabBarController *) rootViewController).selectedViewController;
return navigationController;
} else if ([rootViewController isKindOfClass:[UINavigationController class]])
return (UINavigationController *) rootViewController;
else
return nil;
} else
return nil;
}
by 有涯sui无涯