OC对象的本质

175 阅读16分钟

Objective-C的本质

  • 我们平时编写的Objective-C代码,底层实现其实都是C\C++代码
  • 所以Objective-C的面向对象都是基于C\C++的数据结构实现的
  • 将Objective-C代码转换为C\C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
如果需要链接其他框架,使用-framework参数。比如-framework UIKit

Objective-C对象在内存中的布局

  • 新建一个Person对象,通过观察转换的cpp文件,找到以下结构体定义
struct Person_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	int _age;
	int _height;
};
  • 在Person_IMPL中包含一个结构体成员NSObject_IVARS,它是NSObject_IMPL类型.
struct NSObject_IMPL {
	Class isa;
};
  • 由此可以看出,OC中的对象其实就是通过结构体来实现的。在NSObject_IMPL包含了一个Class类型的成员isa。继续查看Class的定义:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
  • 可以发现其实Class就是一个objc_class类型的结构体指针。在最新的objc4的源码中的objc-runtime-new.h文件中,可以找到最新的objc_class的定义如下:
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    ......
}
  • objc_class继承自结构体objc_object,而结构体objc_object的具体定义如下,内部只有一个isa指针
/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
  • 由于继承关系,结构体objc_class自然也就继承了objc_object的isa指针,所以objc_class也可以转换成如下写法:
struct objc_class {
    Class isa;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    ......
}
  • isa是继承自objc_object的属性
  • superclass表示当前类的父类
  • cache则代表方法缓存。
  • bits是class_data_bits_t类型的属性,用来存放类的具体信息。
  • 查看class_data_bits_t的源码如下:
//此处只列出核心的代码
struct class_data_bits_t {
    ......
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    ......
}
  • 这时候发现了通过bits的内部函数data()可以拿到class_rw_t类型的数据,查看class_rw_t的源码如下:
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
    explicit_atomic<uintptr_t> ro_or_rw_ext;
    Class firstSubclass;
    Class nextSiblingClass;
 private:
    using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;   
    ...
public:
const class_ro_t *ro() const { //获取class_ro_t类信息
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>()->ro;
        }
        return v.get<const class_ro_t *>();
    }
const method_array_t methods() const { //方法列表
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
        }
    }
const property_array_t properties() const { //属性列表
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->baseProperties};
        }
    }
const protocol_array_t protocols() const { //协议列表
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }    
    ...
  • class_rw_ext_t的结构:
struct class_rw_ext_t {
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};
  • 方法列表methods
  • 属性列表properties
  • 协议列表protocols。
  • 一个class_ro_t类型的只读变量ro

继续查看class_ro_t的结构如下:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  //当前instance对象占用内存的大小
    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;//基本属性列表
}

此处就不得不说class_rw_t和class_ro_t的区别了,class_ro_t中存放着类最原始的方法列表,属性列表等等,这些在编译期就已经生成了,而且它是只读的,在运行期无法修改。而class_rw_t不仅包含了编译器生成的方法列表、属性列表,还包含了运行时动态生成的方法和属性。它是可读可写的。

通过lldb打印出oc对象的结构

通过类信息直接打印

  • 可以通过lldb调试打印出objc_class结构体信息
 p (objc_class *)$0
(objc_class *) $5 = 0x00000001000025b0
(lldb) p *$5
(objc_class) $6 = {
  objc_object = {
    isa = {
      cls = 0x0000000100002588
      bits = 4294976904
       = {
        nonpointer = 0
        has_assoc = 0
        has_cxx_dtor = 0
        shiftcls = 536872113
        magic = 0
        weakly_referenced = 0
        deallocating = 0
        has_sidetable_rc = 0
        extra_rc = 0
      }
    }
  }
  superclass = NSObject
  cache = {
    _buckets = {
      std::__1::atomic<bucket_t *> = 0x00000001003ea460 {
        _sel = {
          std::__1::atomic<objc_selector *> = 0x0000000000000000
        }
        _imp = {
          std::__1::atomic<unsigned long> = 0
        }
      }
    }
    _mask = {
      std::__1::atomic<unsigned int> = 0
    }
    _flags = 32804
    _occupied = 0
  }
  bits = (bits = 4320180788)
}
(lldb) p $6.bits
(class_data_bits_t) $7 = (bits = 4320180788)
(lldb) p $6.data()
(class_rw_t *) $8 = 0x000000010180ba30
(lldb) p *$8
(class_rw_t) $9 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294976120
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
  • 通过地址偏移获取class_data_bits_t的信息,首先计算class_data_bits_t的偏移值为32(isa-8+superclass-8+cache-16)
  • isa和superclass占8个字节很好理解,我们来分下下cache为何会有16个字节?cache_t的结构如下去掉一些不占字节的(static修饰的和方法)
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED//电脑或模拟器
    explicit_atomic<struct bucket_t *> _buckets;//explicit_atomic相当于做了一个线程保护,实际类型为一个结构体指针占8个字节
    explicit_atomic<mask_t> _mask;//typedef uint32_t mask_t; 占4个字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16//(__arm64__) && __LP64__真机设备
    explicit_atomic<uintptr_t> _maskAndBuckets;// typedef unsigned long           uintptr_t; 8个字节
    mask_t _mask_unused;//和上面一样4个字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4//(__arm64__) && !__LP64__低于64位的真机设备
    // _maskAndBuckets stores the mask shift in the low 4 bits, and
    // the buckets pointer in the remainder of the value. The mask
    // shift is the value where (0xffff >> shift) produces the correct
    // mask. This is equal to 16 - log2(cache_size).
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
#else
#error Unknown cache mask storage type.
#endif
    
#if __LP64__
    uint16_t _flags;   //typedef unsigned short uint16_t; 2个字节
#endif
    uint16_t _occupied; //2个字节
};
//所以无论在什么设备上cache_t都是16个字节
  • 回到刚刚的lldb调试
(lldb) p/x Person.class
(Class) $0 = 0x00000001000025b0 Person
(lldb) p/x (class_data_bits_t *)(0x00000001000025b0 + 0x20)
(class_data_bits_t *) $1 = 0x00000001000025d0
(lldb) p *$1
(class_data_bits_t) $2 = (bits = 4320180788)
(lldb) p/x $2.data()
(class_rw_t *) $3 = 0x000000010180ba30
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294976120
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
  • 总结,上面两种方法都可以获取到对象的信息剩下的操作就一样的了,不过这种lldb调试需要在搭配好的源码环境下才能正常运行。

通过定义结构体的方法查看对象的结构

  • 根据源码的的对象结构,定义下面对应的结构体
struct xq_bucket_t {
//#if __arm64__
//    explicit_atomic<uintptr_t> _imp;
//    explicit_atomic<SEL> _sel;
//#else
    IMP _imp;
    SEL _sel;
//#endif
};
struct xq_cache_t {
//#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
//    struct xq_bucket_t * _buckets;
//    mask_t _mask;
//#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    uintptr_t _maskAndBuckets;
    mask_t _mask_unused;
//#endif
//#if __LP64__
    uint16_t _flags;
//#endif
    uint16_t _occupied;

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

};
struct xq_objc_class {
    Class ISA;
    Class superclass;
    struct xq_cache_t cache;             // formerly cache pointer and vtable
    struct xq_class_data_bits_t bits;
};

Person *p = [Person alloc];
struct xq_objc_class *pClass = (__bridge struct xq_objc_class *)([Person class]);
// 通过强转也可以达到调试效果

OC对象的内存分配

alloc

  • 在iOS中一般使用如下[[NSObject alloc] init]创建对象,其中[NSObject alloc]就是为NSObject分配内存空间,下面,我们就从源码入手,来理解OC对象是如何分配内存的。

  • [NSObject alloc]的调用流程 objc_alloc->callAlloc->alloc->_objc_rootAllocobjc_alloc->callAlloc->_objc_rootAllocWithZone->_class_createInstanceFromZone->instanceSize、calloc、initInstanceIsa

最后分配
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
	...
    size = cls->instanceSize(extraBytes);//获取对象实际大小
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;  
    //分配内存
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }
    ...
    //关联对象
    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }
    ...
}

可以看出,其中真正用来分配内存的是C函数calloc,calloc函数传入了两个参数,第一个参数表示对象的个数,第二个参数size表示对象占据的内存字节数。因此size就表示当前对象所需要的内存大小。

  • 这里的size变量表示当前对象所占用的内存大小,可以查看cls->instanceSize(extraBytes)属性内部实现,如下:
static inline size_t align16(size_t x) {//16字节对齐
    return (x + size_t(15)) & ~size_t(15);
}

size_t fastInstanceSize(size_t extra) const
{
    ASSERT(hasFastInstanceSize(extra));
    if (__builtin_constant_p(extra) && extra == 0) {
        return _flags & FAST_CACHE_ALLOC_MASK16;
    } else {
        size_t size = _flags & FAST_CACHE_ALLOC_MASK;
        // remove the FAST_CACHE_ALLOC_DELTA16 that was added
        // by setFastInstanceSize
        return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
    }
}

size_t instanceSize(size_t extraBytes) const {
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
        return cache.fastInstanceSize(extraBytes);
    }

    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;
    return size;
}
  • 最终得到一个16字节对齐后的内存大小

OC对象的内存分配

获取内存大小的方法?

在iOS中,我们可以通过三种方式来获取一个对象的内存大小。

sizeof

  • sizeof,它其实不是一个函数,而是一个运算符,它和宏定义类似,在编译期就将传入的类型转换成具体的占用内存的大小。例如int是4个字节,那么sizeof(int)在编译期就会直接被替换成4

注意:sizeof需要传入一个类型过去,它返回的是一个类型所占用的内存空间

class_getInstanceSize

  • class_getInstanceSize(Class _Nullable cls),传入一个Class类型的对象就能得到当前Class所占用的内存大小。例如,class_getInstanceSize([NSObject class]),最后返回的是8,也就说明NSObject对象在内存中占用8个字节,而且由于NSObject最后会转化成结构体NSObject_IMPL,而且内部只有一个isa指针,所以也就可以理解为isa指针占用8个字节的存储空间。
    class_getInstanceSize函数内部其实就是调用alignedInstanceSize函数获取到对象所需要的真实内存大小。
size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}
  • 在调用calloc函数进行内存分配的时候,是将alignedInstanceSize的值当作参数赋值给calloc函数,因此calloc函数可以有如下写法:
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
	...
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }
    ...
    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }
    ......
}

由此可以看出,class_getInstanceSize(Class _Nullable cls)所返回的其实是结构体实际所需要的内存大小。

malloc_size

  • malloc_size(const void *ptr)函数,传入const void *类型的参数,就可以获取到当前操作系统所分配的内存大小。例如:还是利用NSObject来进行测试,malloc_size((__bridge const void *)([[NSObject alloc] init])),将NSObject类型的实例对象作为参数,最后得到的值为16,和我们之前使用class_getInstanceSize([NSObject class])得到的8不相同。

  • 通过上文的分析得知ios分配内存有16字节对齐的操作

  • ios内存对齐,calloc的调用流程 calloc->malloc_zone_calloc->zone->calloc->default_zone_calloc->runtime_default_zone->inline_malloc_default_zone->_nano_malloc_check_clear->segregated_size_to_fit

#define SHIFT_NANO_QUANTUM		4
#define NANO_REGIME_QUANTA_SIZE	(1 << SHIFT_NANO_QUANTUM)	// 16
static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
	size_t k, slot_bytes;

	if (0 == size) {//如果传入的值为0,size=16
		size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
	}
    // size+0b1111右移4位再左移4位
	k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
	slot_bytes = k << SHIFT_NANO_QUANTUM;							// multiply by power of two quanta size
	*pKey = k - 1;													// Zero-based!

	return slot_bytes;
}
// 这里就是内存的16字节对齐
  • 通过上面的分析,可知oc对象内存是16字节对齐的

三种获取内存方法对比

  • 在main函数中创建Person实例对象,并且使用上面的三种方式分别获取Person类的内存大小:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

@interface Person : NSObject{
    @public
    int _height;
    int _age;
    long _num;
}

@end

@implementation Person

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    	Person *p = [[Person alloc] init];
        p->_height = 180;
        p->_age = 20;
        p->_weight = 65;

        NSLog(@"sizeof --> %zd", sizeof(p));

        NSLog(@"class_getInstanceSize --> %zd", class_getInstanceSize([Person class]));

        NSLog(@"malloc_size --> %zd", malloc_size((__bridge const void *)(p)));
    }
    return 0;
}
  • 运行程序,打印出结果如下:
OC-obj[75406:1765092] sizeof --> 8
OC-obj[75406:1765092] class_getInstanceSize --> 24
OC-obj[75406:1765092] malloc_size --> 32
  • 可以看出此时sizeof(p)返回8个字节,class_getInstanceSize返回24个字节,malloc_size则返回32个字节。3个方法返回的内存大小都不一样,这是为什么呢?

sizeof(p)为什么只返回8个字节呢?

  • 因为sizeof传入的是p,而p在此处表示的是一个指向Person实例对象的一个指针,在iOS中,指针类型所占用的内存大小为8个字节。因此sizeof(p)所返回的并不是Person对象的内存大小。

  • 要想使用sizeof获取到Person对象的内存大小,就需要拿到Person最终转换成的结构体类型。

  • sizeof()和函数class_getInstanceSize()返回的是对象真正所需要的内存大小。

为何malloc_size返回的内存大小和对象实际所需内存不同?

  • 结构体的内存对齐操作

结构体不像数组,结构体中可以存放不同类型的数据,它的大小也不是简单的各个数据成员大小之和,限于读取内存的要求,而是每个成员在内存中的存储都要按照一定偏移量来存储,根据类型的不同,每个成员都要按照一定的对齐数进行对齐存储,最后整个结构体的大小也要按照一定的对齐数进行对齐。

  • 结构体的内存对齐规则如下:
第一个成员的首地址为0
每个成员的首地址是自身大小的整数倍
结构体的内存总大小是其成员中所含最大类型的整数倍

iOS系统的内存对齐操作

  • 既然Person的内存占用为24个字节,那么为什么系统会给它分配32个字节呢?其实在iOS系统中也存在内存对齐操作,也验证了上文提及的16字节内存对齐概念。

  • 我们可以通过打印内存信息来查看是否分配了32个字节,断点状态下,使用po p获取到p的内存地址为0x100522160

(lldb) po p
<Person: 0x100522160>
  • 打开Xcode下Debug->Debug Workflow->View Memory,输入刚刚获取到的内存地址,可以得到对象p的内存分配情况 其中前8个字节存储着isa指针,蓝色框中的4个字节存放着_age=0x14,绿色框中的4个字节存放着_height=0xB4,黄色框中的4个字节存放着_weight=0x41,这里因为结构体内存对齐原则,所以一直到32个字节,就是系统分配给XLPerson实例对象的内存空间,这也证明了malloc_size((__bridge const void *)(p))返回的确实是系统分配给p对象的内存空间。

OC对象的分类

  • OC对象主要分为3种:
instance对象(实例对象)
class对象(类对象)
meta-class对象(元类对象) 

instance对象

  • instance对象就是通过alloc操作创建出来的对象,每次调用alloc操作都会创建出不同的instance对象,它们拥有各自独立分配的内存空间。例如上文中使用的XLPerson的实例对象
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];
  • 其中p1、p2就是实例对象,在内存中可以同时拥有多个同一类对象的实例对象。它们各自拥有一块内存空间,用来存储独有的信息。
  • instance对象在内存中存储的信息包括
    isa指针(指向它的类对象) 其他成员变量的值

class类对象

  NSObject *obj1 = [[NSObject alloc] init];
  NSObject *obj2 = [[NSObject alloc] init];
  Class objClass1 = [obj1 class];
  Class objClass2 = [obj2 class];
  Class objClass3 = [NSObject class];
  Class objClass4 = object_getClass(obj1);
  Class objClass5 = object_getClass(obj1);

  NSLog(@"%p---%p---%p---%p---%p",objClass1,objClass2,objClass3,objClass4,objClass5);

可以得到结果为:

OC-obj[76932:1851489] 0x7fff93c23118---0x7fff93c23118---0x7fff93c23118---0x7fff93c23118---0x7fff93c23118
  • 通过结果可以发现,所有的class对象的内存地址都是相同的,这也就说明在内存中只有一个class对象,不管是使用上面的哪种方法获取到的class对象都是同一个。

  • class对象内部其实就是一个object_class的结构体,class对象存储的主要信息:

isa指针
superClass
属性信息(properties),存放着属性的名称,属性的类型等等,这些信息在内存中只需要存放一份
对象方法信息(methods)
协议信息(protocols)
成员变量信息等等(ivars)

mata-class元类对象

  • 元类其实也是一个class类型的对象,它内部的结构和类对象一致,但是元类对象中只存放了如下信息:
isa指针
superClass
类方法信息(class method
  • 元类和类一样,在内存中只会存在一个元类对象。可以通过runtime的方法获取元类对象
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
通过objc4源码发现object_getClass返回的其实就是isa指向的地址,所以可以通过传类对象的方式来获取元类对象
---------------------------------------------------------------------------
// 获取元类对象
Class metaClass1 = object_getClass(objClass1);
Class metaClass2 = object_getClass([NSObject class]);
// 判断对象是否为元类对象
BOOL isMetaClass1 = class_isMetaClass(metaClass1);
BOOL isMetaClass2 = class_isMetaClass(metaClass2);
NSLog(@"meta class---%p:%d---%p:%d",metaClass1,isMetaClass1,metaClass2,isMetaClass2);

调用结果如下:

OC-obj[77443:1879962] meta class---0x7fff93c230f0:1---0x7fff93c230f0:1

instance对象、class对象和mata-class对象的关系

上文多次提到,在Class对象内部都会有一个isa指针,那么这个isa指针的作用是什么呢?其实isa指针是instance对象、class对象和mata-class对象之间的桥梁。

isa指针的作用

  • instance对象的isa指针指向class对象,而且上文也说到,在instance对象中只存储了isa指针和具体的属性的值,当我们调用instance对象的实例方法时,其实是通过isa指针找到它的类对象,在类对象的对象方法列表(methods)中查找到方法的实现,并调用。
  • class对象的isa指针指向mata-class对象,当调用类方法时,会通过类对象的isa指针找到它的元类对象mata-class,然后在mata-class的方法列表中找到对应类方法的实现并执行。

superClass指针的作用

  • superClass其实就是指向class对象或者mata-class对象的父类,下面我们以一个简单的例子来具体说明:
@interface Person : NSObject

- (void)run;
+ (void)sleep;

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        XLPerson *p1 = [[Person alloc] init];
        [p1 run];
        [Person sleep];
    }
}

class对象中superClass指针的作用

Person继承自NSObject,并且声明了一个类方法sleep()和一个对象方法run(),当p1调用对象方法run()时

  • 首先会通过实例对象p1内部的的isa指针找到它的类对象。在类对象的方法列表中查找run()方法。
  • 如果类对象的方法列表中没有此方法的实现,那么会通过类对象的superClass指针找到它的父类对象,在此处就是NSObject对象,并且在NSObject对象的方法列表中寻找run()方法的实现,然后调用。

mata-class对象中superClass指针的作用

还是以上面的例子来说明,当Person调用类方法sleep()时

  • 首先通过Person的isa指针找到它的元类对象,在元类对象的方法列表中寻找sleep()方法。
  • 如果在Person的元类对象中没有找到sleep()方法,那么会通过Person的元类对象的superClass指针找到Person的父类对象的元类对象,此处就是NSObject的元类对象,在NSObject元类对象的方法列表中找到sleep()方法并执行。

isa、superClass总结

  • 首先先看一张非常经典的描述instance对象、类对象以及元类对象之间关系的图片。途中虚线代表isa指针,实线代表superClass指针。
  • instance对象的isa指针指向它的class对象
  • class对象的isa指针指向它的mata-class对象
  • mata-class对象的isa指向基类对象mata-class
  • class对象的superClass指向它的父类的class对象,基类的class对象的superClass指向nil
  • mata-class的superClass指向父类对象的mata-class,基类对象的mata-class的superClass指向基类对象自身(此处是比较特殊的地方)
  • 实例方法查找路线
    • 首先会通过isa指针到class对象中找
    • 如果找不到,通过superClass到父类的class对象中找
    • 如果还找不到,再到基类的class对象中查找
  • 类方法的查找路线
    • 首先会通过类对象的isa指针到mata-class中查找
    • 如果mata-class中找不到,通过superClass到父类的元类对象中查找
    • 如果在父类的元类对象中找不到,就到基类对象的元类对象中查找
    • 如果基类的元类对象中找不到,那么会到基类对象中查找。