iOS Runtime的理解与使用

简介

Objective-C是一门古老的语言,但是是一门动态性的语言,因为它的动态性,使其又有了强大的生命力,在苹果生态系统的平台应用广泛,可谓互相成全了对方,Objective-C的动态性随处可见,当子类覆写父类方法的时候,总是在执行前才决定该执行什么,不像C在编译时就已经决定了代码的执行,能让Objective-C有强大动态性的就是Runtime类库。

原理

Runtime的核心就是消息转发,当对象调用方法是,程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应该消息而做出不同的反应。
简单的说主要有这几点:
1.消息传递(Messaging)
2.动态方法解析和转发
原理这一块儿我并没有写的很详细,大家可以看这几篇原理博客的详细介绍。

使用

1.在使用delegate的时候我们在判断代理是否遵守协议的时候,会发送一条消息,当消息没有被响应的时候,会抛出异常。

  //这里就是发送了一条消息,看代理是否能够响应到,响应到的时候执行方法
    if ([self.delegate respondsToSelector:@selector(dosomething:)]) {
        [self.delegate dosomething:@""];
    }

2.给对象添加category的时候是没有办法给之间添加property方法的,需要使用runtime进行动态绑定,这个可以参考AFNetwortking里面的写法

@interface UIView (category)
//声明
@property (nonatomic,copy) NSString *text;

@end
#import "UIView+category.h"
#import 
//设置key
static char textKey;

@implementation UIView (category)

//设置关联
-(void)setText:(NSString *)text{
    objc_setAssociatedObject(self, &textKey, text, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

//获取关联的值
-(NSString *)text{
    return objc_getAssociatedObject(self, &textKey);
}

@end

3.Method Swizzling
我觉得这才是Runtime运用场景最为广泛的地方,java有反射机制,可以设置动态代理,实现AOP编程,但是Objective-C的Runtime则更为强大。


iOS Runtime的理解与使用_第1张图片
原理图

下面来看一段代码

void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector) { 
//原始方法指针
 originalMethod = class_getInstanceMethod(class, originalSelector); 
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
 // 给类添加方法指针
didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));
 if (didAddMethod)
{
//将原始的方法实现赋给swizzledSelector
 class_replaceMethod(class, swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));
 } else { 
//交换方法实现
method_exchangeImplementations(originalMethod, swizzledMethod); 
}
}

上面是一段比较常见的方法交叉代码,但是我们在开发中用的是Aspects这个第三方的开源库,用这个开源库你可以实现自己的一些埋点统计或者判断UI刷新。
埋点统计

+ (void)load
{
   
    [UIViewController aspect_hookSelector:@selector(viewWillAppear:)
                              withOptions:AspectPositionAfter
                               usingBlock:^(id aspectInfo) {
                                   NSString *className = NSStringFromClass([[aspectInfo instance] class]);
                                   DebugLog(@"%@ class tpo appear",className);
                                   } error:NULL];
    
    [UIViewController aspect_hookSelector:@selector(viewWillDisappear:)
                              withOptions:AspectPositionAfter
                               usingBlock:^(id aspectInfo) {
                                   NSString *className = NSStringFromClass([[aspectInfo instance] class]);
                                   DebugLog(@"%@ class tpo disappear",className);
                               } error:NULL];
    
}

判断子线程UI刷新

+(void)load{
#if DEBUG
    [UIView aspect_hookSelector:@selector(setNeedsLayout)
                    withOptions:AspectPositionAfter
                     usingBlock:^(id aspectInfo) {
                         NSString *className = NSStringFromClass([[aspectInfo instance] class]);
                         className = [NSString stringWithFormat:@"%@ can't use in background",className];
                         NSAssert([NSThread isMainThread],className);
                     } error:NULL];
    
    [UIView aspect_hookSelector:@selector(setNeedsDisplay)
                    withOptions:AspectPositionAfter
                     usingBlock:^(id aspectInfo) {
                         NSString *className = NSStringFromClass([[aspectInfo instance] class]);
                         className = [NSString stringWithFormat:@"%@ can't use in background",className];
                         NSAssert([NSThread isMainThread],className);
                     } error:NULL];
    [UIView aspect_hookSelector:@selector(setNeedsDisplayInRect:)
                    withOptions:AspectPositionAfter
                     usingBlock:^(id aspectInfo) {
                         NSString *className = NSStringFromClass([[aspectInfo instance] class]);
                         className = [NSString stringWithFormat:@"%@ can't use in background",className];
                         NSAssert([NSThread isMainThread],className);
                     } error:NULL];
#else
    //不在release的时候添加是因为和jspatch有冲突
#endif
}

hotfix
其实热修复有很多方案,原理都是Method Swizzling,比较常见的选择方案是JSPatch,但是我觉得JSPatch和Aspects冲突引发崩溃的问题应该有JSPatch的作者bang来解决,不应该由开发者自己解决,虽然bang团队给出过解决方案。

爱生活,爱运动,热爱交流,欢迎讨论!

你可能感兴趣的:(iOS Runtime的理解与使用)