经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
iOS底层原理(一)Objective-C的本质
来源:cnblogs  作者:FunkyRay  时间:2021/3/8 11:49:06  对本文有异议

我们平时编写的Objective-C代码,底层实现其实都是C\C++代码,所以Objective-C的面向对象都是基于C\C++的数据结构实现的

OC对象的本质

Objective-C的对象、类主要是基于C\C++的结构体实现的

通过下面的命令可以将OC代码转换为C++代码来查看

  1. clang -rewrite-objc OC源文件 -o 输出的CPP文件

由于Clang会根据不同平台转换的C++代码有所差异,所以针对iOS平台用下面的命令来转换

  1. // 意为:通过Xcode运行iPhone平台arm64架构,重写OC文件到C++文件
  2. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
  3. 如果需要链接其他框架,使用-framework参数。比如-framework UIKit

凡是继承自NSObject的对象,都会自带一个类型是Class的isa的成员变量,将其转成C++,就可以看到NSObject本质上是一个叫做NSObject_IMPL的结构体,其成员变量isa本质上也是一个指向objc_class结构体的指针

OC对象的内存布局

一个OC对象在内存中的布局是这样的,系统会在堆中开辟一块内存空间存放该对象,这块空间里还包含成员变量和isa指针。然后栈里的局部变量指向这块存储空间的地址

OC对象的内存占用大小

系统会给NSObject对象自动分配16个字节的内存,而NSObject对象实际只占用了8个字节的内存。这8个字节的大小就是成员变量isa指针的大小,多余的8个字节是系统为了内存对齐而分配的

  1. // 获取实例对象的内存大小,实际是获取对象成员变量的内存大小
  2. #import <objc/runtime.h>class_getInstanceSize([NSObject class]);// 获取实例对象的内存大小,实际是获取系统真正分配了多少内存#import <malloc/malloc.h>malloc_size((__bridge const void *)obj);
  3. NSObject *obj = [[NSObject alloc] init];
  4. // 获得NSObject实例对象的成员变量所占用的大小 >> 8
  5. NSLog(@"%zd", class_getInstanceSize([NSObject class]));
  6. // 获得obj指针所指向内存的大小 >> 16
  7. NSLog(@"%zd", malloc_size((__bridge const void *)obj));

验证方法

1.源码验证

下载苹果开源框架 https://opensource.apple.com/tarballs/objc4/

选择最大版本下载

在头文件objc-runtime-new.h中找到对应代码

  1. inline size_t instanceSize(size_t extraBytes) const {
  2. if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
  3. return cache.fastInstanceSize(extraBytes);
  4. }
  5. size_t size = alignedInstanceSize() + extraBytes;
  6. // CF requires all objects be at least 16 bytes.
  7. // 只要小于16个字节都会被赋值16
  8. if (size < 16) size = 16;
  9. return size;
  10. }
2.内存验证

运行Xcode,选择Debug->Debug Workflow -> View Memory查看内存数据

输入obj的内存地址可以看到只有前8个字节有值,但已经分配了16个字节的内存空间

3.LLDB打印验证

利用LLDBmemory read读取对象的内存地址,可以看到也是分配的16个字节

OC对象的分类

OC对象主要分为三种

  • instance对象(实例对象)
  • class对象(类对象)
  • meta-class对象(元类对象)

instance对象

instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象

  1. // object1、object2是NSObject的instance对象(实例对象)
  2. NSObject *object1 = [[NSObject alloc] init];
  3. NSObject *object2 = [[NSObject alloc] init];
  4. // 通过打印可以看出,它们是不同的两个对象,分别占据着两块不同的内存
  5. NSLog(@"instance - %p %p",
  6. object1,
  7. object2);

instance对象在内存中存储的信息

  • isa指针
  • 其他成员变量的具体值

class对象

每个类在内存中有且只有一个class对象

  1. Class objectClass1 = [object1 class];
  2. Class objectClass2 = [object2 class];
  3. Class objectClass3 = object_getClass(object1);
  4. Class objectClass4 = object_getClass(object2);
  5. Class objectClass5 = [NSObject class];
  6. // 通过打印可以看出,上面几个方法返回的都是同一个类对象,内存地址都一样
  7. NSLog(@"class - %p %p %p %p %p %d",
  8. objectClass1,
  9. objectClass2,
  10. objectClass3,
  11. objectClass4,
  12. objectClass5);

注意:class方法返回的一直是类对象,所以哪怕这样写还是会返回类对象

  1. Class objectMetaClass2 = [[[NSObject class] class] class];

class对象在内存中存储的信息

  • isa指针- superclass指针- 类的属性信息(@property)、类的对象方法信息(instance method)- 类的协议信息(protocol)、类的成员变量信息(ivar)
  • ....

meta-class对象

objectMetaClass是NSObject的meta-class对象(元类对象),每个类在内存中有且只有一个meta-class对象

  1. Class objectMetaClass = object_getClass(objectClass5);

meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括

  • isa指针- superclass指针- 类的类方法信息(class method)
  • ....

使用class_isMetaClass(Class _Nullable cls)来查看Class是否为meta-class的方法

  1. NSLog(@"objectMetaClass - %p %d", objectMetaClass, class_isMetaClass(objectMetaClass));

isa和superclass

每个类的实例对象、类对象、元类对象都有一个isa指针

  • instance的isa指向class - 当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用

  • class的isa指向meta-class - 当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用

  • meta-class的isa指向基类的meta-class每个类的类对象、元类对象都有一个superclass指针

  • class的superclass指针指向父类的class

    • 如果没有父类,superclass指针为nil
  • meta-class的superclass指向父类的meta-class

    • 基类的meta-class的superclass指向基类的class

instance调用对象方法的轨迹

  • isa找到class,方法不存在,就通过superclass找父类

class调用类方法的轨迹

  • isa找meta-class,方法不存在,就通过superclass找父类

Class类型的底层结构

我们可以从源码objc-runtime-new.h文件中找到Class类型的本质是结构体objc_class类型,里面包含了superclass指针、cache方法缓存,以及获取具体的类信息的class_data_bits_t类型的属性表

  1. struct objc_class : objc_object {
  2. // Class ISA;
  3. // superclass指针
  4. Class superclass;
  5. // 方法缓存
  6. cache_t cache; // formerly cache pointer and vtable
  7. // 用于获取具体的类信息
  8. class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
  9. // rw意为readwrite,可读可写,t意为table,表格
  10. class_rw_t *data() const {
  11. return bits.data();
  12. }
  13. void setData(class_rw_t *newData) {
  14. bits.setData(newData);
  15. }
  16. }

继承的父类objc_object里有一个isa指针

  1. // 继承的父类结构体里面有一个isa指针
  2. struct objc_object {
  3. private:
  4. isa_t isa;
  5. public:
  6. Class ISA(bool authenticated = false);
  7. Class rawISA();
  8. Class getIsa();
  9. uintptr_t isaBits() const;
  10. ....
  11. };

分析class_data_bits_t这个类型里面的结构可以看出,bits & FAST_DATA_MASK就可以得到class_rw_t类型的表的内存

  1. // class_data_bits_t结构体里的具体分析
  2. struct class_data_bits_t {
  3. friend objc_class;
  4. class_rw_t* data() const {
  5. return (class_rw_t *)(bits & FAST_DATA_MASK);
  6. }
  7. void setData(class_rw_t *newData)
  8. {
  9. ASSERT(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
  10. uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
  11. atomic_thread_fence(memory_order_release);
  12. bits = newBits;
  13. }
  14. }

分析class_rw_t这个类型里面的结构可以看出,里面有方法列表、属性列表、协议列表,以及class_ro_t类型的属性表

  1. // class_rw_t结构体里的具体分析
  2. struct class_rw_t {
  3. // Be warned that Symbolication knows the layout of this structure.
  4. uint32_t flags;
  5. uint16_t witness;
  6. #if SUPPORT_INDEXED_ISA
  7. uint16_t index;
  8. #endif
  9. // ro意为readonly,只读
  10. const class_ro_t *ro() const {
  11. auto v = get_ro_or_rwe();
  12. if (slowpath(v.is<class_rw_ext_t *>())) {
  13. return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
  14. }
  15. return v.get<const class_ro_t *>(&ro_or_rw_ext);
  16. }
  17. void set_ro(const class_ro_t *ro) {
  18. auto v = get_ro_or_rwe();
  19. if (v.is<class_rw_ext_t *>()) {
  20. v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
  21. } else {
  22. set_ro_or_rwe(ro);
  23. }
  24. }
  25. // 方法列表
  26. const method_array_t methods() const {
  27. auto v = get_ro_or_rwe();
  28. if (v.is<class_rw_ext_t *>()) {
  29. return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
  30. } else {
  31. return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
  32. }
  33. }
  34. // 属性列表
  35. const property_array_t properties() const {
  36. auto v = get_ro_or_rwe();
  37. if (v.is<class_rw_ext_t *>()) {
  38. return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
  39. } else {
  40. return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
  41. }
  42. }
  43. // 协议列表
  44. const protocol_array_t protocols() const {
  45. auto v = get_ro_or_rwe();
  46. if (v.is<class_rw_ext_t *>()) {
  47. return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
  48. } else {
  49. return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
  50. }
  51. }

分析class_ro_t这个类型的结构可以看出,instanceSize意为实例对象所占用的内存空间,name存储的是类名,ivars存储的成员变量列表

  1. struct class_ro_t {
  2. uint32_t flags;
  3. uint32_t instanceStart;
  4. // 实例对象占用内存大小空间
  5. uint32_t instanceSize;
  6. #ifdef __LP64__
  7. uint32_t reserved;
  8. #endif
  9. // 类名
  10. explicit_atomic<const char *> name;
  11. void *baseMethodList;
  12. protocol_list_t * baseProtocols;
  13. // 成员变量列表
  14. const ivar_list_t * ivars;
  15. }

总结:

上述分析可以简单用一张图来概述

isa指针

在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址

从arm64架构开始,对isa进行了优化,变成了一个isa_t类型的共用体(union)结构,共用体就是多种数据结构都共用同一块存储空间,里面包含了bits、cls、ISA_BITFIELD结构体以及其他的函数或变量,它们都是共用同一块内存空间的

  1. union isa_t {
  2. isa_t() { }
  3. isa_t(uintptr_t value) : bits(value) { }
  4. uintptr_t bits;
  5. private:
  6. Class cls;
  7. public:
  8. #if defined(ISA_BITFIELD)
  9. struct {
  10. ISA_BITFIELD; // 现在的版本用一个宏来定义
  11. };
  12. }

isa.h中查看ISA_BITFIELD这个结构体,里面的每一个值都是位域。不同架构下的掩码和位域都是不一样的,我们只以arm64架构的来分析

  1. // 在isa.h中查看ISA_BITFIELD
  2. // 每个变量后面标的数字就是位域
  3. // 类似ISA_MASK这种宏的都叫掩码
  4. # if __arm64__
  5. # if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
  6. # define ISA_MASK 0x007ffffffffffff8ULL
  7. # define ISA_MAGIC_MASK 0x0000000000000001ULL
  8. # define ISA_MAGIC_VALUE 0x0000000000000001ULL
  9. # define ISA_HAS_CXX_DTOR_BIT 0
  10. # define ISA_BITFIELD uintptr_t nonpointer : 1; uintptr_t has_assoc : 1; uintptr_t weakly_referenced : 1; uintptr_t shiftcls_and_sig : 52; uintptr_t has_sidetable_rc : 1; uintptr_t extra_rc : 8
  11. # define RC_ONE (1ULL<<56)
  12. # define RC_HALF (1ULL<<7)
  13. # else
  14. # define ISA_MASK 0x0000000ffffffff8ULL
  15. # define ISA_MAGIC_MASK 0x000003f000000001ULL
  16. # define ISA_MAGIC_VALUE 0x000001a000000001ULL
  17. # define ISA_HAS_CXX_DTOR_BIT 1
  18. # define ISA_BITFIELD uintptr_t nonpointer : 1; uintptr_t has_assoc : 1; uintptr_t has_cxx_dtor : 1; uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ uintptr_t magic : 6; uintptr_t weakly_referenced : 1; uintptr_t unused : 1; uintptr_t has_sidetable_rc : 1; uintptr_t extra_rc : 19
  19. # define RC_ONE (1ULL<<45)
  20. # define RC_HALF (1ULL<<18)
  21. # endif
  22. # elif __x86_64__
  23. ....
  24. # else
  25. # error unknown architecture for packed isa
  26. # endif
  27. // SUPPORT_PACKED_ISA
  28. #endif

每一位位域对应的二进制位的排序都是从右向左的,下面是对应的每个位域的含义

上述代码里类似ISA_MASK这样的值都是掩码,以掩码ISA_MASK为例,转成二进制发现对应是1的部分都是用来取值的

而且一共有33位的1,正好对应着shiftcls这个位域的位数,shiftcls又是存储着类对象和元类对象的地址值,那么就能说明在arm64架构之后的isa里存储着更多的信息,需要&ISA_MASK进行一次位运算之后才能将类对象和元类对象的真实地址值取出来

位运算的运用实例

利用共用体和位运算来优化属性的内存空间

创建Person.h文件,然后手动实现setter和getter

  1. @interface Person : NSObject
  2. //@property (assign, nonatomic, getter=isTall) BOOL tall;
  3. //@property (assign, nonatomic, getter=isRich) BOOL rich;
  4. //@property (assign, nonatomic, getter=isHansome) BOOL handsome;
  5. - (void)setTall:(BOOL)tall;
  6. - (void)setRich:(BOOL)rich;
  7. - (void)setHandsome:(BOOL)handsome;
  8. - (BOOL)isTall;
  9. - (BOOL)isRich;
  10. - (BOOL)isHandsome;
  11. @end

利用共用体的本质,在Person.m的类扩展中创建一个私有的共用体类型的变量

  1. @interface Person()
  2. {
  3. union {
  4. char bits;
  5. struct {
  6. char tall : 1;
  7. char rich : 1;
  8. char handsome : 1;
  9. };
  10. } _tallRichHandsome;
  11. }
  12. @end

该共用体一共只占有1个字节,都是根据char bits来分配大小的。而sturct结构体是对这1个字节大小的占用做说明的,里面每一个的1就是位域,指明占用了1个二进制位,虽然是char类型的,但都是根据位域后面给定的值来确定实际占用大小的。tall、rich、handsome三个变量都是占用着同一个内存区域,也就是值都会存储在一个字节里,这就是共用体的本质,这么做主要是为了做优化,节省内存空间。而且写不写这个结构体都是根据char bits来确定了分配空间大小的,没有影响的

由于上述结构体里的三个变量占用一个字节大小就足够了,那么我们对应每一个变量用一个二进制位来存取值。我们先分别设定三个掩码对应三个值

  1. // 0x0000 0001
  2. #define TallMask (1<<0)
  3. // 0x0000 0010
  4. #define RichMask (1<<1)
  5. // 0x0000 0100
  6. #define HandsomeMask (1<<2)

setter的实现如下,如果参数为YES,那么将掩码进行按位或运算;如果参数为NO,那么先将掩码取反,然后再进行按位与运算

  1. @implementation Person
  2. - (void)setTall:(BOOL)tall
  3. {
  4. if (tall) {
  5. _tallRichHandsome.bits |= TallMask;
  6. } else {
  7. _tallRichHandsome.bits &= ~TallMask;
  8. }
  9. }
  10. - (void)setRich:(BOOL)rich
  11. {
  12. if (rich) {
  13. _tallRichHandsome.bits |= RichMask;
  14. } else {
  15. _tallRichHandsome.bits &= ~RichMask;
  16. }
  17. }
  18. - (void)setHandsome:(BOOL)handsome
  19. {
  20. if (handsome) {
  21. _tallRichHandsome.bits |= HandsomeMask;
  22. } else {
  23. _tallRichHandsome.bits &= ~HandsomeMask;
  24. }
  25. }
  26. @end

getter的实现如下,先将掩码进行按位与运算,然后再取反两次;因为返回值是BOOL类型,那么不是0就是1,所以按位与运算后的值只要不是0的都是有值的,那么取反两次肯定就得到的不是0就是1了

  1. - (BOOL)isTall
  2. {
  3. return !!(_tallRichHandsome.bits & TallMask);
  4. }
  5. - (BOOL)isRich
  6. {
  7. return !!(_tallRichHandsome.bits & RichMask);
  8. }
  9. - (BOOL)isHandsome
  10. {
  11. return !!(_tallRichHandsome.bits & HandsomeMask);
  12. }

如此一来,我们就做到了优化了属性的内存空间,而且也实现了setter和getter

利用位运算进行位移枚举的实现

创建一个位移枚举,每一个值都对应一个二进制位

  1. typedef enum {
  2. OptionsNone = 0, // 0b0000
  3. OptionsOne = 1<<0, // 0b0001
  4. OptionsTwo = 1<<1, // 0b0010
  5. OptionsThree = 1<<2, // 0b0100
  6. OptionsFour = 1<<3 // 0b1000
  7. } Options;

和对应的枚举值进行按位与运算,就能得到是否存在该枚举值

  1. @implementation ViewController
  2. - (void)setOptions:(Options)options
  3. {
  4. if (options & OptionsOne) {
  5. NSLog(@"包含了OptionsOne");
  6. }
  7. if (options & OptionsTwo) {
  8. NSLog(@"包含了OptionsTwo");
  9. }
  10. if (options & OptionsThree) {
  11. NSLog(@"包含了OptionsThree");
  12. }
  13. if (options & OptionsFour) {
  14. NSLog(@"包含了OptionsFour");
  15. }
  16. }
  17. - (void)viewDidLoad {
  18. [super viewDidLoad];
  19. [self setOptions: OptionsOne | OptionsFour];
  20. }
  21. @end

面试题

1.一个NSObject对象占用多少内存?

系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)

2.看下面代码,分别描述Person和Student对应的内存占用

  1. @interface Person : NSObject
  2. {
  3. int _height;
  4. }
  5. @end
  6. @interface Student : Person
  7. {
  8. int _weight;
  9. }
  10. @end
  11. Person *p = [[Person alloc] init];
  12. NSLog(@"%zd %zd", class_getInstanceSize([Person class]), // 16
  13. malloc_size((__bridge const void *)(p))); // 16
  14. Student *s = [[Student alloc] init];
  15. NSLog(@"%zd %zd", class_getInstanceSize([Student class]), // 16
  16. malloc_size((__bridge const void *)(s))); // 16

默认在64bit处理器下,由于Person继承自NSObject,所以根据内存对齐,系统给NSObject对象分配了16个字节存放isa指针。Person的成员变量height由于是Int类型,占用4个字节。因为isa指针实际只占用了8个字节,还有多余的8个字节空间,所以无需再多分配内存,那么Person的实际占用和系统分配都是16个字节(内存对齐一般以成员变量占比最大的倍数来增加:isa指针占用8个字节,占用最大,所以是8的倍数)

Student继承自Person,isa指针和成员变量height实际占用了12个字节,还有多余的4个字节。而成员变量weight正好又占用4个字节,那么也不用再分配更多的内存空间,Stuent对象的实际占用和系统分配也都是16个字节

3.看下面代码,描述Person的内存占用

  1. @interface Person : NSObject
  2. {
  3. int _age;
  4. int _height;
  5. int _no;
  6. }
  7. @end
  8. Person *p = [[Person alloc] init];
  9. NSLog(@"%zd %zd", class_getInstanceSize([Person class]), // 24
  10. malloc_size((__bridge const void *)(p))); // 32

默认在64bit处理器下,由于Person继承自NSObject,里面的isa指针实际占用了8个字节,而Person里面有三个Int类型的成员变量,实际占用是12个字节,由于结构体的内存对齐原则,系统要分配24个字节(也就是3倍的isa指针的8个字节)才能容纳所有的成员变量,所以Person对象的实际占用为24个字节。

但系统本身都会以16的倍数来进行内存分配,所以要分配大于实际占用字节的两倍才可以,所以Person对象的系统分配为分配32个字节

4.看下面代码,简述Student的对象方法调用轨迹,然后分别注释掉 + (void)test 方法和 - (void)test 方法后会怎样调用

  1. @interface NSObject (Test)
  2. + (void)test;
  3. - (void)test;
  4. @end
  5. @implementation NSObject (Test)
  6. + (void)test
  7. {
  8. NSLog(@"+[NSObject test] - %p", self);
  9. }
  10. - (void)test
  11. {
  12. NSLog(@"-[NSObject test] - %p", self);
  13. }
  14. @interface Person : NSObject
  15. + (void)test;
  16. - (void)test;
  17. @end
  18. @interface Student : Person
  19. + (void)test;
  20. - (void)test;
  21. @end
  22. Student *s = [[Student alloc] init];
  23. [s test];
  24. [Student test];

1.[s test] 这个方法调用,首先Student的实例对象会根据isa指针去Student的类对象里面查找- (void)test方法,如果找到了则调用该方法。如果没找到,那么就根据superclass指针去父类Person的类对象里查找,如果找到了则调用Person的- (void)test方法。如果没找到,那么就根据superclass指针去基类NSObject的类对象里查找,如果找到了则调用NSObject的- (void)test方法。

如果注释掉了NSObject的- (void)test方法,那么Student实例对象在基类NSObject的类对象里也找不到该方法,由于NSObject类对象的superclass指针指向nil,那么就会crash

2.[Student test] 这个方法调用,首先Student的类对象会根据isa指针去Student的元类对象里查找+ (void)test方法,如果找到了则调用该方法。如果没找到,那么就根据superclass指针去父类Person的元类对象里查找,如果找到了则调用Person的+ (void)test方法。如果没找到,那么就根据superclass指针去基类NSObject的元类对象里查找,如果找到了则调用NSObject的+ (void)test方法。

如果注释掉了NSObject的+ (void)test方法,那么Student的类对象在基类NSObject的元类对象里也找不到该方法,由于NSObject元类对象的superclass指针指向NSObject的类对象,所以就会调用NSObject类对象的- (void)test方法。

如果NSObject的两个方法都注释掉了,那么由于上一步的逻辑会去NSObject类对象里调用- (void)test方法,该方法也找不到,那么NSObject类对象的superclass指针是指向nil的,最后还是会crash

iOS的消息机制本质就是消息调用,所以不会真的区分类方法和对象方法,都是根据方法名进行查找

5.isMemberOfClass、isKindOfClass、isSubclassOfClass的区别,并说下原理

我们先通过一段代码打印可以得知

  1. Person *person = [[Person alloc] init]; // Person对象
  2. NSObject *obj = [[NSObject alloc] init]; // NSObject对象
  3. Class person_class = [person class]; // Person类对象
  4. Class obj_class = [obj class]; // NSObject类对象
  5. Class person_meta_class = object_getClass(person_class); // Person元类对象
  6. Class obj_meta_class = object_getClass(obj_class); // NSObject元类对象
  7. Class person_meta_meta_class = object_getClass(person_meta_class); // NSObject元类对象
  8. Class obj_meta_meta_class = object_getClass(obj_meta_class); // NSObject元类对象
  9. // Person对象, NSObject对象, Person类对象,NSObject类对象
  10. NSLog(@"%@, %@, %@, %@", person, obj, person_class, obj_class);
  11. // Person元类对象, NSObject元类对象, NSObject元类对象,NSObject元类对象
  12. NSLog(@"%@, %@, %@, %@", person_meta_class, obj_meta_class, person_meta_meta_class, obj_meta_meta_class);

isMemberOfClass

我们在objc4源码的NSObject.mm里可以看到,isMemberOfClass的类方法会拿到isa指针所指的对象和传进来的类型做比较;对象方法会拿当前类对象来做比较

  1. + (BOOL)isMemberOfClass:(Class)cls {
  2. return self->ISA() == cls;
  3. }
  4. - (BOOL)isMemberOfClass:(Class)cls {
  5. return [self class] == cls;
  6. }

我们可以通过一段代码打印来分析比较

  1. // Person类对象, Person类对象
  2. NSLog(@"%d", [person isMemberOfClass:person_class]); // 1
  3. // Person类对象, NSObject类对象
  4. NSLog(@"%d", [person isMemberOfClass:obj_class]); // 0
  5. // NSObject类对象, NSObject类对象
  6. NSLog(@"%d", [obj isMemberOfClass:obj_class]); // 1
  7. // Person元类对象, Person类对象
  8. NSLog(@"%d", [person_class isMemberOfClass:person_class]); // 0
  9. // Person元类对象, NSObject类对象
  10. NSLog(@"%d", [person_class isMemberOfClass:obj_class]); // 0
  11. // NSObject元类对象, NSObject类对象
  12. NSLog(@"%d", [obj_class isMemberOfClass:obj_class]); // 0
  13. // Person元类对象, Person元类对象
  14. NSLog(@"%d", [person_class isMemberOfClass:person_meta_class]); // 1
  15. // Person元类对象, NSObject元类对象
  16. NSLog(@"%d", [person_class isMemberOfClass:obj_meta_class]); // 0
  17. // NSObject元类对象, NSObject元类对象
  18. NSLog(@"%d", [obj_class isMemberOfClass:obj_meta_class]); // 1
  19. // 所有类型的元类对象的isa指针都指向NSObject的元类对象,包括NSObject的元类对象自己
  20. // NSObject元类对象, Person元类对象
  21. NSLog(@"%d", [person_meta_class isMemberOfClass:person_meta_class]); // 0
  22. // NSObject元类对象, NSObject元类对象
  23. NSLog(@"%d", [person_meta_class isMemberOfClass:obj_meta_class]); // 1
  24. // NSObject元类对象, NSObject元类对象
  25. NSLog(@"%d", [obj_meta_class isMemberOfClass:obj_meta_class]); // 1

isKindOfClass

isKindOfClass的类方法会拿到isa指针所指向的对象以及该对象的superclass指针所指向的对象和传进来的类型做比较;对象方法会拿当前类对象以及该对象的superclass指针所指向的对象来做比较

  1. + (BOOL)isKindOfClass:(Class)cls {
  2. for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
  3. if (tcls == cls) return YES;
  4. }
  5. return NO;
  6. }
  7. - (BOOL)isKindOfClass:(Class)cls {
  8. for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
  9. if (tcls == cls) return YES;
  10. }
  11. return NO;
  12. }

我们可以通过一段代码打印来分析比较

  1. // Person类对象, Person类对象
  2. NSLog(@"%d", [person isKindOfClass:person_class]); // 1
  3. // Person类对象, NSObject类对象
  4. NSLog(@"%d", [person isKindOfClass:obj_class]); // 1
  5. // NSObject类对象, NSObject类对象
  6. NSLog(@"%d", [obj isKindOfClass:obj_class]); // 1
  7. // Person元类对象, Person类对象
  8. NSLog(@"%d", [person_class isKindOfClass:person_class]); // 0
  9. // Person元类对象的superclass指向NSObject元类对象,而NSObject元类对象的superclass指向的就是NSObject类对象
  10. // Person元类对象, NSObject类对象
  11. NSLog(@"%d", [person_class isKindOfClass:obj_class]); // 1
  12. // NSObject元类对象, NSObject类对象
  13. NSLog(@"%d", [obj_class isKindOfClass:obj_class]); // 1
  14. // Person元类对象, Person元类对象
  15. NSLog(@"%d", [person_class isKindOfClass:person_meta_class]); // 1
  16. // Person元类对象, NSObject元类对象
  17. NSLog(@"%d", [person_class isKindOfClass:obj_meta_class]); // 1
  18. // NSObject元类对象, NSObject元类对象
  19. NSLog(@"%d", [obj_class isKindOfClass:obj_meta_class]); // 1
  20. // NSObject元类对象, Person元类对象
  21. NSLog(@"%d", [person_meta_class isKindOfClass:person_meta_class]); // 0
  22. // NSObject元类对象, NSObject元类对象
  23. NSLog(@"%d", [person_meta_class isKindOfClass:obj_meta_class]); // 1
  24. // NSObject元类对象, NSObject元类对象
  25. NSLog(@"%d", [obj_meta_class isKindOfClass:obj_meta_class]); // 1

isSubclassOfClass

isSubclassOfClass的类方法会拿到当前类对象以及superclass指针所指向的对象和传进来的类型做比较;该方法没有对象方法

  1. + (BOOL)isSubclassOfClass:(Class)cls {
  2. for (Class tcls = self; tcls; tcls = tcls->getSuperclass()) {
  3. if (tcls == cls) return YES;
  4. }
  5. return NO;
  6. }

我们可以通过一段代码打印来分析比较

  1. // Person类对象, Person类对象
  2. NSLog(@"%d", [person_class isSubclassOfClass:person_class]); // 1
  3. // Person类对象, NSObject类对象
  4. NSLog(@"%d", [person_class isSubclassOfClass:obj_class]); // 1
  5. // NSObject类对象, NSObject类对象
  6. NSLog(@"%d", [obj_class isSubclassOfClass:obj_class]); // 1
  7. // Person类对象, MJPerson元类对象
  8. NSLog(@"%d", [person_class isSubclassOfClass:person_meta_class]); // 0
  9. // Person类对象, NSObject元类对象
  10. NSLog(@"%d", [person_class isSubclassOfClass:obj_meta_class]); // 0
  11. // NSObject类对象, NSObject元类对象
  12. NSLog(@"%d", [obj_class isSubclassOfClass:obj_meta_class]); // 0
  13. // Person元类对象, Person元类对象
  14. NSLog(@"%d", [person_meta_class isSubclassOfClass:person_meta_class]); // 1
  15. // Person元类对象, NSObject元类对象
  16. NSLog(@"%d", [person_meta_class isSubclassOfClass:obj_meta_class]); // 1
  17. // NSObject元类对象, NSObject元类对象
  18. NSLog(@"%d", [obj_meta_class isSubclassOfClass:obj_meta_class]); // 1
  19. // Person元类对象, Person类对象
  20. NSLog(@"%d", [person_meta_class isSubclassOfClass:person_class]); // 0
  21. // Person元类对象, NSObject类对象
  22. NSLog(@"%d", [person_meta_class isSubclassOfClass:obj_class]); // 1
  23. // NSObject元类对象, NSObject类对象
  24. NSLog(@"%d", [obj_meta_class isSubclassOfClass:obj_class]); // 1

原文链接:http://www.cnblogs.com/funkyRay/p/ios-di-ceng-yuan-li-yiobjectivec-de-ben-zhi.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号