美文网首页
Runtime 1.0

Runtime 1.0

作者: 脚踏实地的小C | 来源:发表于2023-11-14 18:15 被阅读0次

  Runtime 一个耳熟能详的词语,但是你知道它具体是啥?能干些什么嘛?那么就由小编带你去探索下Runtime这扇神秘的大门吧。

本文内容:

什么是Runtime?
Runtime能做什么?(动态添加方法、方法交换、获取类的属性和方法列表)

一、什么是Runtime?

Runtime是Objective-C的一个重要特性,它是一组在运行时执行的C函数,提供了一些在编译时无法确定的特性,如动态类型识别、动态方法调用等。
在iOS和macOS开发中,开发者通常使用Runtime来进行一些高级的操作,如方法交换、动态添加方法等。

二、Runtime能做些什么?

1、动态添加方法

  Runtime允许在运行时动态添加方法,这对于一些动态创建类或者在运行时实现某些特定功能的情况非常有用。

#import "MyClass.h"
#import <objc/runtime.h>

@implementation MyClass

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    if(sel == @selector(dynamicMethod)) {
        class_addMethod([self class], sel, (IMP)dynamicMethodImplementation, "v@:");
        return  YES;
    }
    return [super resolveInstanceMethod:sel];
}

void dynamicMethodImplementation(id self, SEL _cmd) {
    NSLog(@"Dynamic Method Implementation");
}

@end

参数解析:

1、`v`:方法的返回类型是`void`,表示该方法没有返回值。
2、`@`:第一个参数的类型是Objective-C对象。
3、`:`:表示方法有一个参数。
所以,`v@:`表示一个没有返回值的方法,该方法有一个参数,而这个参数的类型是Objective-C对象,在实际使用中,`v@:`的方法通常是用于处理事件,比如按钮点击时触发的方法。

哪些情况下合适?

1、`处理未知的方法调用`:当你无法提前知道某个方法是否存在,但在运行时需要根据某些条件是否添加该方法的实现时;
2、`动态生成方法`:当某个类有大量方法,但在特定情况下只需动态生成或添加其中的一部分方法时;
3、`根据属性名动态生成默认值`:在一些需要频繁添加或修改属性的场景下,使用动态方法解析 生成默认值可以使代码更加具有可维护性,避免了频繁手动管理默认值的繁琐工作;
4、`实现消息转发机制`:动态方法解析是Objective-C消息转发机制的一部分,当Runtime无法找到匹配的方法时,
就会调用`+ (BOOL)resolveInstanceMethod`方法。通过动态方法解析,你可以在这个阶段决定是否添加方法的实现或者将消息转发给其他对象

...

PS:虽然动态方法解析提供了一些灵活性,但在项目中过度使用,可能会导致代码难以理解和维护。
建议在确实需要在运行时动态处理方法的情况下使用,而在其他情况下,最好在编译时进行方法声明。
2、方法交换

  交换两个方法的实现,通常用于在不改变原有代码的情况下修改方法的行为,常用于在某些情况下打印方法调用日志或者统计方法消耗时等。

#import "MyClass.h"
#import <objc/runtime.h>

@implementation MyClass

- (void)originalMethod {
    NSLog(@"Original Method");
}

- (void)swizzledMethod {
    NSLog(@"Swizzled Method");
}

+ (void)load {
    // dispatch_once 确保这个方法只执行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL originalSelector = @selector(originalMethod);
        SEL swizzledSelector = @selector(swizzledMethod);
        // class_getInstanceMethod 获取originalMethod和swizzledMethod的方法实现
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        /**
         * class_addMethod 尝试将`originalMethod`的实现替换为`swizzledMethod`,这样如果`originalMethod`不存在,就添加`swizzledMethod`的实现
         * 如果返回成功,说明`originalMethod`不存在,直接替换成功,否则,使用`method_exchangeImplementations`交换两个方法的实现
         **/
        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        
        if (didAddMethod) {
            //将`originalMethod`的实现替换为`swizzledMethod`
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            // 交换两个方法的实现
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

@end

项目中什么情况下使用?

1、`日志记录和调试`:通过方法交换,你可以在方法调用前后插入日志记录的逻辑,方便调试和监控程序行为;
2、`性能统计`:可以用在方法调用前后记录时间戳,从而进行性能统计和监测,了解方法的耗时情况;
3、`无侵入地修改现有功能`:如果你在不想修改原有代码的情况下修改某个方法的行为,可以使用方法交换。这对于在第三方库中修改某些行为或修复Bug非常有用。
4、`AOP(面向切面编程)`:方法交换是AOP的一种实现方式,通过在方法调用的前后插入代码,可以实现一些横切关注点的逻辑,比如日志、权限检查等。
5、`热修复`:在热修复框架中,方法交换常被用于替换应用中的某些方法,以修复应用程序中的Bug或者紧急情况。

PS:使用方法交换也是会导致代码变得难以理解和维护,所以要慎重考虑。在项目中使用方法交换时,建议进行充分的测试和文档记录,以确保代码的可维护性和稳定性。
3、获取类的属性和方法列表:

  可以获取类的属性和方法列表,这在一些运行时动态处理属性和方法的场景中非常有用。

    unsigned int count;
    //class_copyPropertyList 用于获取指定类的属性列表,返回一个指向属性列表的指针
    objc_property_t *properties = class_copyPropertyList([MyClass class], &count);
    for (int i = 0; i < count ; i++) {
        //property_getName用于获取属性的名称
        const char *propertyName = property_getName(properties[i]);
        NSLog(@"Property:%s",propertyName);
    }
    // free释放内存,因为`class_copyPropertyList`返回的指针需要手动释放
    free(properties);
    
    // class_copyMethodList 用于获取指定类的方法列表,返回一个指向方法列表的指针
    Method *methods = class_copyMethodList([MyClass class], &count);
    for (int i = 0; i < count; i++) {
        // method_getName 用于获取方法的选择器(SEL)
        SEL methodName = method_getName(methods[i]);
        NSLog(@"Method:%@", NSStringFromSelector(methodName));
    }
    // free释放内存,因为`class_copyMethodList`返回的指针需要手动释放
    free(methods);

哪些情况下需要用到?

1、在运行时动态地设置对象的属性
// 获取类的属性列表
unsigned int count;
objc_property_t *properties = class_copyPropertyList([MyClass class], &count);

// 设置对象属性的值
id myObject = [[MyClass alloc] init];
for (int i = 0; i < count; i++) {
    const char *propertyName = property_getName(properties[i]);
    NSString *propertyNameString = [NSString stringWithUTF8String:propertyName];
    [myObject setValue:@"New Value" forKey:propertyNameString];
}
free(properties);

2、将对象转换为一种可传输或可存储的格式
// 获取类的属性列表
unsigned int count;
objc_property_t *properties = class_copyPropertyList([MyClass class], &count);

// 将对象属性转换为字典进行序列化
NSMutableDictionary *serializedObject = [NSMutableDictionary dictionary];
id myObject = [[MyClass alloc] init];
for (int i = 0; i < count; i++) {
    const char *propertyName = property_getName(properties[i]);
    NSString *propertyNameString = [NSString stringWithUTF8String:propertyName];
    id propertyValue = [myObject valueForKey:propertyNameString];
    [serializedObject setObject:propertyValue forKey:propertyNameString];
}
free(properties);
3、自动生成代码,例如根据数据模型生成数据库操作代码
// 获取类的方法列表
unsigned int count;
Method *methods = class_copyMethodList([MyClass class], &count);

// 自动生成代码
for (int i = 0; i < count; i++) {
    SEL methodName = method_getName(methods[i]);
    NSString *methodSignature = NSStringFromSelector(methodName);
    NSLog(@"Generated code for method: %@", methodSignature);
}
free(methods);

未完待续...

相关文章

网友评论

      本文标题:Runtime 1.0

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