详解Objective-C的isa与meta-class

深入探讨Objective-C中isa指针的作用与meta-class的概念,揭示类与对象之间的关系及其运行时机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先说明,这篇文章几乎都是抄录的别人的博客,简书文章,在此总结,只是为了方便记忆和以后阅读,如果有什么失礼的地方,请大家及时指正。

大神们:(推荐阅读原文)
1. 优快云博客:小九
2. 优快云博客:梵尘yst
3. 简书:曲年

好了,抄录正式开始

每个Objective-C对象都有一个隐藏的数据结构,这个数据结构是Objective-C对象的第一个成员变量,它就是isa指针。
这个isa到底是什么呢?官方介绍是这样的:

Every object is connected to the run-time system through itsisa instance variable, inherited from the NSObject class.isa identifies the object’s class; it points to a structurethat’s compiled from the class definition. Through isa, anobject can find whatever information it needs at run timesuch asits place in the inheritance hierarchy, the size and structure ofits instance variables, and the location of the methodimplementations it can perform in response to messages.
可见,一个对象(Object)的isa指向了这个对象的类(Class),而这个对象的类(Class)的isa指向了metaclass。这样我们就可以找到静态方法和变量了。
Objective-C的运行时是动态的,它能让你在运行时为类添加方法或者去除方法以及使用反射。这在其它语言是不多见的。

类的实例对象的 isa 指向它的类;类的 isa 指向该类的 metaclass;
类的 super_class 指向其父类,如果该类为根类则值为 NULL;

metaclass 的 isa 指向根 metaclass,如果该 metaclass 是根 metaclass则指向自身;
metaclass 的 super_class 指向父 metaclass,如果该 metaclass 是根 metaclass则指向该 metaclass 对应的类;
Object-C 为每个类的定义生成两个 objc_class ,一个普通的 class,另一个即metaclass。我们可以在运行期创建这两个 objc_class 数据结构,然后使用 objc_addClass将 class注册到运行时系统中,以此实现动态地创建一个新的类。
在NSObject.h里面:

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

再点开 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

}

这一些定义对于懂的人自然懂,不会的人根本看不懂。建议看完下面的例子,再点开参考里面的链接仔细看一遍。
现在我们知道的是,对于我们新建的一个类,都会有一个隐藏的属性isa,可以通过它进行一些访问。


我们现在新建一个类Parent,继承于NSObject, 里面有成员方法-(void)selectorP,类方法+(void)ClassSelectorP。

再新建一个类Child,继承于Parent,里面有成员方法-(void)selectorC, 类方法+(void)ClassSelectorC。

现在我们新建一个实例Child* child = [Chlid new];

  1. 当我们调用[child class] 的时候,child就会通过isa指针去找到Child的class。
  2. 当我们调用[child superclass]的时候,child 通过isa找到Child的class,再通过super_class,找到Parent的class。
    在这里,再普及objc_class 的两种类型:
    class     实例对象(child、Child)的isa指向的结构体;
    metaclass  class的isa指向的一个结构体;
  3. 接着,调用[child SelectorC],child通过isa找到Child的class,在class(注意看上面 struct objc_class 的定义)的方法列表里面找到SelectorC;
  4. 再试着调用[child SelectorP],child通过isa找到Child的class,发现class里面并没有这个方法,通过class里面的super_class找到Parent的class,在里面的方法列表找到了SelectorP;
  5. 再是类方法[Child ClassSelectorC],Child(请注意,大写)通过isa找到Child的class,通过class的isa找到Child的metaclass,在metaclass的方法列表里面找到了ClassSelectorC;
  6. 再试着调用[Child ClassSelectorP],Child通过isa找到Child的class,通过class的isa找到Child的metaclass,发现metaclass里面并没有这个方法,通过metaclass里面的super_class找到Parent的metaclass,在里面的方法列表找到了ClassSelectorP;  
- (void)viewDidLoad {
    [super viewDidLoad];
    Class clazz = [self class];
    Class clarr = [AroundMapController class];
    Class metalclazz = objc_getMetaClass("AroundMapController");
    if (class_respondsToSelector(metalclazz, @selector(viewWillAppear:))) {
        NSLog(@"ok");
    }
    if (clarr == clazz) {
        NSLog(@"2 ok");
    }
}

这个是验证runtime的机制,可以把这段代码复制到自己的controller.m里面,运行一下。记得把AroundMapController改成自己的controller的名字,还有引入头文件

Class newClass =  
    objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);  
class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");  
objc_registerClassPair(newClass);  
  1. 函数ReportFunction就是添加的实例方法的具体实现
void ReportFunction(id self, SEL _cmd)  
{  
    NSLog(@"This object is %p.",self);  
    NSLog(@"Class is %@, and super is %@.",[self class],[self superclass]);  
    Class currentClass = [self class];  
    for( int i = 1; i < 5; ++i )  
    {  
        NSLog(@"Following the isa pointer %d times gives %p",i,currentClass);  
        currentClass = object_getClass(currentClass);  
    }  
    NSLog(@"NSObject's class is %p", [NSObject class]);  
    NSLog(@"NSObject's meta class is %p",object_getClass([NSObject class]));  
}  

看起来一切都很简单,运行时创建类只需要三步:

  1. 为”class pair”分配空间(使用objc_allocateClassPair).
    1. 为创建的类添加方法和成员(上例使用class_addMethod添加了一个方法)。
    2. 注册你创建的这个类,使其可用(使用objc_registerClassPair)。

估计读者马上就要问:什么是“class pair”? objc_allocateClassPair只返回一个值:Class。那么pair的另一半在哪里呢?
是的,估计你已经猜到了这个另一半就是meta-class,也就是这篇短文的标题,但是要解释清楚它是什么,为什么需要它,还需要交代下OC的对象与类的相关背景。

一个数据结构何以成为一个对象?

每个对象都会有一个它所属的类。这是面向对象的基本概念,但是在OC中,这对所有数据结构有效。任何数据结构,只要在恰当的位置具有一个指针指向一个class,那么,它都可以被认为是一个对象。
在OC中,一个对象所属于哪个类,是由它的isa指针指向的。这个isa指针指向这个对象所属的class。
实际上,OC中对象的定义是如下的样子:

typedef struct objc_object {  
      Class isa;  
}*id;  

这个定义表明:任何以一个指向Class的指针作为首个成员的数据结构都可以被认为是一个objc_object.
最重要的特性就是,你可以向OC中的任何对象发送消息,如下这样:

@”stringValue" writeToFile:@"/file.txt atomically:YES encoding: NSUTF8StringEncoding error:NULL];  

运行原理就是,当你向一个OC对象发送消息时(上文的@“stringValue”),运行时库会根据对象的isa指针找到这个对象所属的类(上文为例,会找到NSCFString类).这个类会包含一个所有实例方法的列表及一个指向superclass的指针以便可以找到父类的实例方法。运行时库会在类的方法列表以及父类(们)的方法列表中寻找符合这个selector(上文为例,这个selector是”writeToFile:atomically:encoding:error”)的方法。找到后即运行这个方法。关键点就是类要定义这个你发送给对象的消息。

什么是meta-class?

typedef struct objc_class *Class;  
struct objc_class{  
     Class isa;  
     Class super_class;  
    /*followed by runtime specific details...*/  
};  
  1. 为了可以调用类方法,这个类的isa指针必须指向一个包含这些类方法的类结构体。
    这样就引出了meta-class的概念:meta-class是一个类对象的类。
    简单解释下:
  2. 当你向一个对象发送消息时,runtime会在这个对象所属的那个类的方法列表中查找。
  3. 当你向一个类发送消息时,runtime会在这个类的meta-class的方法列表中查找。
  4. meta-class之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。

meta-class的类又是什么呢?
完美的闭环

  1. meta-class,就像Class一样,也是一个对象。你依旧可以向它发送消息调用函数,自然的,meta-class也会有一个isa指针指向其所属类。所有的meta-class使用基类的meta-class作为他们的所属类。具体而言,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己所属的类。
  2. 根据这个规则,所有的meta-class使用基类的meta-class作为它们的类,而基类的meta-class也是属于它自己,也就是说基类的meta-class的isa指针指向它自己。(译:完美的闭环)

类和meta-class的继承

  1. 就像一个类使用super_class指针指向自己的父类一样,meta-class的super_class会指向类的super_class的meta-class。一直追溯到基类的meta-class,它的super_class会指向基类自身。(译:万物归根)
  2. 这样一来,整个继承体系中的实例、类和meta-class都派生自继承体系中的基类。对于NSObject继承体系来说,NSObject的实例方法对体系中所有的实例、类和meta-class都是有效的;NSObject的类方法对于体系中所有的类和meta-class都是有效的。

实验证明:

为了证实以上的论述,让我们查看下开篇代码中ReportFunction的输出。这个函数的目的就是沿着isa指针进行打印。
为了运行ErportFunction,我们需要创建一个实例,并调用report方法。

id instanceOfNewClass = [[newClass alloc]initWithDomain:@"some Domain" code:0 userInfo:nil];  
[instanceOfNewClass performSelector:@"report)];  
[instanceOfNewClass release];  

因为我们并没有对report方法进行声明,所以我们使用performSelector进行调用,这样避免编译器警告。
然后ReportFunction函数会沿着isa进行检索,来告诉我们class,meta-class以及meta-class的class是什么样的情况:

【注:ReportFunction使用object_getClass来获取isa指针指向的类,因为isa指针是一个受保护成员,你不能直接访问其他对象的isa指针。ReportFunction没有使用class方法是因为在一个类对象上调用这个方法是无法获得meta-class的,它只是返回这个类而已。(所以[NSString class]只是返回NSString类,而不是NSString的meta-class]

以下是程序的输出:

This object is 0x10010c810.  
Class is RuntimeErrorSubclass, and super is NSError.  
Followingthe isa pointer 1times gives 0x10010c600  
Followingthe isa pointer 2times gives 0x10010c630  
Followingthe isa pointer 3times gives 0x7fff71038480  
Followingthe isa pointer 4times gives 0x7fff71038480  
NSObject's class is 0x7fff710384a8  
NSObject's meta class is 0x7fff71038480  

观察通过isa获得的地址:
对象的地址是 0x10010c810.
类的地址是 0x10010c600.
类的meta-class地址是 0x10010c630.
类的meta-class的类地址是 0x7fff71038480.(即NSOjbect的meta-class)
NSObject的meta-class的类地址是它自身。
这些地址的值并不重要,重要的是它们说明了文中讨论的从类到meta-class到NSObject的meta-class的整个流程。

结论:
meta-class是类对象的类,每个类都有自己单独的meta-class。所有的类对象并不会属于同一个meta-class。
meta-class要保证类对象具有继承体系中基类的所有实例和类方法,以及继承体系中的所有中间类方法。对于所有NSObject继承体系下的类,NSObject的实例方法和协议方法对他们和他们meta-class的对象都要有效。
所有的meta-class使用基类的meta-class作为自己的基类,对于顶层基类的meta-class也是一样,只是它指向自己而已。

再次说明:
这篇文章纯属抄录,感谢各位大神的总结,如果本文有什么不对的地方,请及时指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值