Runtime 见闻整理

Runtime

基本是用C和汇编写的
Runtime 涉及三个点,面向对象,消息分发,消息转发

面向对象

Objective-C 的对象是基于 Runtime 创建的结构体

image

消息分发

    // 创建person对象
    Person *p = [[Person alloc] init];
    
    // 调用对象方法
    [p eat];
    
    // 本质:让对象发送消息
    objc_msgSend(p, @selector(eat));

    // 调用类方法的方式:两种
    // 第一种通过类名调用
    [Person eat];
    // 第二种通过类对象调用
    [[Person class] eat];
    
    // 用类名调用类方法,底层会自动把类名转换成类对象调用
    // 本质:让类对象发送消息
    objc_msgSend([Person class], @selector(eat));

消息转发

完整的消息转发流程图:


image
  • 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<count; i++) {
        // 取出i位置对应的成员变量
        Ivar ivar = ivars[i];
        // 查看成员变量
        const char *name = ivar_getName(ivar);
        // 归档
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        [encoder encodeObject:value forKey:key];
    }
    free(ivars);
}

- (id)initWithCoder:(NSCoder *)decoder
{
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([Movie class], &count);
        for (int i = 0; i<count; i++) {
        // 取出i位置对应的成员变量
        Ivar ivar = ivars[i];
        // 查看成员变量
        const char *name = ivar_getName(ivar);
       // 归档
       NSString *key = [NSString stringWithUTF8String:name];
      id value = [decoder decodeObjectForKey:key];
       // 设置到成员变量身上
        [self setValue:value forKey:key];

        }
        free(ivars);
    } 
    return self;
}
@end

字典、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 <objc/message.h>

@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无涯

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容