深入理解objc中的类与对象

作者: ZhengLi | 来源:发表于2016-08-16 17:13 被阅读767次

    引言

    促使我写这篇文章的主要原因是我发现平时利用的class方法与object_getClass函数的返回值竟然是不一样的,这与我之前理解的类的概念一直有所偏差。在查阅了部分资料后,发现对objc中关于类与对象有了一个新的理解,遂撰此文记录下。

    为了更好的阅读体验,推荐到我的博客阅读。
    码字不易,各位看官看的喜欢烦请点个赞吧!以示鼓励啊😢

    深入了解类与对象

    想要搞清楚这两个方法的区别,需要彻底搞清楚objc中关于类与对象的概念。

    objc中对象与类的概念

    objc中关于对象的定义如下:

    /// Represents an instance of a class.
    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    

    可见对象是一个含有isa指针的结构体。那么isa指向的Class又是什么呢?

    /// An opaque type that represents an Objective-C class.
    typedef struct objc_class *Class;
    

    原来Class是一个指向objc_class结构体的指针,apple的注释写的很清楚,Class相当于objc中的类。所以只有我们搞明白了objc_class结构体到底是什么,也就搞清楚了objc中关于类的概念。
    objc/runtime.h中关于该结构体的定义如下:

    struct objc_class { 
        Class isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class super_class                                        OBJC2_UNAVAILABLE;
        const char *name                                         OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
        struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
        struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    /* Use `Class` instead of `struct objc_class *` */
    

    虽然下面的一段串内容已经是OBJC2_UNAVAILABLE了,但依然可以看出一些apple关于class的定义的倪端:

    1. 我们注意到,objc_class结构体内部也含有一个isa指针,也就说“类也是有类的”。虽然读起来很拗口,但这也是为什么类有时候也被称为类对象的原因:类其实也是一个对象~
    2. 下面一段内容含有:类的方法列表(这些方法其实都是对象方法,至于为什么,后面解释),类的成员变量列表,类的cache列表,类的协议列表。更重要的是,它还有一个指向Classsuper_class指针。

    接下来,我们来一一讲解这些东西到底都是干嘛的。


    • 要想搞清楚下面的概念,首先要知道顶部的Class isa的含义。我们之前已经提到过,类也是对象,正是因为这个isa指针。我们从objc_object的结构体定义中已经看得很明白:何为对象?不过是一个含有指向Classisa指针罢了。故类不过也是一个对象。

    • 看到这如果你没有一个清晰的概念,可能已经糊涂了。没关系,只要搞明白类对象里的isa指针指向的是什么,你就明白了一切。先上个图:

    对象,类,元类

    我们现在讲的类对象,就是中间的RootClass,ASuperClass,AClass。我们可以清晰的看到,此处的isa指针指向的是MetaClass,又译为元类。而所谓元类,就是类对象的类,也可以说是类对象的类对象

    看到这里,你可能已经恍然大悟:对象的isa指针指向类对象,类对象的isa指针指向了元类。元类的isa指针指向根元类。三者统一了objc关于对象,类的基本概念。

    • 讲完了对象,类对象的isa指针。让我们回到objc1中关于那长长的一串定义:
    #if !__OBJC2__
        Class super_class                                        OBJC2_UNAVAILABLE;
        const char *name                                         OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
        struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
        struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
    #endif
    

    结合上面的元类概念,我们就能明白,原来苹果设计类对象的用意,就是为了存储对象的某些信息,比如对象方法,类遵守的一些协议,对象的属性,对象的类的父类(拗口。。。)等等。所以我们也就不能理解设计元类的用意:存储类的一些信息,比如类方法就存在元类里。相信读到这里,已经对objc中的对象和类有了一个很深刻的理解。下面说说我写这篇文章的主要原因:理解object_getClass函数与class方法。

    理解object_getClass函数与class方法。

    注:此处需要对runtime有了解

    天天都在用的class方法

    • 说起class方法这个方法,大家必然都不陌生,最常用的大概就是下面这个对象方法:

      Class aClass = [anObject class];
      

      此方法目的就是返回某个对象的类。说到这里,结合上面的结论,既然类是一个对象,那么给类发送class消息会怎么样?会编译不过?还是crash?

      Clas aClass = [NSObject class];
      

      上面这种写法是完全可行的,但是并没有任何意义。给类对象发送class消息,会返回类对象自身。

      如何验证?

        //用作测试的类    
        @interface testClass : NSObject
        
        @end
        
        //测试代码
          testClass *testObject = [testClass new];
          Class testObjectClass = [testObject class];
          Class aClass = [testClass class];
            
          NSLog(@"testObjectClass.p == %p",testObjectClass); //object's class
            
          NSLog(@"[testClass class].p === %p",aClass);// class's class
      

      最后结果:

        2016-08-16 16:21:25.581 总结测试[49724:3045182] testObjectClass.p == 0x100286ed8 
        2016-08-16 16:21:25.581 总结测试[49724:3045182] [testClass class].p === 0x100286ed8
      
    • 为什么会这样?

        - (Class)class OBJC_SWIFT_UNAVAILABLE("use 'anObject.dynamicType' instead");
        + (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");//[NSObject class]
        + (Class)class;//[NSProxy class]
      

      点开class方法定义我们会发现,其实class既是类方法,也是对象方法。只不过执行对象方法的时候,会返回自己。代码:

          + (Class)class{
              return self;
          }
          - (Class)class{
              return object_getClass(self);
          }
      

      也就理解为:当调用者是类对象时,class方法返回自身;当调用者是对象时,返回object_getClass函数的返回值。(object_getClass函数其实返回的是isa指针指的对象,后面会解释)

    强大的runtime函数:object_getClass

    • 知道了class方法的实质,接下来就要好好了解下object_getClass这个函数了。结合上面的给出的对象、类、元类图我们能猜想出该函数的伪码:

        Class object_getClass(id obj)
        {
            if (obj) return obj->getIsa();
            else return Nil;
        }
      

      就是说:该函数返回的是idisa指向的结构体

    • 看到这里,你也许感觉豁然开朗,于是你可能会这样写:

        NSNumber *aNumber = [NSNumber numberWithInt:2];
      
        NSLog(@"instance.p === %p",aNumber);//对象地址
        
        NSLog(@"classInstance.p === %p",[NSNumber class]);//类对象地址 class方法获得
        NSLog(@"classInstance.pWithGetClassFunc%p",object_getClass(aNumber));//类对象地址 函数获得
        
        NSLog(@"metaClass.p%p",object_getClass(object_getClass(aNumber)));//类对象的isa指针
        NSLog(@"metaClass.isa.p%p,",object_getClass(object_getClass(object_getClass(aNumber))));//元类的isa指针
        NSLog(@"metaClass.isa.isa.p%p",object_getClass(object_getClass(object_getClass(object_getClass(aNumber)))));//元类的isa指针的isa指针
        NSLog(@"metaClass.isa.isa.isa.p%p",object_getClass(object_getClass(object_getClass(object_getClass(object_getClass(aNumber))))));//元类的isa指针的isa指针的isa指针
          
      
      然后你会看到如下输出:
          
        instance.p ===                        0xb000000000000022
        classInstance.p ===                  0x10e5022a0
        classInstance.pWithGetClassFunc       0x105a6a368
        
        metaClass.p                           0x105a6a390
        metaClass.isa.p                       0x105617198
        metaClass.isa.isa.p                   0x105617198
        metaClass.isa.isa.isa.p               0x105617198
      

      先看第二段,你会发现第一行和第二行的地址不一样,这是因为元类的isa 指针指向的是根元类,而根元类的isa指针指向自己。

      再看第一段,对象的地址和类对象的地址完全不一样。这是因为类对象既不在栈上,也不在堆上。你可以把它理解为单粒。至于为什么是这样,不在本文讨论中。有兴趣的同学可以自己查阅资料。

      然后我们会发现[NSNumber class]获得的类对象与函数object_getClass获得的类对象地址居然不一样?!

      what f -- k

      没关系,耐着性子这样查:

      Class class1 = [NSNumber class];
      Class class2 = [aNumber class];
      Class class3 = object_getClass(aNumber);
      

      打个断点,在控制台依次p出每个class:

       (lldb) p class1
       (Class) $0 = NSNumber
       (lldb) p class2
       (Class) $1 = __NSCFNumber
       (lldb) p class3
       (Class) $2 = __NSCFNumber
      

    原来NSNumber是类簇。这里还是简单的说说类簇的概念吧:一个父类有好多子类,父类在返回自身对象的时候,向外界隐藏各种细节,根据不同的需要返回的其实是不同的子类对象,这其实就是抽象类工厂的实现思路,iOS最典型的就是NSNumber


    码字不易,各位看官看完如有收获烦请点个赞吧!以示鼓励啊😢
    至此,终于写完了这篇文章。希望对大家有所帮助吧~~

    相关文章

      网友评论

        本文标题:深入理解objc中的类与对象

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