Runtime 总结及应用

什么是Runtime?

runtime简称运行时。OC是运行时机制,也就是在运行时才做一些处理。其中最主要的是消息机制,属于动态调用过程。在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。

查看源码

1 首先cd到查看文件文件夹

2 clang -rewrite-objc main.m ,生成mian.cpp文件

Runtime 在实际开发中,会经常用到吗?这个答案是肯定的。但是Runtime用的好不好在于理解程度,理解的好代码质量高效实用

Runtime具体有那些应用呢?

1 消息传递(调用方法)

一个对象的方法像这样 [obj foo],编译器转成消息发送objc_msgSend(obj, foo) 具体流程如下:

1 首先通过obj的isa指针找到他的class

2 注册方法编号 sel,可以快速查找

3 根据编号在class的menthod list 找 foo

4 一旦找到去执行实现

Objective-C运行时会调用 +resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程(动态添加方法)。如果未实现方法,运行时就会移到下一步:forwardingTargetForSelector。如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法。如果还不能处理未知消息,就会进入完整消息转发阶段。

Runtime系统会向对象发送methodSignatureForSelector:消息,并取到返回的方法签名用于生成NSInvocation对象。为接下来的完整的消息转发生成一个 NSMethodSignature对象。NSMethodSignature 对象会被包装成 NSInvocation 对象,forwardInvocation: 方法里就可以对 NSInvocation 进行处理了。如果未实现,Runtime则会发出 -doesNotRecognizeSelector: 消息,程序这时也就挂掉了

2 动态添加方法

使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。

方法介绍

// 参数1:给哪个类添加方法
// 参数2:添加方法的方法编号SEL
// 参数3:添加方法的函数实现IMP(函数地址)
// 参数4:函数的类型,(返回值+参数类型)
class_addMethod(Class cls, SEL name, IMP imp, const char * types)

实例:

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
    p = objc_msgSend(p, sel_registerName("init"));

    [p performSelector:@selector(eat)];
}

@end
@implementation Person

/**
 void的前面没有+、-号,因为只是C的代码;
 必须有两个指定参数(id self,SEL _cmd)
 */
void eat(id self, SEL sel)
{
    NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}

//当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来

+(Bool)resolveInstanceMethod:(SEL)sel
{
    if(sel == @selector(eat)){
        class_addMethod(self,sel,(imp)eat,"v@");
    }
    
    return [super resolveInstanceMethod];
}
3 交换方法(Method Swizzling)

使用场景:当第三方框架或者系统的原生方法功能不能满足我们的时候,我们可以在保持系统原有方法功能的基础上,添加额外的功能

方法:

// 交换方法地址,交换两个方法的实现
method_exchangeImplementations(Method m1, Method m2)

实例:

封装一下交换方法的类方法,方便以后调用

#define  util.h

static inline void wpy_swizzled_method(Class cls ,SEL originalSelector, SEL swizzledSelector)
{
    Class class = [self class];
    //原有方法
    Method originalMethod = class_getInstanceMethod(cls, originalSelector);
    //替换原有方法的新方法
    Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
    //先尝试給源SEL添加IMP,这里是为了避免源SEL没有实现IMP的情况
    BOOL didAddMethod =
    class_addMethod(cls,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {//添加成功:说明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP
        class_replaceMethod(class,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {//添加失败:说明源SEL已经有IMP,直接将两个SEL的IMP交换即可
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

#endif

例如我们要替换viewDidAppear() 可以这样写

@implementation UIViewController (Swizzling)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
       Class class = [self class];

        wpy_swizzled_method(class, @selector(viewDidAppear:), @selector(wpy_viewDidAppear:));
    });
}


- (void)wpy_viewDidAppear:(BOOL)animated{
    [self wpy_viewDidAppear:animated];
    
    NSLog(@"被调用了");
}

1.swizzling建议在+load中完成。+load 和 +initialize 是Objective-C runtime会自动调用两个类方法。+load 是在一个类被初始加载时调用,一定会被调用;+initialize 是在应用第一次调用该类的类方法或实例方法前调用,相当于懒加载方式,可能不被调用。此外 +load 方法还有一个非常重要的特性,那就是子类、父类和分类中的 +load 方法的实现是被区别对待的。换句话说在 Objective-C runtime 自动调用 +load 方法时,分类中的 +load 方法并不会对主类中的 +load 方法造成覆盖。

2.swizzling应该只在dispatch_once 中完成,由于swizzling 改变了全局的状态,所以我们需要确保在任何情况下(多线程环境,或者被其他人手动再次调用+load方法)只交换一次,防止再次调用又将方法交换回来。+load方法本身即为线程安全,为什么仍需添加dispatch_once,其原因就在于+load方法本身无法保证其中代码只被执行一次。

4 动态添加属性

场景:分类是不能自定义属性和变量的,这时候可以使用runtime动态添加属性方法

原理: 给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。

方法:

/** 关联对象、set方法
id object:给哪个对象添加关联,给哪个对象设置属性
const void *key:关联的key,要求唯一,建议用char 可以节省字节
id value:关联的value,给属性设置的值
objc_AssociationPolicy policy:内存管理的策略
*/
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
// 获取关联的对象、get方法
id objc_getAssociatedObject(id object, const void *key)
// 移除关联的对象
void objc_removeAssociatedObjects(id object)

实例:

@interface UIViewController (WPYCategory)

/** 设置导航栏的透明度 */
@property (nonatomic, assign) CGFloat wpy_navBarAlpha;

@end


static const void* WPYNavBarAlphaKey     = @"WPYNavBarAlphaKey";

@implementation UIViewController (WPYCategory)

- (void)setWPY_navBarAlpha:(CGFloat)gk_navBarAlpha {
    objc_setAssociatedObject(self, GKNavBarAlphaKey, @(wpy_navBarAlpha), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    [self setNavBarAlpha:wpy_navBarAlpha];
}


- (CGFloat)wpy_navBarAlpha {
    return [objc_getAssociatedObject(self, WPYNavBarAlphaKey) floatValue];
}


@end

5 NSCoding自动归档解档

场景:如果一个模型有许多个属性,实现自定义模型数据持久化时,需要对每一个属性都实现一遍encodeObject 和 decodeObjectForKey方法,比较麻烦。我们就可以使用Runtime来解决。
原理:用runtime提供的函数遍历Model自身所有属性,并对属性进行encode和decode操作。

实例:

@implementation MJMusicModel

// 设置不需要归解档的属性
- (NSArray *)ignoredNames {
    return @[@"_musicUrl"];
}

// 归档调用方法
- (void)encodeWithCoder:(NSCoder *)encoder
{
    unsigned int count = 0;
    // 获得这个类的所有成员变量
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i
6 字典转模型

与上类似就是利用runtime遍历model的属性转换成key去字典中取到相应的值model的过程不再详细阐述

你可能感兴趣的:(Runtime 总结及应用)