为了账号安全,请及时绑定邮箱和手机立即绑定

Objective-C之我所理解的Runtime

标签:
Java

前言

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.方法互换

  • 本质是将方法的实现进行了交换

webp

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


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消