×

iOS runtime 之 Class 和 MetaClass

96
hi_xgb
2016.03.27 20:48* 字数 747

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/

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

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


欢迎大家关注我们团队的公众号,不定期分享各类技术干货


image
日记本
Web note ad 1