OC类原理分析(上)

isa分析到元类

创建类LGPerson,初始化实例p,LGPerson *p = [LGPerson alloc];添加断点通过lldb动态调试探索isa的指向关系

<!-- main.m文件 -- >
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *p = [LGPerson alloc];
        NSLog(@"%@",p);
    }
    return 0;
}

<!-- lldb调试信息 -->
(lldb) x p
0x10078ec80: 65 83 00 00 01 80 1d 01 00 00 00 00 00 00 00 00  e...............
0x10078ec90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
// 格式化打印p实例对象的内存地址
(lldb) p/x p
(LGPerson *) $1 = 0x000000010078ec80
// 格式化打印0x000000010078ec80地址下的连续地址空间内存储的数据,拿到了p对象的首地址,也就是isa指针地址
(lldb) x/4gx 0x000000010078ec80
0x10078ec80: 0x011d800100008365 0x0000000000000000
0x10078ec90: 0x0000000000000000 0x0000000000000000
// 通过isa指针和ISA_MASK的与操作,解析了LGPerson类对象
(lldb) p/x 0x011d800100008365 & 0x00007ffffffffff8
(long) $2 = 0x0000000100008360
// 打印这个地址数据,得到了LGPerson
(lldb) po 0x0000000100008360
LGPerson

// 通过实例对象的isa指向类对象,我拿到了类对象内存地址0x0000000100008360,格式化输出类对象的内存地址
(lldb) x/4gx 0x0000000100008360
0x100008360: 0x0000000100008338 0x00007fff88994008
0x100008370: 0x00007fff2021baf0 0x0000802c00000000
// 将类对象的首地址(isa指针地址)0x0000000100008338和ISA_MASK做与操作,然后打印得到LGPerson
(lldb) po 0x0000000100008338 & 0x00007ffffffffff8
LGPerson

0x00000001000083600x0000000100008338明显是两个内存地址,但是输出的却是一个对象,这是为什么呢?猜想类和我们的对象一样可以无限开辟,也就是内存中不只有一个类?下面进行验证

分析类对象在内存中的个数
void lgTestClassNum(void){
    Class class1 = [LGPerson class];
    Class class2 = [LGPerson alloc].class;
    Class class3 = object_getClass([LGPerson alloc]);
    Class class4 = [LGPerson alloc].class;
    NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        lgTestClassNum();
    }
    return 0;
}

// 打印结果
0x100008360-
0x100008360-
0x100008360-
0x100008360
得出结论:

通过打印发现只有0x0000000100008360才是类对象class。而0x0000000100008338并不是类,那它是什么呢?

打开Products文件,把上面工程的可执行文件拖入MachOView中进行查看

image.png

发现了__OBJC_METACLASS_RO_,它就是元类对象MetaClass,是由系统帮我们生成的。至此我们明白了0x0000000100008338就是元类对象

isa走位图和继承链

LGPerson *p = [LGPerson alloc];添加断点通过lldb动态调试探索isa的指向关系

<!-- main.m文件 -- >
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *p = [LGPerson alloc];
        NSLog(@"%@",p);
    }
    return 0;
}

<!-- lldb调试信息 -- >
(lldb) x/4gx p
0x100536870: 0x011d800100008365 0x0000000000000000
0x100536880: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008365 & 0x00007ffffffffff8
(long) $1 = 0x0000000100008360
(lldb) po 0x0000000100008360
LGPerson

(lldb) x/4gx 0x0000000100008360
0x100008360: 0x0000000100008338 0x00007fff88994008
0x100008370: 0x00007fff2021baf0 0x0000802c00000000
(lldb) p/x 0x0000000100008338 & 0x00007ffffffffff8
(long) $3 = 0x0000000100008338
(lldb) po 0x0000000100008338
LGPerson

(lldb) x/4gx 0x0000000100008338
0x100008338: 0x00007fff88993fe0 0x00007fff88993fe0
0x100008348: 0x0000000100536890 0x0002e03500000003
(lldb) p/x 0x00007fff88993fe0 & 0x00007ffffffffff8
(long) $5 = 0x00007fff88993fe0
(lldb) po 0x00007fff88993fe0
NSObject

(lldb) x/4gx 0x00007fff88993fe0
0x7fff88993fe0: 0x00007fff88993fe0 0x00007fff88994008
0x7fff88993ff0: 0x00000001030147b0 0x0003e03100000007
(lldb) p/x 0x00007fff88993fe0 & 0x00007ffffffffff8
(long) $5 = 0x00007fff88993fe0
(lldb) po 0x00007fff88993fe0
NSObject
得出结论

通过上面打印结果得出isa走位图,对象p isa -> 类LGPerson isa -> 元类MetaClass isa -> 根元类NSObject isa -> 根元类NSObject

  • 对象的isa指向类Class
  • 类对象的isa指向元类MetaClass
  • 元类对象的isa指向根元类(rootMetaClass)
  • 根元类的isa指针指向自己
isa走位图
下面探索继承链
void lgTestNSObject(void){
    // NSObject实例对象
    NSObject *object1 = [NSObject alloc];
    // NSObject类
    Class class = object_getClass(object1);
    // NSObject元类
    Class metaClass = object_getClass(class);
    // NSObject根元类
    Class rootMetaClass = object_getClass(metaClass);
    // NSObject根根元类
    Class rootRootMetaClass = object_getClass(rootMetaClass);
    NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
    
    // LGPerson元类
    Class pMetaClass = object_getClass(LGPerson.class);
    Class psuperClass = class_getSuperclass(pMetaClass);
    NSLog(@"%@ - %p",psuperClass,psuperClass);

    // LGTeacher -> LGPerson -> NSObject
    // 元类也有一条继承链
    Class tMetaClass = object_getClass(LGTeacher.class);
    Class tsuperClass = class_getSuperclass(tMetaClass);
    NSLog(@"%@ - %p",tsuperClass,tsuperClass);

    // NSObject 根类特殊情况
    Class nsuperClass = class_getSuperclass(NSObject.class);
    NSLog(@"%@ - %p",nsuperClass,nsuperClass);
    // 根元类 -> NSObject
    Class rnsuperClass = class_getSuperclass(metaClass);
    NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        lgTestNSObject();
    }
    return 0;
}

// 打印结果
0x10066c440 实例对象
0x7fff88994008 类
0x7fff88993fe0 元类
0x7fff88993fe0 根元类
0x7fff88993fe0 根根元类
2021-07-13 21:16:21.309877+0800 002-isa分析[25200:2810935] NSObject - 0x7fff88993fe0
2021-07-13 21:16:21.309940+0800 002-isa分析[25200:2810935] LGPerson - 0x100008338
2021-07-13 21:16:21.309986+0800 002-isa分析[25200:2810935] (null) - 0x0
2021-07-13 21:16:21.310028+0800 002-isa分析[25200:2810935] NSObject - 0x7fff88994008
得出结论
  • NSOject对象的元类与根元类是一个
  • 元类间也存在着继承的关系,跟类是一样的
  • NSObject的父类是(null),地址为0x0,即NSObject没有父类
继承链

源码分析类的结构

上一篇探索对象本质中,提到两个结构体类型:objc_classobjc_object,在objc4源码中搜索objc_class的定义,源码中对其定义有两个版本

  • 旧版位于runtime.h中,已经被废除
#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;  //被废弃的
  • 新版位于objc-runtime-new.h这个是objc4-818最新优化的,我们后面类的结构分析也是基于新版来分析
struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // 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

    Class getSuperclass() const {
#if __has_feature(ptrauth_calls)
#   if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
        if (superclass == Nil)
            return Nil;

#if SUPERCLASS_SIGNING_TREAT_UNSIGNED_AS_NIL
        void *stripped = ptrauth_strip((void *)superclass, ISA_SIGNING_KEY);
        if ((void *)superclass == stripped) {
            void *resigned = ptrauth_sign_unauthenticated(stripped, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS));
            if ((void *)superclass != resigned)
                return Nil;
        }
#endif
            
        void *result = ptrauth_auth_data((void *)superclass, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS));
        return (Class)result;
...
通过上述的源码查看有以下几点说明:
  • 结构体类型objc_class继承自objc_object类型,其中objc_object也是一个结构体,且有一个isa属性,所以objc_class也拥有了isa属性
Class ISA:8字节,CLass superclass:8字节,cache_t cache:16字节
struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
//省略跟内存大小无关的代码
}
  • 探索ISA、superclass、cache是为了通过内存偏移找到bits,
class_data_bits_t bits:
struct class_data_bits_t {
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;
private:
    bool getBit(uintptr_t bit) const
    {
        return bits & bit;
    }

    //此处省略了部分代码

public:

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    
    //此处省略了部分代码
  • 通过获取class_rw_t* 类型的data(),将会拿到这个类的methods、properties、protocols、deepCopy、ro等等信息
class_rw_t
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

//省略了部分代码
    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;


    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }

    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 *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }
};
  • 通过解析class_rw_t这个结构体可以拿到类的信息,比如这个类的methods、properties、protocols、deepCopy、ro等等信息

指针和内存平移

普通指针
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10; //
        int b = 10; //
        LGNSLog(@"%d -- %p",a,&a);
        LGNSLog(@"%d -- %p",b,&b);
    }
    return 0;
}

// 打印结果
KC打印: 10 -- 0x7ffeefbff33c
KC打印: 10 -- 0x7ffeefbff338
得出结论
  • a、b都指向10,但是a、b的地址不一样,这是一种拷贝,属于值拷贝,也称为深拷贝
  • a,b的地址之间相差 4 个字节,这取决于a、b的类型
对象指针
// 对象 -
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [LGPerson alloc];
LGNSLog(@"%@ -- %p",p1,&p1);
LGNSLog(@"%@ -- %p",p2,&p2);

// 打印结果
KC打印: <LGPerson: 0x101855fe0> -- 0x7ffeefbff330
KC打印: <LGPerson: 0x101856030> -- 0x7ffeefbff328
得出结论
  • p1、p2 是指针,p1 是 指向[LGPerson alloc]创建的空间地址,即内存地址,p2 同理
  • &p1、&p2是 指向 p1、p2对象指针的地址,这个指针就是二级指针
数组指针
int c[4] = {1,2,3,4};
int *d   = c;
NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
NSLog(@"%p - %p - %p",d,d+1,d+2);

for (int i = 0; i<4; i++) {
      int value =  *(d+i);
      NSLog(@"%d",value);
}

// 打印结果
2021-07-14 19:43:11.814671+0800 002-内存偏移[28063:3135116] 0x7ffeefbff350 - 0x7ffeefbff350 - 0x7ffeefbff354
2021-07-14 19:43:11.815728+0800 002-内存偏移[28063:3135116] 0x7ffeefbff350 - 0x7ffeefbff354 - 0x7ffeefbff358
2021-07-14 19:47:38.555196+0800 002-内存偏移[28063:3135116] 1
2021-07-14 19:47:38.555581+0800 002-内存偏移[28063:3135116] 2
2021-07-14 19:47:38.555991+0800 002-内存偏移[28063:3135116] 3
2021-07-14 19:47:38.556058+0800 002-内存偏移[28063:3135116] 4
得出结论
  • &c 和 &c[0] 都是取 首地址,即数组名等于首地址
  • &c 与 &c[1] 相差4个字节,地址之间相差的字节数,主要取决于存储的数据类型
  • 可以通过 首地址+偏移量取出数组中的其他元素,其中偏移量是数组的下标,内存中首地址实际移动的字节数 等于 偏移量 * 数据类型字节数

类的内存结构内存计算

探索类的内存结构
  • 打开objc4源码LGPerson *p1 = [[LGPerson alloc] init];添加断点,运行工程执行至断点处
#import <Foundation/Foundation.h>
#import "LGPerson.h"
#import "LGTeacher.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *p1 = [[LGPerson alloc] init];
    }
    return 0;
}

根据前文提及的objc_class 的新版定义(objc4-818版本)有以下几个属性
struct objc_class : objc_object {
    // Class ISA; //8字节
    Class superclass; //Class 类型 8字节
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
    //....方法部分省略,未贴出
}

// lldb调试
// 0x00000001000083a8 对应isa,0x000000010036a140对应superclass,下面打印成功验证
// 0x000000010076ea40 对应cache,0x0002802800000003对应bits
(lldb) x/4gx LGPerson.class 
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x000000010076ea40 0x0002802800000003
(lldb) po 0x000000010036a140
NSObject
// 成功验证LGPerson 的父类superclass 为NSObject
(lldb) p/x NSObject.class
(Class) $2 = 0x000000010036a140 NSObject
  • isa属性:继承自objc_object的isa,占 8字节
  • superclass属性:Class类型,Class是由objc_object定义的,是一个指针,占8字节
  • cache属性:简单从类型class_data_bits_t目前无法得知,而class_data_bits_t是一个结构体类型,结构体的内存大小需要根据内部的属性来确定,而结构体指针才是8字节
  • bits属性:只有首地址经过上面3个属性的内存大小总和的平移,才能获取到bits
计算 cache 类的内存大小

进入cache类cache_t的定义(只贴出了结构体中非static修饰的属性,主要是因为static类型的属性 不存在结构体的内存中,另外方法函数也不在结构体内存中)有如下几个属性

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;  //是指针,占8字节
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask; // 占4字节
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
  • _bucketsAndMaybeMask是uintptr_t类型,指针占8字节
  • _maybeMask是mask_t类型, typedef uint32_t mask_t,uint32_t占4字节
  • _flags是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
  • _occupied是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节

最后计算出cache类的内存大小 = 8 + 4 + 2 + 2 = 16字节
总结:Class ISA:8字节,CLass superclass:8字节,cache_t cache:16字节

获取bits
// 继续上面lldb调试,获取bits
(lldb) x/4gx LGPerson.class 
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x000000010076ea40 0x0002802800000003
(lldb) po 0x000000010036a140
NSObject
// 成功验证LGPerson 的父类superclass 为NSObject
(lldb) p/x NSObject.class
(Class) $2 = 0x000000010036a140 NSObject
(lldb) po sizeof(LGPerson.class)
8
// LGPerson的内存首地址 添加 isa superclass cache 共32字节内存偏移
// 将首地址平移32字节,获取bits地址
(lldb) p/x 0x100008380+0x20
(long) $4 = 0x00000001000083a0
(lldb) p (class_data_bits_t *)0x00000001000083a0
(class_data_bits_t *) $5 = 0x00000001000083a0
// 通过bits地址获取bits数据
(lldb) p $5->data()
(class_rw_t *) $6 = 0x000000010076ea00
// 打印bits中的数据信息
(lldb) p *$6 
(class_rw_t) $7 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000344
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
获取类的首地址有两种方式
  • 通过p/x LGPerson.class直接获取首地址
  • 通过x/4gx LGPerson.class打印内存信息获取
其中的data()获取数据,是由objc_class提供的方法
class_rw_t *data() const {
     return bits.data();
}
  • 从$6指针的打印结果中可以看出bits中存储的信息,其类型是class_rw_t,也是一个结构体类型。但我们还是没有看到属性列表、方法列表等,需要继续往下探索
探索 属性列表,即property_list

通过查看class_rw_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 *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }

在获取bits并打印bits信息的基础上,通过class_rw_t提供的方法,继续探索 bits中的属性列表,以下是lldb 探索的过程图示

(lldb) p $5->data()
(class_rw_t *) $6 = 0x000000010076ea00
(lldb) p *$6 
(class_rw_t) $7 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000344
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
(lldb) p $7.properties()
(const property_array_t) $8 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008260
      }
      arrayAndFlag = 4295000672
    }
  }
}
(lldb) p $8.list 
(const RawPtr<property_list_t>) $9 = {
  ptr = 0x0000000100008260
}
(lldb) p $9.ptr
(property_list_t *const) $10 = 0x0000000100008260
(lldb) p *$10 
(property_list_t) $11 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $11.get(0) 
(property_t) $12 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
  • p $7.properties()命令中的propertoes方法是由class_rw_t提供的,方法中返回的实际类型为property_array_t
  • 由于list的类型是property_list_t,是一个指针,所以通过p *$10获取内存中的信息,同时也证明bits中存储了property_list,即属性列表
  • p $11.get(0),想要获取LGPerson中的成员变量name
遗留问题的探索

问题一 探索成员变量的存储?

  1. 属性与成员变量的区别就是有没有set、get方法,如果有,则是属性,如果没有,则是成员变量
  2. 由上面的属性列表分析可得出property_list中只有属性,没有成员变量,那么问题来了,成员变量存储在哪里?为什么会有这种情况?
  3. 通过查看objc_classbits属性中存储数据的类class_rw_t的定义发现,除了methods、properties、protocols方法,还有一个ro方法,其返回类型是class_ro_t,通过查看其定义,发现其中有一个ivars属性,我们可以做如下猜测:是否成员变量就存储在这个ivar_list_t类型的ivars属性中呢?

通过lldb调试可以看出,获取的ivars属性,其中的count 为2,通过打印发现 成员列表中除了有hobby,还有name,所以可以得出以下一些结论:

  • 通过{}定义的成员变量,会存储在类的bits属性中,通过bits --> data() -->ro() --> ivars获取成员变量列表,除了包括成员变量,还包括属性定义的成员变量
  • 通过@property定义的属性,也会存储在bits属性中,通过bits --> data() --> properties() --> list获取属性列表,其中只包含属性

问题二 探索类方法的存储?

  1. lldb调试可得出methods list中只有实例方法,没有类方法,那么问题来了,类方法存储在哪里?为什么会有这种情况?下面我们来仔细分析下
  2. 前面我们曾提及了元类,类对象的isa指向就是元类,元类是用来存储类的相关信息,所以我们猜测:是否类方法存储在元类的bits中呢?可以通过lldb命令来验证我们的猜测。
  3. 通过lldb对元类方法列表的打印结果,我们可以知道,我们的猜测是正确的,可以得出以下结论:
  • 类的实例方法存储在类的bits属性中,通过bits --> methods() --> list获取实例方法列表,例如LGPerson类的实例方法sayHello 就存储在 LGPerson类的bits属性中,类中的方法列表除了包括实例方法,还包括属性的set方法get方法

  • 类的类方法存储在元类的bits属性中,通过元类bits --> methods() --> list获取类方法列表,例如LGPerson中的类方法sayBye就存储在LGPerson类的元类(名称也是LGPerson)的bits属性中

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 网络中有个关于类的关系的图 图中虚线部分是isa的的继承关系证明如下图 有关lldb指令的说明: p/x 以16进...
    东旭39阅读 365评论 0 1
  • 一、概要 先要区分两个概念:1、类:一种结构体,所有对象公用一个类结构。2、对象:通过类创建出来,每个对象有独立的...
    Johnny_Wu阅读 481评论 0 0
  • OC是一门面向对象语言,面向对象离不开对象,类,继承,类方法,实例方法,属性,实例变量,对于习惯了面向对象的同学来...
    闭家锁阅读 891评论 3 3
  • 类Class ,也可以称为类对象,在编译时会转成objc_class, objc_class继承自objc_obj...
    爱你因为泰勒阅读 472评论 0 1
  • 我们之前的篇幅介绍了对象,也知道对象是一个类的实例。那么它的结构又是怎么样的。为了更直接的观察。我们做好充足的前戏...
    这货不是文熙阅读 525评论 0 3