美文网首页Swift&Objective-C
Objective-C之我所理解的Runtime

Objective-C之我所理解的Runtime

作者: Bestmer | 来源:发表于2019-01-24 10:57 被阅读6次

前言

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;

@end

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

  • 本质是将方法的实现进行了交换
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动态的添加一个方法实现,添加完毕后,就会执行我们添加的方法实现;
  • 这样,当一个类调用不存在的方法时,就不会崩溃了。

7.APP防crash处理


最后

温故而知新。

相关文章

网友评论

    本文标题:Objective-C之我所理解的Runtime

    本文链接:https://www.haomeiwen.com/subject/mdxyjqtx.html