iOS Runtime学习

2019-03-22  本文已影响0人  iOS_tree

iOS的Object-C是一门动态语言,在调用对象的方法时,是通过向对象发送消息的形式进行实现,在编译阶段并不会判断发送对象的方法是否存在,而是在代码运行的时候进行的判断。在正常情况下,调用一个对象的没有实现的方法,会导致程序发生崩溃消息,提示找不到该对象的实现方法。Object-C语言的动态特性基于Runtime系统实现,我们可以通过Runtime的一些公共的函数进行一些特殊操作。如给系统的类动态添加属性,调换类的实现方法等。我在这里简单介绍一些我们可以常用的Runtime方法。

1、动态添加属性

当我们需要给系统的类或者第三方库的类添加属性时,我们可以使用Runtime进行实现。主要通过两个方法进行关联属性的设置和获取,方法如下:

//设置对象的关联属性
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)

//获取对象的关联属性
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)

实例代码如下:

- (void)setWeight:(float)weight {
    objc_setAssociatedObject(self, "weight", @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (float)getWeight {
    return [objc_getAssociatedObject(self, "weight") floatValue];
}

2、动态方法解析

当我们的需求存在需要调用方法1,但是方法1又没有实现时,我们可以使用动态添加方法的方式进行实现。当消息发送的对象的方法没有实现时,系统会调用对象的+ (BOOL)resolveClassMethod:(SEL)sel方法或者+ (BOOL)resolveInstanceMethod:(SEL)sel方法进行处理,我们实现这个两个方法及可拦截方法的调用的第一步,在获取到我们需要动态添加的方法时,此时进行动态添加即可,当我们返回YES时,代表解决了方法不存在的文图,代码如下:

void functionMethod1(id obj, SEL _cmd) {
    NSLog(@"Doing functionMethod1   %@",obj);
}

void classFunctionMethod1(id obj, SEL _cmd) {
    NSLog(@"Doing classFunctionMethod1   %@",obj);
}

/*********************1、动态方法解析******************************/
//处理类方法
+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(class_function1)) {
        class_addMethod(object_getClass(self), sel, (IMP)classFunctionMethod1, "v@:");
        return YES;
    }
    return [super resolveClassMethod:sel];
}

//处理实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(function1)) {
        class_addMethod([self class], sel, (IMP)functionMethod1, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

代码通过判断function1和class_function1方法来进行拦截,进把方法实现指向functionMethod1函数和classFunctionMethod1函数。此时会调用functionMethod1函数和classFunctionMethod1函数来替代function1和class_function1方法的实现。阻止了程序的崩溃。

3、重定向消息接收者

当我们调用某个对象的某个方法时,该方法又没有实现时,可以对该方法进行重新指定调用对象,也就是发送消息的对象主角。当方法消息发送给另外一个对象后,只要另外一个对象实现该方法,则程序会继续运行下去,以此避免因为不存在的方法而导致程序崩溃。代码如下:

/*********************2、重定向消息接收者******************************/
//处理类方法
+ (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(class_function2)) {
        return [ESCPersonModel class];
    }
    return [super forwardingTargetForSelector:aSelector];
}

//处理实例方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(function2)) {
        return [ESCPersonModel new];
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

4、重定向方法接收着和方法名

当我们通过第3步的重定向消息接受者没有对消息进行处理时,会进入最后一步,再次确认消息及消息接收者,此时方法消息及消息接收者被封装成NSInvocation对象,我们更改NSInvocation对象下的消息签名及消息接收对象即可避免程序崩溃的产生。代码如下:

/*********************3、重定向消息接收者和方法******************************/
//处理类方法
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"class_function3"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];//签名,进入forwardInvocation
    }
    
    return [super methodSignatureForSelector:aSelector];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;
    if (sel_isEqual(sel, NSSelectorFromString(@"class_function3"))) {
        anInvocation.selector = NSSelectorFromString(@"class_function2");
        Class p = [ESCPersonModel class];
        if([p respondsToSelector:anInvocation.selector]) {
            [anInvocation invokeWithTarget:p];
        }else {
            [self doesNotRecognizeSelector:sel];
        }
    }
}

//处理实例方法 
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"function3"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];//签名,进入forwardInvocation
    }
    
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;
    if (sel_isEqual(sel, NSSelectorFromString(@"function3"))) {
        anInvocation.selector = NSSelectorFromString(@"function2");
        ESCPersonModel *p = [ESCPersonModel new];
        if([p respondsToSelector:anInvocation.selector]) {
            [anInvocation invokeWithTarget:p];
        }else {
            [self doesNotRecognizeSelector:sel];
        }
    }
}

demo地址:https://github.com/XMSECODE/ESCRuntimeDemo
runtime详解参考:https://www.jianshu.com/p/d4b55dae9a0d

上一篇下一篇

猜你喜欢

热点阅读