美文网首页
OC 对象的分类

OC 对象的分类

作者: 笔头还没烂 | 来源:发表于2023-04-09 23:33 被阅读0次

    Objective-C 中的对象,简称OC对象,主要可以分主3种:

    1. instance 对象(实例对象)

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

        NSObject *object1 = [[NSObject alloc] init];
        NSObject *object2 = [[NSObject alloc] init];
        
        • object1、object2 是 NSObject 的 instance 对象(实例对象)
        • 它们是不同的两个对象,分别占据着两块不同的内存
      • instance 对象在内存中存储的信息包括:isa 指针、其他成员变量

    2. class 对象(类对象)

      示例代码:

      #import "ViewController.h"
      #import <objc/runtime.h>
      
      @interface ViewController ()
      
      @end
      
      @implementation ViewController
      
      - (void)viewDidLoad {
          [super viewDidLoad];
          
          NSObject *object1 = [[NSObject alloc] init];
          NSObject *object2 = [[NSObject alloc] init];
          //实例对象的内存地址值输出
          NSLog(@"%p %p",object1,object2);
          /**
           输出结果如下:
           0x600000f440a0 0x600000f440f0
           */
          
          
          /**
          一个类的类对象是惟一的;
           */
          
          Class objectClass1 = [object1 class];
          Class objectClass2 = [object2 class];
          Class objectClass3 = [NSObject class];
          Class objectClass4 = object_getClass(object1);
          Class objectClass5 = object_getClass(object2);
          //类对象的内存地址值输出
          NSLog(@"%p",objectClass1);
          NSLog(@"%p",objectClass2);
          NSLog(@"%p",objectClass3);
          NSLog(@"%p",objectClass4);
          NSLog(@"%p",objectClass5);
          
          /**
           输出结果如下:
           0x1b8c94148
           0x1b8c94148
           0x1b8c94148
           0x1b8c94148
           0x1b8c94148
           */
      }
      

      输出结果如下:

      0x600000f440a0 0x600000f440f0

      0x1b8c94148
      0x1b8c94148
      0x1b8c94148
      0x1b8c94148
      0x1b8c94148

      小结:

      • object1、object2 是 NSObject 的 instance 对象(实例对象),它们是不同的两个对象,分别占据着两块不同的内存
      • objectClass1 ~ objectClass5 都是 NSObject 的 class对象(类对象),它们是同一个对象。每个类在内存中有且只有一个类对象

      class 对象在内存中存储的信息主要包括

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

      示例代码:

      #import "ViewController.h"
      #import <objc/runtime.h>
      
      @interface ViewController ()
      
      @end
      
      @implementation ViewController
      
      - (void)viewDidLoad {
          [super viewDidLoad];
          NSLog(@"===============实例对象输出==================");
          NSObject *object1 = [[NSObject alloc] init];
          NSObject *object2 = [[NSObject alloc] init];
          //基类的实例对象的内存地址值输出
          NSLog(@"%p %p",object1,object2);
          /**
           输出结果如下:
           0x600003e103b0 0x600003e10380
           */
          NSLog(@"================类对象输出=================");
          
          /**
          一个类的类对象是惟一的;
          类对象调用 class 方法返回的一直是 class 对象
           */
          Class objectClass1 = [object1 class];
          Class objectClass2 = [object2 class];
          Class objectClass3 = [NSObject class];
          Class objectClass4 = object_getClass(object1);
          Class objectClass5 = object_getClass(object2);
          //基类的类对象的内存地址值输出
          NSLog(@"%p",objectClass1);
          NSLog(@"%p",objectClass2);
          NSLog(@"%p",objectClass3);
          NSLog(@"%p",objectClass4);
          NSLog(@"%p",objectClass5);
          
          /**
           输出结果如下:
           0x1b8c94148
           0x1b8c94148
           0x1b8c94148
           0x1b8c94148
           0x1b8c94148
           */
          NSLog(@"===============元类对象输出=================");
          Class objectMetaClass = object_getClass(objectClass1);
          //基类的元类对象的地址输出
          NSLog(@"%p",objectMetaClass);
          /**
           输出结果如下:
           0x1b8c940f8
           */
          
          NSLog(@"============================================");
          /**
          基类的元类对象调用 class 方法,返回的仍然是它本身
           */
          NSLog(@"%p",[objectMetaClass class]);
          /**
           输出结果如下:
           0x1b8c940f8
           */
          NSLog(@"============查看是否是元类对象===============");
          /**
           查看是否是元类对象
           */
          BOOL result = class_isMetaClass([NSObject class]);
          if(result) {
              NSLog(@"[NSObject class] 返回的结果是元类对象");
          }else {
              NSLog(@"[NSObject class] 返回的结果不是元类对象");
          }
          /**
           输出结果:
           [NSObject class] 返回的结果不是元类对象
           */
      }
      
      
      @end
      

      输出结果如下:

      ===============实例对象输出==================

      0x600003e103b0 0x600003e10380

      ================类对象输出=================

      0x1b8c94148

      0x1b8c94148

      0x1b8c94148

      0x1b8c94148

      0x1b8c94148

      ===============元类对象输出=================

      0x1b8c940f8

      ============================================

      0x1b8c940f8

      ============查看是否是元类对象===============

      [NSObject class] 返回的结果不是元类对象

      • objectMetaClass 是 NSObject 的 meta-class对象(元类对象)
      • 每个类在内存中有且只有一个 meta-class 对象
      • meta-class 对象和 class 对象的内存结构是一样的(都是 Class 指针类型),但是用途不一样,元类对象在内存中存储的信息主要包括:
        • isa 指针;
        • superclass 指针;
        • 类的类方法信息(class method)

      小结:

      • 实例对象:

        • 通过 alloc 出来的对象是实例对象;
        • 实例对象存储了 isa 指针以及其他的成员变量。
      • 类对象:

        • 调用实例对象或者类对象的 class 方法 或者执行 runtime 的 object_getClass 方法并将实例对象作为参数传入,返回的结果都是类对象;
        • 类对象在内存中只有一份,即是惟一的。类对象无论调用多少次 class 方法,最终返回的依然还是类对象;
        • 类对象主要存储 isa 指针、superclass 指针、属性信息、对象方法信息、协议信息、成员变量信息(如变量的类型、变量的名字)等“只需定义一份”的信息,类似于 Java 中“类”的概念,相当于定义了一个“模子”。只是这个“模子”不包含类方法,oc 将类方法存储到了元类对象当中。
      • 元类对象:

        • 执行 runtime 的 object_getClass 方法并将类对象作为参数传入,返回的结果是元类对象;
        • 元类对象调用 class 方法,返回的是基类的元类对象。
        • 元类对象主要存储 isa 指针、superclass 指针,以及类方法信息。class_isMetaClass 可用于判断传入的对象是否是元类对象。
    4. 面试题:

      (1)对象的 isa 指向哪里?

      • 实例对象的 isa 指定类对象;类对象的 isa 指针指向元类对象;元类对象的 isa 指针指向 NSObject 基类的 元类对象。

      相关源码:

      (2)OC 的类信息存放在哪里?

      • OC 的实例对象存储了成员变量信息(成员变量的值);

      • OC 的类对象存储了类信息中的属性信息、对象方法信息、协议信息、成员变量信息(如成员变量的类型、成员变量的变量名等)等;

      • 元类对象存储了类信息中的类方法信息等;

    5. runtime 的相关源码在 obj4 中查看;

    6. objc_getClass 和 object_getClass 方法的区别:

      源码对比如下:

      (1)object_getClass 的源码:

      /***********************************************************************
      * object_getClass.
      * Locking: None. If you add locking, tell gdb (rdar://7516456).
      **********************************************************************/
      Class object_getClass(id obj)
      {
          if (obj) return obj->getIsa();
          else return Nil;
      }
      

      (2)objc_getClass 的源码:

      /***********************************************************************
      * objc_getClass.  Return the id of the named class.
      **********************************************************************/
      Class objc_getClass(const char *aClassName)
      {
          if (!aClassName) return Nil;
      
          // NO unconnected, YES class handler
          return look_up_class(aClassName, NO, YES);
      }
      

      通过源码可以看出,objc_getClass 是将类名作为参数,参数是一个字符串,通过字符串返回对应的类对象;

      object_getClass 是将对象作为参数:

      • 传入的如果是实例对象,则返回类对象;

      • 传入的如果是类对象,则返回元类对象;

      • 传入的如果是元类对象,则返回基类的元类对象;

    7. OC 的消息机制

      如:

      Person *person = [[Person alloc] init];
      [person personInstanceMethod];
      //最后一行代码,底层实现可转为:objc_msgSend(person,@selector(personInstanceMethod));
      
    8. 方法的调用问题

      • 实例对象的 isa 指向 类对象:当调用对象方法时,通过 instance 的 isa 找到 class,最后找到对象方法的实现进行调用。
      • 类对向的 isa 指向元类对象:当调用类方法时,通过 class 的 isa 找到 meta-class,最后找到类方法的实现进行调用。
    9. superClass 指针

      • 作用:如果是类对象的 superClass 指针,则指向父类的类对象;如果是元类对象的 superClass 指针,则指向父类的元类对象。
      • 类对象的superClass 指针的方法调用流程:
        • 假设子类实例对象调用父类的实例方法(eg: [stu personInstanceMethod]),则调用流程为:
          • 第一步:先通过实例对象的 isa 指针找到子类的类对象(因为实例方法存在该子类的类对象中),在该子类的类对象的对象方法列表中找要调用的方法;能找到则直接调用,找不到则进入第二步;
          • 第二步:如果在子类类对象的实例方法列表中找不到要调用的方法,此时通过子类类对象的 superClass 指针去找该子类的父类类对象,进入第三步;
          • 第三步:找到子类的父类类对象后,会看父类类对象的实例方法列表中是否存在要调用的方法,如果有则直接调用,如果没有,则继续通过该父类类对象的 superClass 指针去找该父类的父类类对象,一层层往上找,直到找到 NSObject,如果所有的父类类对象的实例方法列表中都不存在该方法的实现,则代码直接闪退,会报 unrecognized selector 的经典错误;
        • 场景举例:当 Student 的 instance 对象要调用 Person 的实例方法时,会先通过 isa 找到 Student 的 class,然后通过 superclass 找到 Person 的 class,最后找到实例方法进行调用。
      • 元类对象的 superClass 指针的方法调用流程:
        • 假设子类对象调用父类的类方法(eg: [Student personClassMethod]),则调用流程与类对象的 superClass 指针类似,如下:
          • 第一步:先通过子类类对象的 isa 找到该子类的元类对象(因为类方法是存在该子类的元类对象中),看该子类的元类对象的类方法列表中是否存在要调用的方法,如果有则直接调用,如果没有则进入第二步;
          • 第二步:如果在子类元类对象的类方法列表中找不到要调用的类方法,此时通过子类的元类对象的 superClass 指针去找该子类的父类的元类对象,进入第三步;
          • 第三步:找到子类的父类元类对象后,会看父类元类对象的类方法列表中是否存在要调用的方法,如果有则直接调用,如果没有,则继续通过该父类的 superClass 指针去找该父类的父类类对象,一层层往上找,直到找到 NSObject。如果所有父类元类对象的类方法列表中都不存在该方法的实现,则代码直接闪退,会报 unrecognized selector 的经典错误;
        • 场景举例:当 Student 的 class 要调用 Person 的类方法时,会先通过 isa 找到 Student 的 meta-class,然后通过 superclass 找到 Person 的 meta-class,最后找到类方法的实现进行调用。
      • 小结:类对象的 superClass 指针指向父类的类对象;元类对象的 superClass 指针指向父类的元类对象;
        附苹果官方图:


        对象的isa 指针和 superClass 指针.png

    相关文章

      网友评论

          本文标题:OC 对象的分类

          本文链接:https://www.haomeiwen.com/subject/ejsiddtx.html