Objective-C 1.0 中类与对象的定义

栏目: Objective-C · 发布时间: 6年前

内容简介:2006 年,苹果发布了全新的 Objective-C 2.0,我们可以在苹果官网下载最新的 Objective-C Runtime 源码:疑问:Objective-C 2.0 源码为什么被命名为 objc4 ?本文我们先来介绍一下 Objective-C 1.0 中类与对象的定义,虽然它早已被废弃,而且在 Objective-C 2.0 中已完全重写了,但由于 1.0 的代码阅读起来相对简单清晰,易于理解,仍具一定参考意义。

2006 年,苹果发布了全新的 Objective-C 2.0,我们可以在苹果官网下载最新的 Objective-C Runtime 源码: objc4-750.1.tar.gz 进行阅读和分析。

疑问:Objective-C 2.0 源码为什么被命名为 objc4 ?

本文我们先来介绍一下 Objective-C 1.0 中类与对象的定义,虽然它早已被废弃,而且在 Objective-C 2.0 中已完全重写了,但由于 1.0 的代码阅读起来相对简单清晰,易于理解,仍具一定参考意义。

相关宏定义和头文件

  • __OBJC__

根据 Common Predefined Macros__OBJC__ 宏在 Objective-C 编译器中被预定义为 1 。我们可以使用该宏来判断头文件是通过 C 编译器还是 Objective-C 编译器进行编译。

  • __OBJC2__

__OBJC2__ 宏在 objc-config.h 头文件中被定义:

#ifndef __OBJC2__
#   if TARGET_OS_OSX  &&  !TARGET_OS_IOSMAC  &&  __i386__
        // old ABI
#   else
#       define __OBJC2__ 1
#   endif
#endif
  • objc-private.h

objc-private.h 文件中声明 该头文件必须在其它头文件之前导入 ,避免与其它地方定义的 idClass 产生冲突(因为在 Objective-C 1.0 和 2.0 中,定义类和对象的结构体是不同的,objc4 源码有多处分别定义了 objc_classobjc_object ,他们通过相关宏来区分):

/* Isolate ourselves from the definitions of id and Class in the compiler 
 * and public headers.
 */

#ifdef _OBJC_OBJC_H_
#error include objc-private.h before other headers
#endif

此外,在该文件中还声明了如下两个宏,作为后续不同版本下定义类和对象的区分:

#define OBJC_TYPES_DEFINED 1
#undef OBJC_OLD_DISPATCH_PROTOTYPES
#define OBJC_OLD_DISPATCH_PROTOTYPES 0

Objective-C 1.0

Class 和 id

objc.h 中,

  • Class 被定义为指向 struct objc_class指针
  • id 被定义为指向 struct objc_object指针
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

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

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

上述代码被定义在 !OBJC_TYPES_DEFINED 宏内,如前面所述,在最新的 objc4-750.1(Objective-C 2.0)源码中定了 OBJC_TYPES_DEFINED 宏为 1 ,所以上述代码只在老版本的 Objective-C 1.0 中生效。

其中 objc_class 结构体在 runtime.h 中定义如下:

#if !OBJC_TYPES_DEFINED

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

#endif
  • OBJC_ISA_AVAILABILITY

OBJC_ISA_AVAILABILITYobjc-api.h 文件中定义:

/* OBJC_ISA_AVAILABILITY: `isa` will be deprecated or unavailable 
 * in the future */
#if !defined(OBJC_ISA_AVAILABILITY)
#   if __OBJC2__
#       define OBJC_ISA_AVAILABILITY  __attribute__((deprecated))
#   else
#       define OBJC_ISA_AVAILABILITY  /* still available */
#   endif
#endif

可以看出,旧版本中,类型为 Classisa 指针在 Objective-C 2.0 中被废弃了,在 2.0 中, isa 的类型为 union isa_t ,在下文会介绍。

  • OBJC2_UNAVAILABLE

OBJC2_UNAVAILABLEobjc-api.h 文件中定义:

/* OBJC2_UNAVAILABLE: unavailable in objc 2.0, deprecated in Leopard */
#if !defined(OBJC2_UNAVAILABLE)
#   if __OBJC2__
#       define OBJC2_UNAVAILABLE UNAVAILABLE_ATTRIBUTE
#   else
        /* plain C code also falls here, but this is close enough */
#       define OBJC2_UNAVAILABLE                                       \
            __OSX_DEPRECATED(10.5, 10.5, "not available in __OBJC2__") \
            __IOS_DEPRECATED(2.0, 2.0, "not available in __OBJC2__")   \
            __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE
#   endif
#endif

同样地,通过相关宏的限定, objc_class 结构体及其内部成员只在 Objective-C 1.0 中生效。虽然在 Objective-C 2.0 中通过 C++ 的结构体语法重写了 struct objc_class 的定义,我们这里仍然可以简单分析上述这个纯 C 语法写的 struct objc_class ,窥探一下 Objective-C 1.0 中类的内部实现,仅作为参考:

  • isa: 指向该类的元类(Meta Class)的指针
  • super_class: 指向父类的指针
  • name: 类名
  • version: 类的版本信息
  • info: 类信息,供运行时使用的一些标记位
  • instance_size: 该类的实例对象的大小
  • objc_ivar_list: 指向 该类成员变量列表 的指针
  • objc_method_list: 指向 方法(函数指针)列表指针 的指针
  • objc_cache: 指针 方法调用缓存 的指针
  • objc_protocol_list: 指向 该类实现的协议列表 指针

下面我们逐项分析。

SEL

首先,在 objc.h 中,定义了 SEL 为指向 struct objc_selector 的指针:

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

但在 objc4 源码中,我们找不到 objc_selector 结构体的具体定义,不过通过调试,我们可以把 SEL 理解为就是“一个保存方法名的字符串”。

IMP

IMP 定义为一个函数指针,指向方法调用时对应的函数实现:

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

简化如下:

typedef id (*IMP)(id, SEL, ...);

Method

runtime.h 中, Method 被定义为一个指向 struct objc_method 指针,

#if !OBJC_TYPES_DEFINED

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

#endif

objc_method 结构体的定义如下:

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

它包含了一个 SEL (方法的名称)和 IMP (方法的具体函数实现),以及方法的类型 method_types ,它是个 char 指针,存储着方法的参数类型和返回值类型。

实际上, Method 的作用,相当于是在 SELIMP 之间做了一个映射,让我们可以通过 SEL 方法名找到其对应的函数实现 IMP

struct objc_class 中的方法列表结构体 objc_method_list 的定义如下:

struct objc_method_list {
    struct objc_method_list * _Nullable obsolete             OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

其中, __LP64__ 宏由预处理器定义,用于表示当前操作系统为 64 位,该宏在头文件中无法找到的,我们可以在 Mac 的终端中执行 cpp -dM /dev/null 进行查看。

Ivar

同样地, Ivar 被定义为一个指向 struct objc_ivar 的指针:

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

objc_ivar 结构体的定义如下:

struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}                                                            OBJC2_UNAVAILABLE;

struct objc_class 中的成员变量列表结构体 objc_ivar_list 的定义如下:

struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}

Cache

typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE;
};

Protocol

#ifdef __OBJC__
@class Protocol;
#else
typedef struct objc_object Protocol;
#endif
struct objc_protocol_list {
    struct objc_protocol_list * _Nullable next;
    long count;
    __unsafe_unretained Protocol * _Nullable list[1];
};

Category

/// An opaque type that represents a category.
typedef struct objc_category *Category;
struct objc_category {
    char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
    char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
}

Property

/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;

下面我们根据 Objective-C 1.0 上述定义,思考并回答如下几个问题。

类与对象的本质

  • 实例对象(Object)

我们知道,在面向对象(OOP)的编程语言中,每一个对象都是某个类的实例。

如前面所述,在 Objective-C 中,所有对象的本质都是一个 objc_object 结构体,定义如下:

struct objc_object {
    Class isa;
};

每一个实例对象都有一个 isa 指针,指向该对象对应的类,每一个类描述了一系列它的实例对象的信息,包括对象占用的内存大小,成员变量列表、成员方法列表 … 等。

对于一个具体的实例对象被初始化后,它的内存结构大致如下:

Objective-C 1.0 中类与对象的定义

其中, isa 指针为该结构体的第一个成员变量,而其类声明的成员变量依次排列在其后,排列顺序为:根类在前,父类在后,最后才为当前类。

所以网上也有这样的说法:“凡是首地址是 *isa 的 struct 指针,都可以被认为是 objc 中的对象。”

  • 类对象(Class)

在 Objective-C 中,类也被设计为一个对象,我们称之为类对象。每一个类也有一个名为 isa 的指针,也可以接受消息(类方法调用):

struct objc_class {
    Class isa;
    Class super_class;
    // ...
}

Cocoa 中绝大多数的类都继承了 NSObject 基类(除了 NSProxy ),其定义如下,与上述 objc_class 结构体基本一致。

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}
  • 元类对象(Meta Class)

因为类是对象,则它也必须是另一个类的实例,那么类的类型是什么呢?这里就引出了另外一个概念 —— Meta Class(元类)。

在 Objective-C 中,每一个类都有其 一一对应 的元类。在元类的 methodLists 中保存着类方法列表。

元类(Meta Class)也是一个对象,那么元类对象的 isa 指针又指向哪里呢?为了设计上的完整,所有的元类的 isa 指针都会指向 同一个 根元类(Root Meta Class)。根元类本身的 isa 指针指向自己,这样就行成了一个闭环,如下图所示:

Objective-C 1.0 中类与对象的定义

从图中我们也可以看出,对于 NSObject(基类或者叫根类:Root Class),它的父类指向 nil,它的 isa 指针指向根元类;对于根元类(Root Meta Class),它的父类为根类 NSObject,它的 isa 指针指向自己。

对象的实例方法的调用过程

  • 调用一个实例对象 receiver 的方法 message ,即: [receiver message] ,在编译时会被转换成 objc_msgSend(id, SEL, ...) ,objc_msgSend 的执行逻辑大致如下:
  • 根据实例对象的 isa 指针,找到该对象对应的 Class
  • Class 中根据 SEL 方法名寻找所调用方法对应的函数实现 IMP
  • 先在当前类的 cache 中查找(因为调用方法的过程是个查找 methodLists 的过程,如果每次调用都去查找,效率会非常低。所以对于调用过的方法,会以 map 的方式保存在 cache 中,下次再调用就会快很多)。如果在 cache 没找到,就去当前类的 methodLists 列表中查找,最后根据 super_class 指针找到父类(超类),在父类的 methodLists 中查找,直到找到 NSObject 类为止。如果都没找到,就报错崩溃(在崩溃前还有其他一些操作 +resolveInstanceMethod: …);
  • 找到 SEL 对应的 Method 后,就可以得到其函数实现 IMP ,然后执行。

Objective-C 1.0 中类与对象的定义

类对象的类方法的调用过程

当一个类方法被调用时,会先根据类对象的 isa 指针找到其元类,元类会首先查找它本身是否有该类方法的实现,如果没有,则该元类会向它的父类查找该方法,直到一直找到继承链的头。

  • 对象 -> 实例方法的调用:在类中寻找方法实现的函数指针地址;
  • 类(对象)-> 类方法的调用:在元类中寻找方法实现的函数指针地址;
  • 元类(对象):为了定义的完整性引申出的概念,只在编译器中会用到,在实际开发基本不会接触到。

为什么不能给类动态添加成员变量却可以添加成员方法?

参考: Objective-C 类成员变量深度剖析

类的成员变量布局以及其实例对象的大学在编译时已经确定,设想一下如果 Objective-C 允许给一个类动态增加成员变量,会带来一个问题是:为基类动态增加成员变量会导致所有已创建出的子类实例都无法使用。

我们所说的“类实例”概念(对象),指的是一块内存区域,包含了 isa 指针和所有的成员变量。所以假如允许动态修改类成员变量布局,已经创建出的类实例就不符合类定义了,变成了无效对象。

而方法的定义是在 objc_class 中管理的,不管如何增删类方法,都不影响类实例的内存布局,已经创建出的类实例仍然可正常使用。通过 Category 给类添加的成员方法是

通过 Category 给类添加属性的原理

Objective-C 中类的属性可以理解为:成员变量 + getter 方法 + setter 方法,虽然我们无法动态地给一个类添加成员变量,但我们可以通过 Category 给类添加成员方法。所以在类的 Category 中添加一个属性时,我们可以在其 getter 和 setter 方法中通过 关联对象 的方式达到添加“成员变量”的效果。

示例代码如下:

  • MyClass+Category.h
#import "MyClass.h"

@interface MyClass (Category)

@property (nonatomic, copy) NSString *name;

@end
  • MyClass+Category.m :
#import "MyClass+Category.h"
#import <objc/runtime.h>

@implementation MyClass (Category)

- (NSString *)name {
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY);
}

@end

但进一步思考一下,关联对象又是存在什么地方呢? 如何存储?对象销毁时候如何处理关联对象呢?通过查阅 objc4 源码,可以看到所有的关联对象都由 AssociationsManager 管理, AssociationsManager 里面是由一个静态 AssociationsHashMap 来存储所有的关联对象的。最后,在 Runtime 中负责销毁对象的函数 objc_destructInstance 里面会判断被销毁的对象有没有关联对象,如果有,会调用 _object_remove_assocations 做关联对象的清理工作。

参考:


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C++ 程序设计语言(特别版)(英文影印版)

C++ 程序设计语言(特别版)(英文影印版)

[美] Bjarne Stroustrup / 高等教育出版社 / 2001-8-1 / 55.00

《C++程序设计语言》(特别版)(影印版)作者是C++的发明人,对C++语言有着全面、深入的理解,因此他强调应将语言作为设计与编程的工具,而不仅仅是语言本身,强调只有对语言功能有了深入了解之后才能真正掌握它。《C++程序设计语言》编写的目的就是帮助读者了解C++是如何支持编程技术的,使读者能从中获得新的理解,从而成为一名优秀的编程人员和设计人员。一起来看看 《C++ 程序设计语言(特别版)(英文影印版)》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

MD5 加密
MD5 加密

MD5 加密工具