美文网首页
iOS Runtime详解(消息机制,类元对象,缓存机制,消息转

iOS Runtime详解(消息机制,类元对象,缓存机制,消息转

作者: 为自己丶拼个未来 | 来源:发表于2018-06-06 17:56 被阅读0次

    前言

    上一篇文章中,我详细的讲解了一些基本关键词以及基本概念。

    • SEL方法的名字,可以理解为字符串指针类型
    • id指向一个类的实例对象
    • isa每个类的示例对象都保存的指针,指向类对象
    • Class指向类对象
    • _cmd每个OC方法都有的参数

    Objective C中的实例方法

    在OC中,方法调用称为向对象发送消息,为什么这么叫呢?我们先看个例子

    [receiver message]
    

    那么,[receiver message]编译后是什么呢?

    编译后是这个样子的

    objc_msgSend(receiver, selector)
    

    我们看看这个方法的文档

    id objc_msgSend(id self, SEL op, ...)
    

    参数

    • self 消息的接收者
    • op 消息的selector,一个C的字符串用来定位
    • … 消息传入参数的数组

    Runtime如何找到实例方法执行体?

    因为OC中存在一种对象叫做类对象(Class Object),类对象中保存了方法的列表,superClass等信息。

    类对象的定义

    typedef struct objc_class *Class;
    
    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class super_class                                        OBJC2_UNAVAILABLE;
        const char *name                                         OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
        struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
        struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    

    我们会看到struct objc_method_list **methodLists OBJC2_UNAVAILABLE;那么,OC的实例对象又是如何找到类对象呢?从上文的第一部分,我们可以看到,objc_msgSend这个C函数输入参数包括,id类型的self,SEL类型的_cmd,以及参数列表。很直观,id类型中一定存在一个可以找到类对象的指针。我们来看看id定义

    typedef struct objc_object *id;
    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    }
    

    现在很清楚了,实例对象通过isa找到类对象。
    我们知道了

    • OC的对象通过isa找到类对象
    • 类对象查找自己存储的方法列表来找到对应的方法执行体
    • 方法执行体执行具体的代码,并返回值给调用者。

    那么,实际的方法执行体(Method)到底是什么东西?
    看看Method的定义

    struct objc_method {
        SEL method_name                                          OBJC2_UNAVAILABLE;
        char *method_types                                       OBJC2_UNAVAILABLE;
        IMP method_imp                                           OBJC2_UNAVAILABLE;
    } 
    

    Method保存了

    • 方法的名字
    • 方法的类型(参数列表)
    • 方法的具体实现,由IMP指向

    其中IMP的定义

    #if !OBJC_OLD_DISPATCH_PROTOTYPES
    typedef void (*IMP)(void /* id, SEL, ... */ ); 
    #else
    typedef id (*IMP)(id, SEL, ...); 
    #endif
    

    我们来看一个例子,
    定义一个

    @interface CustomObject : NSObject
    -(NSString *)returnMeHelloWorld;
    @end
    @implementation CustomObject
    
    -(NSString *)returnMeHelloWorld{
        return @"hello world";
    }
    @end
    

    我们先只看调用这一行

        NSString * helloWorld =  [obj returnMeHelloWorld];
    
    • 编译成如下id objc_msgSend(self,@selector(returnMeHelloWorld:));
    • 在self中沿着isa找到CustomObject的类对象
    • 类对象查找自己的方法list,找到对应的方法执行体Method
    • 把参数传递给IMP实际指向的执行代码
    • 代码执行返回结果给helloWorld

    类方法又是如何处理的?

    上文提到了,实例方法,那么类方法又是如何处理的呢?

    由上文我们知道,实例对象由类对象创建,类对象保存了实例对象的方法列表。

    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class super_class                                        OBJC2_UNAVAILABLE;
        const char *name                                         OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
        struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
        struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    

    这个isa指向的一个Class类型,就是保存了类方法的地方。这个Class类型的东西就是类元对象

    类对象由类元对象(meta class)创建,类元对象保存了类对象的类方法,类名字等信息。

    类元对象和类对象都是Class类型,只不过服务的对象不同罢了。

    到这里,有同学可能要问了,类元对象由什么创建呢?

    答案: NSObject类元对象

    那么NSObject类元对象由什么创建呢?(就喜欢刨根问底)

    答案: NSObject类元对象自身

    我们来看一个例子,验证下这一些列的讲解,也加深一下印象

        Class class = [CustomObject class];//类对象
    
        Class metaClass = object_getClass(class);//类元对象
        Class metaOfMetaClass = object_getClass(metaClass);//NSObject类元对象
        Class rootMataClass = object_getClass(metaOfMetaClass);//NSObject类元对象的类元对象
    
        NSLog(@"CustomObject类对象是:%p",class);
        NSLog(@"CustomObject类元对象是:%p",metaClass);
        NSLog(@"metaClass类元对象:%p",metaOfMetaClass);
        NSLog(@"metaOfMetaClass的类元对象的是:%p",rootMataClass);
        NSLog(@"NSObject类元对象%p",object_getClass([NSObject class]));
    

    可以看到Log

    CustomObject类对象是:0x10248aed0
    CustomObject类元对象是:0x10248aea8
    metaClass类元对象:0x102ce5198
    metaOfMetaClass的类元对象的是:0x102ce5198
    NSObject类元对象0x102ce5198
    
    查找流程

    看看SuperClass

    在面相对象中,我们知道,子类调用一个方法,如果子类没有实现,会查找基类。OC作为一种面相对象的语言,当然支持这些。
    我们依然写一段示例代码

        Class class = [CustomObject class];//类对象
        Class superClass = class_getSuperclass(class);//基类对象NSObject
        Class superOfNSObject = class_getSuperclass(superClass);//NSObject类元对象
    
        NSLog(@"CustomObject类对象是:%p",class);
        NSLog(@"CustomObject类superClass是:%p",superClass);
        NSLog(@"NSObject的superClass是:%p",superOfNSObject
    

    可以看到log

    CustomObject类对象是:0x101792e88
    CustomObject类superClass是:0x101fec170
    NSObject的superClass是:0x0
    

    综合上述两个例子,我们绘制出完整的isa和superClass图

    提高方法查找机制效率-Class Cache

    通过上文我们知道,在OC中方法是通过isa指针,找到方法中的Method list来查询的。而一个类往往会实现很多方法,每次调用都查询一次Method list的分发表(dispatch table)的代价是很高的(因为,这种查询可能每个RunLoop就执行上亿次)。这也就引入了Class Cache.

    Class Cache认为,当一个方法被调用,那么它之后被调用的可能性比较大。

    举个例子,我们常见的alloc,init,调用顺序如下

    CustomObject * obj = [[CustomObject alloc] init];
    
    • alloc是类方法,沿着isa找到CustomObject类元对象,发现没有实现alloc
    • 沿着super,找到NSObject类元方法,执行alloc方法,并把alloc加入到NSObject类元对象的Class Cache中
    • init是实例方法,沿着isa找到CustomObject的类对象,发现没有实现init
    • 沿着super,找到NSObject类对象,执行init,并把init加入到NSObject的类对象Class Cache中

    这里,再提一下alloc,init俩个方法
    alloc方法的文档

    初始化isa,其他所有属性被设为0

    init

    NSObject的init返回self,其余的子类要调用[super init]进行必要的初始化工作。

    为什么要放在一起写?

    因为alloc和init有可能返回不同的对象

    例如下面的代码

        id a = [NSMutableArray alloc];
        id b = [a init];
        NSLog(@"%p",a);//0x7fc550400fb0
        NSLog(@"%p",b);//0x7fc5505523a0
    

    来看看为什么绝大部分对象都要继承自NSObject?

    NSObject根类除了定义了一些基本的方法,例如,description,alloc。也定义了一些Runtime的基础方法,这里把一些不常用的列出来。并且,从上文的isa和superclass图来看,NSObject是整个消息机制的核心。

     //两个Runtime的接口-这个之后在method Swizzling中详细讲解
    +initialize //在一个类接收第一条消息之前的
    +load //在一个类对象加载到Runtime的时候调用
    
    //检查是否可以向实力对象发送某消息
    +(BOOL)instancesRespondToSelector:(SEL)aSelector
    -respondsToSelector:
    
    //向对象发送消息
    - (id)performSelector:(SEL)aSelector
    - performSelector:withObject:
    - performSelector:withObject:withObject:
    ...
    
    //动态消息转发处理机制
    +resolveInstanceMethod:
    - forwardingTargetForSelector:
    - forwardInvocation:
    

    如果对这些函数不熟悉,我还是建议回去好好研读下NSObject的文档。*

    假如一个对象没办法处理实例方法怎么办?

    Objective C的方法就是前两个参数是self和_cmd的C方法

    void ocMethod(id self, SEL _cmd) {
        // implementation ....
    }
    

    在Objective中,对一个对象发送一个它没有实现的Selector是完全合法的,这样做可以隐藏某一个消息背后实现,也可以模拟多继承(OC不支持多继承)。这个机制就是动态转发机制。

    主要包括三个函数

    +resolveInstanceMethod:
    - forwardingTargetForSelector:
    - forwardInvocation:
    

    动态方法的机制第一步,类自己处理
    使用resolveInstanceMethod
    这个方法的文档

    动态为实例方法提供一个实现
    这个方法在Objective C消息转发机制之前被调用。如果 respondsToSelector或者instancesRespondToSelector: 被调用,可以为改Selector提供动态的实现者。

    举个例子

    #import "ViewController.h"
    #import <objc/runtime.h>
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
        [self performSelector:@selector(dynamicSelector) withObject:nil];
    #pragma clang diagnostic pop
    
    }
    
    void myMehtod(id self,SEL _cmd){
        NSLog(@"This is added dynamic");
    }
    
    +(BOOL)resolveInstanceMethod:(SEL)sel{
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
        if (sel == @selector(dynamicSelector)) {
    #pragma clang diagnostic pop
            class_addMethod([self class],sel, (IMP)myMehtod,"v@:");
            return YES;
        }else{
            return [super resolveInstanceMethod:sel];
        }
    }
    
    @end
    

    简单介绍下,class_addMethod这个函数

    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
    

    作用,为一个类添加实例方法
    参数

    • cls 想要添加的类对象
    • name 添加后的方法Selector名字
    • imp 具体的方法实现
    • types 方法参数的编码,编码方式见文档

    返回值

    • 如果方法已成功添加,则返回YES,否则返回NO

    第二部(第一步不能处理的情况下),调用forwardingTargetForSelector来简单的把执行任务转发给另一个对象,到这里,还是廉价调用

    #import "ViewController.h"
    #import <objc/runtime.h>
    
    @interface CustomObject : NSObject
    -(void)dynamicSelector;
    @end
    @implementation CustomObject
    
    -(void)dynamicSelector{
        NSLog(@"hello world");
    }
    @end
    @interface ViewController ()
    @property (strong,nonatomic)CustomObject * myObj;
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.myObj = [[CustomObject alloc] init];
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
        [self performSelector:@selector(dynamicSelector) withObject:nil];
    #pragma clang diagnostic pop
    
    }
    
    -(id)forwardingTargetForSelector:(SEL)aSelector{
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
        if (aSelector == @selector(dynamicSelector) && [self.myObj respondsToSelector:@selector(dynamicSelector)]) {
            return self.myObj;
        }else{
            return [super forwardingTargetForSelector:aSelector];
        }
    #pragma clang diagnostic pop
    
    }
    @end
    

    第三步,当前两步都不能处理的时候,调用forwardInvocation转发给别人,返回值仍然返回给最初的Selector

    #import "ViewController.h"
    #import <objc/runtime.h>
    
    @interface CustomObject : NSObject
    -(void)dynamicSelector;
    @end
    @implementation CustomObject
    
    -(void)dynamicSelector{
        NSLog(@"hello world");
    }
    @end
    @interface ViewController ()
    @property (strong,nonatomic)CustomObject * myObj;
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.myObj = [[CustomObject alloc] init];
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
        [self performSelector:@selector(dynamicSelector) withObject:nil];
    #pragma clang diagnostic pop
    
    }
    -(void)forwardInvocation:(NSInvocation *)anInvocation{
        if ([self.myObj respondsToSelector:[anInvocation selector]]) {
            [anInvocation invokeWithTarget:self.myObj];
        }else{
            [super forwardInvocation:anInvocation];
        }
    }
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        return [CustomObject instanceMethodSignatureForSelector:aSelector];
    }
    @end
    

    总结

    消息转发机制使得OC可以进行”多继承”,比如有一个消息中心负责处理消息,这个消息中心很多个类都要用,继承或者聚合都不是很好的解决方案,使用单例看似可以,但单例的缺点也是很明显的。这时候,把消息转发给消息中心,无疑是一个较好的解决方案。

    Runtime能提供什么?

    Runtime的文档

    总的来说,Runtime能够提供增加,修改,删除,查询类(属性方法),协议,block。

    举个例子-用SEL属性来动态执行方法

    经常会根据后台的Json来执行不同的方法,一两个还好,如果是10个20个,用if else或者switch都是很坑爹的设计。

    #import "ViewController.h"
    #import <objc/runtime.h>
    
    @interface ViewController ()
    @property (nonatomic)SEL methodToInvoke;
    
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        int a = arc4random() % 2;
        _methodToInvoke = NSSelectorFromString([[self numToSelector] objectForKey:@(a)]);
        #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:_methodToInvoke withObject:nil];
    #pragma clang diagnostic pop
    }
    -(NSDictionary *)numToSelector{
        return @{@(0):@"method1",
                 @(1):@"method2"};
    }
    -(void)method1{
        NSLog(@"method1");
    }
    -(void)method2{
        NSLog(@"method2");
    }
    @end
    

    实际开发中Runtime如何应用?

    • 动态改变方法的执行体
    • Method Swizzling
    • NSSelectorFromString,NSClassFromString…
    • 动态添加属性(主要是类别)
    • 动态遍历属性和方法,动态为类添加方法(写Model类的wrapper很有用)
    • 消息转发机制使得架构更容易

    相关文章

      网友评论

          本文标题:iOS Runtime详解(消息机制,类元对象,缓存机制,消息转

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