iOS runtime 之 Class 和 MetaClass

作者: hi_xgb | 来源:发表于2016-03-27 20:48 被阅读6279次

    Objective-C 是 C 的超集,提供了面向对象的机制,而面向对象思想里有两个重要的概念类和实例。那么 Objective-C 里类的结构是怎样的呢?我们来一起学习记录下。

    首先了解几个概念,

    id

    在 objc.h 中我们可以看到id的定义

    /// A pointer to an instance of a class.
    typedef struct objc_object *id;
    

    正如注释里说明的,id 是指向一个objc_object的指针。那objc_object又是什么呢?

    objc_object

    在 objc.h 中我们也能看到如下定义

    /// Represents an instance of a class.
    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    

    于是我们知道了objc_object会被转换成 C 的结构体,而在这个struct中有一个 isa 指针,指向它的类别 Class。

    Class

    再往下看 Class的定义:

    /// An opaque type that represents an Objective-C class.
    typedef struct objc_class *Class;
    

    发现 Class 本身指向的也是一个 C 的 struct objc_class

    objc_class

    在 runtime.h 中我们可以看到如下定义:

    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;
    /* Use `Class` instead of `struct objc_class *` */
    

    MetaClass

    我们发现 Class 本身也有一个 isa 指针,指向的是它的 MetaClass

    • 当我们对一个实例发送消息时(-开头的方法),会在该 instance 对应的类的 methodLists 里查找。
    • 当我们对一个类发送消息时(+开头的方法),会在该类的 MetaClass 的 methodLists 里查找。

    这一过程如下图所示:


    • 每个 Class 都有一个 isa 指针指向一个唯一的 Meta Class
    • 每一个 Meta Class 的 isa 指针都指向最上层的 Meta Class,即 NSObject 的 MetaClass,而最上层的 MetaClass 的 isa 指针又指向自己

    NSClassFromString

    runtime 的动态性还体现在能在运行时新建类,以下我列出了一种使用方式。假设有一个雇员类,雇员里又有工程师、设计师、产品经理这几种岗位分类。有一种比较推荐的方式是用类簇,这样使用者无需记住各种岗位,只要知道雇员即可。具体代码实现如下:

    BBEmployee.h

    #import <Foundation/Foundation.h>
    
    typedef NS_ENUM(NSInteger, BBEmployeeType)
    {
        BBEmployeeEngineer,
        BBEmployeeDesigner,
        BBEmployeeProduceManager
    };
    
    @interface BBEmployee : NSObject
    
    + (instancetype)bb_employeeWithType:(BBEmployeeType)type;
    
    - (instancetype)initWithEmployeeType:(BBEmployeeType)type;
    
    - (void)work;
    
    - (void)rest;
    
    @end
    

    BBEmployee.m

    #import "BBEmployee.h"
    
    static NSString *b_engineer = @"BBEngineer";
    static NSString *b_designer = @"BBDesigner";
    static NSString *b_productManager = @"BBProductManager";
    
    @implementation BBEmployee
    
    + (instancetype)bb_employeeWithType:(BBEmployeeType)type
    {
        return [[self alloc] initWithEmployeeType:type];
    }
    
    - (instancetype)initWithEmployeeType:(BBEmployeeType)type
    {
        if ([super init]) {
            
            NSString *className = [self employeeClassNameWithType:type];
            if (className.length == 0) {
                self = [BBEmployee new];
            }
            else
            {
                  //根据分类动态生成子类
                Class class = NSClassFromString(className);
                self = [class new];
            }
        }
        return self;
    }
    
    - (void)work
    {
        NSLog(@"employee start coding...");
    }
    
    - (void)rest
    {
        NSLog(@"employee start coding...");
    }
    
    - (NSString *)employeeClassNameWithType:(BBEmployeeType)type
    {
        if (type == BBEmployeeEngineer) {
            return b_engineer;
        }
        else if (type == BBEmployeeDesigner)
        {
            return b_designer;
        }
        else if (type == BBEmployeeProduceManager)
        {
            return b_productManager;
        }
        return @"";
    }
    
    @end
    

    BBEngineer.h

    #import "BBEmployee.h"
    
    @interface BBEngineer : BBEmployee
    
    @end
    

    BBEngineer.m

    #import "BBEngineer.h"
    
    @implementation BBEngineer
    
    - (void)work
    {
        NSLog(@"engineer start coding...");
    }
    
    - (void)rest
    {
        NSLog(@"engineer start resting...");
    }
    
    @end
    

    BBDesigner.h

    #import "BBEmployee.h"
    
    @interface BBDesigner : BBEmployee
    
    @end
    

    BBDesigner.m

    #import "BBDesigner.h"
    
    @implementation BBDesigner
    
    - (void)work
    {
        NSLog(@"designer start coding...");
    }
    
    - (void)rest
    {
        NSLog(@"designer start resting...");
    }
    
    @end
    

    BBProductManager.h

    #import "BBEmployee.h"
    
    @interface BBProductManager : BBEmployee
    
    @end
    

    BBProductManager.m

    #import "BBProductManager.h"
    
    @implementation BBProductManager
    
    - (void)work
    {
        NSLog(@"product manager start coding...");
    }
    
    - (void)rest
    {
        NSLog(@"product manager start resting...");
    }
    
    @end
    

    以上就是 NSClassFromString 结合类簇的一种使用方式,这样后期要扩展雇员类型时只需在基类中添加子类,要扩展具体子类的行为也只需在基类中暴露接口,具体实现在对应子类中处理。

    这是我写的 runtime 系列文章中的一篇,还有以下几篇从其他方面对 runtime 进行了介绍

    1. iOS runtime之消息转发
    2. 深入理解 Objective-C 的方法调用流程
    3. iOS runtime 之 Category
    4. Objective-C 深入理解 +load 和 +initialize

    参考资料:
    http://chun.tips/blog/2014/11/05/bao-gen-wen-di-objective%5Bnil%5Dc-runtime-(2)%5Bnil%5D-object-and-class-and-meta-class/

    如果您觉得本文对您有所帮助,请点击「喜欢」来支持我。

    转载请注明出处,有任何疑问都可联系我,欢迎探讨。

    相关文章

      网友评论

      • AFWater:简单来说,MetaClass是存储的类方法列表,Class是存储的实例方法列表。
        为什么不把MetaClass的类方法列表,放到 Class里面去?即用Class来完整的解释一个类,既存储 类方法列表,又存储实例方法列表
        苹果设计MetaClass的初衷是什么?感觉用Class也可以完整的解释一个类,存储所有有关方法信息
        NieFeng1024:@萧旭 假设 类方法常驻内存不回收,而成员方法生命周期和成员绑定需要回收内存,那么区分开就有意义了。求正解。
        萧旭:@阳光暖暖的_ 楼主说的不是更节省内存么?

        希望能有人回答下楼主的疑惑,同时也是我的疑惑!谢谢
        蚂蚁牙齿不黑:节省内存啊 还有别的原因吗
      • 杰克大王:有帮助,谢谢
      • MountainHill:"当我们对一个类发送消息时(+开头的方法),会在该类的 MetaClass 的 methodLists 里查找。"
        理解错误,class也是对象,像类发送消息实际上是像类对象发送消息,是在类的methodLists里查找,不是在类的MetaClass。
        coderRcsh:@萧旭 向对象发送消息,对象会根据isa指针,从这个对象的类的方法列表去查找这个消息方法,class也是对象,所以 类方法就是从class对象的类中去获取方法列表,而metaClass就是class对象的类。懂了吗
        萧旭:@Iceguest感觉楼主说的有点道理啊!class也是对象,向类发送消息不也就是向对象发送消息吗???
        希望能有人回答一下!!
        Iceguest:没明白你的意思,向类发消息就是在MetaClass的方法列表中查找,向实例对象发消息才在对象的方法列表中查找
      • dd916d1c1a4b:收藏了,写的很清晰

      本文标题:iOS runtime 之 Class 和 MetaClass

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