专注、坚持

Obj-C 中的对象

2019.12.23 by kingcos
Date Notes Refer.
2019-03-18 首次提交 objc4-750
2019-08-18 抽离 isa 部分;
重整文章结构
Obj-C 中的 isa 指针 - kingcos
2019-08-29 增加 class_rw_t 等细节 -
2019-12-23 细节微调 objc4-756.2

0

Preface

Obj-C 中的对象分为实例对象(Instance Object)、类对象(Class Object)、以及元类对象(Meta-class Object)三种,本文将借助源码简单了解一下不同类型对象的真实构造。

1

为了方便下文举例,这里我们定义一个 EDdeviceProtocol 协议,并让继承自 NSObjectComputer 类遵守该协议:

// EDdeviceProtocol 协议
@protocol EDdeviceProtocol
- (void)powerOn;
@end

// Computer 类,继承自 NSObject,遵守 EDdeviceProtocol
@interface Computer : NSObject <EDdeviceProtocol> {
// 成员变量
@public
    int _memorySize;
    int _diskSize;
}

// 属性
@property (copy) NSString *model;

// 实例方法
- (void)print:(NSString *)content;

// 类方法
+ (NSString *)arch;
@end

@implementation Computer
- (void)print:(NSString *)content {
    NSLog(@"Print content: %@.", content);
}

// 协议方法
- (void)powerOn {
    NSLog(@"%s", __func__);
}

+ (NSString *)arch {
    return @"ARM 64";
}
@end

实例对象

我们创建一个 Computer 类型的实例对象,并对其中的成员变量赋值:

// cpt 为指向 Computer 类型实例对象的指针
Computer *cpt = [[Computer alloc] init];

// ERROR: Direct access to Objective-C's isa is deprecated in favor of object_getClass()
// cpt->isa;

cpt->_memorySize = 16;
cpt->_diskSize = 512;

cpt.model = @"MacBook Pro 13'";

我们在程序结束前的 return 0; 一行打个断点,即可看到 cpt 指向的实例对象中存储的数据:

5

其中存放了 isa 指针成员变量(但无法直接访问)以及其它成员变量的具体值。isa 并不是开发者自己定义的,而是继承自 NSObject 基类。

// NSObject.h

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

类对象

我们知道,只要创建的实例对象属于同一个类,那么它们就都可以调用该类的实例方法,因此这些方法其实并不需要每个实例对象都单独保存一份。在 Obj-C 中,实例方法存放在类对象中,类方法存储在元类对象中。与实例对象不同,类对象和元类对象并不需要开发者手动创建。类对象可以通过 [cpt class][Computer class] 或 Runtime API object_getClass 获取:

// 获取类对象的方法:
Class cptClass1 = [cpt class];
Class cptClass2 = [Computer class];
Class cptClass3 = object_getClass(cpt);

// LLDB:
// (lldb) p/x cptClass1
// (Class) $0 = 0x00000001000013d0 Computer
// (lldb) p/x cptClass2
// (Class) $1 = 0x00000001000013d0 Computer
// (lldb) p/x cptClass3
// (Class) $2 = 0x00000001000013d0 Computer

类对象的类型是 Class,本质是指向 objc_class 结构体的指针。objc_class 结构体中存放了 isasuperclass、方法缓存、以及为该类实例对象所公用的一些信息:

// Object.mm

typedef struct objc_class *Class;

// objc-runtime-new.h
struct objc_class : objc_object {
    // Class ISA;
    // 父类指针
    Class superclass;
    // 方法缓存,详见 Reference
    cache_t cache;             // formerly cache pointer and vtable
    // 可读可写表(class_rw_t)等
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    // ...
};

// objc-private.h
struct objc_object {
private:
    // isa 指针
    isa_t isa;

// ...
}

class_data_bits_t

先来看看 objc_class 中的 class_data_bits_t

// objc-runtime-new.h

struct objc_class : objc_object {
    // objc_class 中的 data 方法,返回指向 class_rw_t 的指针
    class_rw_t *data() {
        // bits 即 objc_class 中 class_data_bits_t 结构体类型的成员变量
        return bits.data();
    }

    // ...
}

#if !__LP64__
// 非 64 位,跳过
#elif 1
// class is a Swift class from the pre-stable Swift ABI
#define FAST_IS_SWIFT_LEGACY    (1UL<<0)
// class is a Swift class from the stable Swift ABI
#define FAST_IS_SWIFT_STABLE    (1UL<<1)
// class or superclass has default retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR     (1UL<<2)
// data pointer
#define FAST_DATA_MASK          0x00007ffffffffff8UL
#else
// 不会落到该 case 下,跳过
#endif

struct class_data_bits_t {
    // Values are the FAST_ flags above.
    uintptr_t bits;

    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

    // ...
}

// sys/_types/_uintptr_t.h
typedef unsigned long           uintptr_t;

如上,class_data_bits_t 结构体中只有一个 uintptr_t bits 成员变量,其本质是 unsigned long 类型,占用 8 个字节,即 64 位。但其存储方式类似位域,将不同的数据存储在不同的位中(关于位域可详见《C/C++ 中的位域与共用体》一文):

Bits Explanation
0 ~ 1 FAST_IS_SWIFT_LEGACY:标志该类是否来自于 ABI 稳定版本的 Swift
1 ~ 2 FAST_IS_SWIFT_STABLE:标志该类是否来自于 ABI 稳定版本的 Swift
2 ~ 3 FAST_HAS_DEFAULT_RR:类或父类含有默认的持有或引用(Retain / Reference)retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
3 ~ 47 FAST_DATA_MASK:指向 class_rw_t 结构体的指针
47 ~ 63 为了字节对齐,掩码填充 0

class_rw_t

class_rw_t 指的是可读可写(Read-Write)表。该结构体中存储了指向 class_ro_t 只读表的指针、以及方法、属性、协议等信息:

// objc-runtime-new.h

// 可读可写表
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    // 只读表的指针(const:不可修改指针指向内存空间中的数据)
    const class_ro_t *ro;

    // 方法、属性、协议信息
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    // ...
};

其中 method_array_tproperty_array_tprotocol_array_t 类型的结构几乎一致,这里以 method_array_t 为例:

// objc-runtime-new.h

// method_array_t 继承自 list_array_tt,其中 List 类型为 method_list_t,Element 类型为 method_t
class method_array_t :
    public list_array_tt<method_t, method_list_t>
{
    // ...
};

// list_array_tt 中定义了元素类型为 Element,列表为 List
template <typename Element, typename List>
class list_array_tt {
    // ...
};

// method_t 即是 Obj-C 中方法的结构(详见下文)
struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;

    // ...
};

// method_list_t 继承自 entsize_list_tt,其中 List 类型为 method_list_t,Element 类型为 method_t
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
    // ...
};

// entsize_list_tt 是非脆弱(Non-fragile)结构体数组的一般实现
template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
    // ...
};

由于 Obj-C 中的 Class 是一个不透明类型,但我们可以尝试一下将类对象的原始结构桥接到其中,首先需要将以下我整理好的内容放在 .h 引入,需要注意的是引入的实现文件需要支持 C++,即以 .mm 结尾:

-> 点击此处即可查看完整内容 <-
//
//  class_details.h
//  Powered by kingcos.me
//
//  Created by kingcos on 2019/8/31.
//  Copyright © 2019 kingcos. All rights reserved.
//

#ifndef class_details_h
#define class_details_h

#define FAST_DATA_MASK          0x00007ffffffffff8UL

typedef struct v_objc_class *V_Class;
typedef unsigned long v_uintptr_t;
typedef v_uintptr_t v_cache_key_t;
typedef uint32_t v_mask_t;
typedef v_uintptr_t v_protocol_ref_t;

struct v_bucket_t {
    IMP _imp;
    v_cache_key_t _key;
};

struct v_cache_t {
    struct v_bucket_t *_buckets;
    v_mask_t _mask;
    v_mask_t _occupied;
};

union v_isa_t {
    V_Class cls;
    v_uintptr_t bits;
};

struct v_objc_object {
    v_isa_t isa;
};

template <typename Element, typename List, uint32_t FlagMask>
struct v_entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
    Element first;
};

struct v_method_t {
    SEL name;
    const char *types;
    IMP imp;
};

struct v_method_list_t : v_entsize_list_tt<v_method_t, v_method_list_t, 0x3> {
};

struct v_protocol_list_t {
    uintptr_t count;
};

struct v_ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    // alignment is sometimes -1; use alignment() instead
    uint32_t alignment_raw;
    uint32_t size;
};

struct v_ivar_list_t : v_entsize_list_tt<v_ivar_t, v_ivar_list_t, 0> {
};

struct v_property_t {
    const char *name;
    const char *attributes;
};

struct v_property_list_t : v_entsize_list_tt<v_property_t, v_property_list_t, 0> {
};

struct v_class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    uint32_t reserved;

    const uint8_t * ivarLayout;

    const char * name;
    v_method_list_t * baseMethodList;
    v_protocol_list_t*** * baseProtocols;
    const v_ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    v_property_list_t *baseProperties;
};

template <typename Element, typename List>
class v_list_array_tt {
    union {
        List* list;
        v_uintptr_t arrayAndFlag;
    };
};

class v_method_array_t :
public v_list_array_tt<v_method_t, v_method_list_t>
{
};

class v_property_array_t :
public v_list_array_tt<v_property_t, v_property_list_t>
{
};

class v_protocol_array_t :
public v_list_array_tt<v_protocol_ref_t, v_protocol_list_t>
{
};

struct v_class_rw_t {
    uint32_t flags;
    uint32_t version;

    const v_class_ro_t *ro;

    v_method_array_t methods;
    v_property_array_t properties;
    v_protocol_array_t protocols;
};

struct v_class_data_bits_t {
    v_uintptr_t bits;

    v_class_rw_t* data() {
        return (v_class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

struct v_objc_class : v_objc_object {
    V_Class superclass;
    v_cache_t cache;
    v_class_data_bits_t bits;

    v_class_rw_t *data() {
        return bits.data();
    }
};

#endif /* class_details_h */

我们仍以 Computer 类为例,将类或元类对象转换为其真实结构时,我们就能看到其中的细节了:

// main.mm

V_Class cptClassObject = (__bridge V_Class)[Computer class];
v_class_rw_t *cptRWTable = cptClassObject->data();

4

有心的同学可能会发现,这里的方法列表个数显示为 5,根据上面的 Obj-C 源码,应当是 model 属性自动生成的 getter & setter,以及 print:arch,那么怎么会多一个呢?其实这是由于定义属性,增加了一个名为 .cxx_destruct 的方法,关于此本文不再涉及,考虑放置在 dealloc 相关的文章中。

因此 class_rw_t 中存储的方法、属性和协议列表都继承自 list_array_tt<Element, List>,其中的值共有三种可能:

  1. 空,即没有定义任何内容;
  2. 一个指向单一列表的指针,即只存储了编译时(包括类本身和分类中)确定的内容;
  3. 一个指向列表的指针数组(类似二维数组),即存储了编译时确定以及运行时附加的内容。

class_ro_t

class_ro_t 存在于 class_rw_t 中,是只读(Read-Only)表:

// objc-runtime-new.h

// 只读表
struct class_ro_t {
    // 标志位
    uint32_t flags;
    uint32_t instanceStart;
    // 实例大小
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;

    // 类名
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    // 成员变量
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

而与 class_rw_t 不同的是,class_ro_t 中的方法、属性、成员变量分别直接继承自 method_list_tivar_list_tproperty_list_t, 少了一维。这是因为只读表只存储编译时(包括类本身和分类中)确定的内容,在运行时将不会再次附加其它的数据。

为了证明上述结论,我们再简单了解一下类在首次初始化中所执行的 realizeClass 方法(注意,realizeClass 存在于 obj4-750,而在 Xcode 11.1 和 obj4-756.2 及之后该函数已被更名为 realizeClassWithoutSwift 且有部分差异,但由于主体部分未改变且变更不影响如下分析暂时略过):

// objc-runtime-new.h

// 类是未实现的 Future 类(关于 Future 类目前网上资料较少,Runtime 中有 objc_getFutureClass API,官方文档意为用作与 CoreFoundation 中类型无缝桥接(Toll-Free Bridging),比如 __NSCFArray 等,但该方法并未暴露在外界,即不允许开发者去调用)
// class is unrealized future class - must never be set by compiler
#define RO_FUTURE             (1<<30)
// 类已实化
// class is realized - must never be set by compiler
#define RO_REALIZED           (1<<31)

// class_t->data is class_rw_t, not class_ro_t
#define RW_REALIZED           (1<<31)
// class is unresolved future class
#define RW_FUTURE             (1<<30)

// objc-runtime-new.mm

/***********************************************************************
* realizeClass
* Performs first-time initialization on class cls,
* 在类上执行首次初始化,
* including allocating its read-write data.
* 包括分配读写数据。
* Returns the real class structure for the class.
* 返回类的真实类结构。
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClass(Class cls)
{
    runtimeLock.assertLocked();

    // 只读表指针
    const class_ro_t *ro;
    // 可读可写表指针
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;

    // 若为空,则返回
    if (!cls) return nil;
    // 若已经实化,则返回
    if (cls->isRealized()) return cls;
    assert(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?

    // 实化前,cls->data() 返回指向只读表的指针,因此可以强转
    ro = (const class_ro_t *)cls->data();
    // 判断是否为未来的类(即 RO_FUTURE 标志着读写数据是否已经被分配)
    // 可读可写表和只读表的首个成员均为 flags 且 RO_FUTURE 与 RW_FUTURE 实际值相同
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        // 将数据赋值给可读可写表
        rw = cls->data();
        // 将可读可写表中的只读表赋值给只读表
        ro = cls->data()->ro;
        // 更新标记位
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // 通常为该逻辑

        // Normal class. Allocate writeable class data.
        // 普通类,分配可写入类数据
        // 分配空间
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
        // 将可读可写表中的只读表指针指向只读表指针
        rw->ro = ro;
        // 更新标记位,实化且正在实化
        rw->flags = RW_REALIZED|RW_REALIZING;
        // 将可读可写表写回类中
        cls->setData(rw);
    }

    // ---
    // 以下内容与 class_rw_t 相关性不大,仅供参考

    // 是否为元类
    isMeta = ro->flags & RO_META;

    rw->version = isMeta ? 7 : 0;  // old runtime went up to 6

    // Choose an index for this class.
    // 设置类的索引
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    cls->chooseClassArrayIndex();

    // 打印 Debug 信息
    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u",
                     cls->nameForLogging(), isMeta ? " (meta)" : "",
                     (void*)cls, ro, cls->classArrayIndex());
    }

    // 先实化父类,再实化类 / 元类,最后实化类本身
    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    supercls = realizeClass(remapClass(cls->superclass));
    metacls = realizeClass(remapClass(cls->ISA()));

#if SUPPORT_NONPOINTER_ISA
    // 通常为该逻辑

    // Disable non-pointer isa for some classes and/or platforms.
    // Set instancesRequireRawIsa.
    bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
    bool rawIsaIsInherited = false;
    static bool hackedDispatch = false;

    if (DisableNonpointerIsa) {
        // Non-pointer isa disabled by environment or app SDK version
        instancesRequireRawIsa = true;
    }
    else if (!hackedDispatch  &&  !(ro->flags & RO_META)  &&
             0 == strcmp(ro->name, "OS_object"))
    {
        // hack for libdispatch et al - isa also acts as vtable pointer
        hackedDispatch = true;
        instancesRequireRawIsa = true;
    }
    else if (supercls  &&  supercls->superclass  &&
             supercls->instancesRequireRawIsa())
    {
        // This is also propagated by addSubclass()
        // but nonpointer isa setup needs it earlier.
        // Special case: instancesRequireRawIsa does not propagate
        // from root class to root metaclass
        instancesRequireRawIsa = true;
        rawIsaIsInherited = true;
    }

    if (instancesRequireRawIsa) {
        cls->setInstancesRequireRawIsa(rawIsaIsInherited);
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    // 在重新映射的情况下更新超类和元类
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    // 如果是父类,且非元类,调整实例变量,可能会重新分配 class_ro_t 并更新 ro 变量
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    // 设置实例大小
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }

    // 设置子类 / 基类
    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    // 真正去连接分类
    methodizeClass(cls);

    return cls;
}

struct objc_class : objc_object {
    // 判断类是否实化
    bool isRealized() {
        return data()->flags & RW_REALIZED;
    }
}

method_t

method_t 是 Obj-C 中方法的本质类型:

// objc.h

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

// objc-ptrauth.h

#if __has_feature(ptrauth_calls)
// Method lists use process-independent signature for compatibility.
using MethodListIMP = IMP __ptrauth_objc_method_list_imp;
#else
using MethodListIMP = IMP;
#endif

struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

method_t 中存储了方法的名称、类型和 IMP

Member Notes
SEL name; 方法名,又称方法选择器(Selector),同名方法即使不同类也拥有相同的 SEL 类型方法名;其底层数据类型为 struct objc_selector 但没有找到相关开源,可近似认为是 C 中 char * 字符串;可以使用 @selector()sel_registerName() 方法传入方法来得到方法名;也可通过 sel_getName()NSStringFromSelector()SEL 类型转换为 char *NSString
const char *types; 方法类型编码(Type Encoding),将方法的返回值、参数等信息编码的字符串;Obj-C 方法默认会有两个参数,即 id self & SEL op,因此默认就会有 @:,首个数字代表所有参数占用的总字节数,后续则代表每个参数的起始字节数;@encode();详细可见 Type Encodings - Objective-C Runtime Programming Guide
MethodListIMP imp; 方法实现,即方法的内存地址,类似 C 中的函数指针,可以通过该地址访问到方法的代码块进而执行

元类对象

元类对象和类对象一样,都是 Class 类型,但元类对象只可以通过 Runtime 的 object_getClass API 传入类对象,获取到元类对象。需要注意的是 [[Computer class] class][[cpt class] class] 都只能返回类对象。

对于 object_getClass,如果参数是实例对象,返回类对象;如果参数是类对象,返回元类对象;如果参数是元类对象,返回 NSObject(基类)的元类对象。class_isMetaClass 可以用来检查 Class 是否为元类对象。

VClass cptMetaVClass = (__bridge VClass)object_getClass([Computer class]);
class_rw_t *rwt = cptMetaVClass->data();

// LLDB:
// Printing description of rwt->methods->first:
// (method_t) first = {
//     name = "arch"
//     types = 0x0000000100000f86 "@16@0:8"
//     imp = 0x0000000100000d40 (DemoOC`::+[Computer arch]() at main.mm:52)
// }

元类对象中除了存储 isasuperclass 指针,还存储了类方法。其存放位置和类对象存放实例方法的存放位置(方法列表)一致。

superclass

3

What

superclass 是仅存在于类对象和元类对象的一个成员。类对象中的 superclass 指向当前类的父类,元类对象中的 superclass 指向该元类对象的父类。基类 NSObjectsuperclass 指向 nil,根元类对象的 superclass 指向基类 NSObject 的类对象。

isa & superclass

isa 将实例对象与类对象、元类对象连接起来;superclass 将子类、父类、基类连接起来。

当向一个实例发送实例方法的消息时(也可称调用实例方法,但因为 Obj-C 是消息结构的语言,方法调用并不严谨),该实例对象会通过 isa 找到其类对象,因为实例方法存储在类对象中;如果该类的类对象中并没有存储该实例方法,其类对象将使用 superclass 找到其父类的类对象,并在其中查找实例方法;直到在 NSObject 基类的类对象也无法找到时,superclass 将指向 nil 停止查找,抛出异常:unrecognized selector sent to instance(注意,我们这里仅讨论消息传递,暂时忽略消息转发)。

@interface Computer : NSObject
- (void)run;
@end

@implementation Computer
@end

@interface Mac : Computer
@end

@implementation Mac
// - (void)run {
//     NSLog(@"RUN");
// }
@end

// '-[Mac run]: unrecognized selector sent to instance 0x10330f6c0'
[[[Mac alloc] init] run];

当向一个类(的类对象)发送类方法的消息时,不会经过实例对象;类对象会通过其 isa 指针找到其元类对象,因为类方法存储在元类对象中;如果该类的元类对象中并没有存储该类方法,其元类对象将使用 superclass 找到其父类的元类对象,并在其中查找类方法;直到查找到 NSObject 基类的元类对象还没有该类方法时,NSObject 元类对象的 superclass 将指回 NSObject 类对象中,在其存储的实例方法列表中查找,若此时还没有找到将停止查找,并抛出异常:unrecognized selector sent to instance,因为 NSObject 类对象的 superclass 将指向 nil

@interface NSObject (Extension)
@end

@implementation NSObject (Extension)
- (void)run {
    NSLog(@"RUN");
}
@end

@interface Computer : NSObject
+ (void)run;
@end

@implementation Computer
@end

@interface Mac : Computer
@end

@implementation Mac
@end

// OUTPUT:
// RUN

通过上一点,其实我们可以知道在 Obj-C 中,方法是通过消息传递的,而传递的消息其实就是方法名的 SEL,因此这也是严谨意义上 Obj-C 不支持重载(Overload)的原因(但支持参数个数不同的重载),具体可详见《Obj-C 中的重载与重写》一文。

Reference