美文网首页
iOS OC对象的本质窥探(对象分类)(二)

iOS OC对象的本质窥探(对象分类)(二)

作者: 木子雨廷t | 来源:发表于2020-01-06 22:06 被阅读0次
    上面一篇文章讲了OC对象的本质,编译成C++对象是以什么形式存储的,一个对象占多少内存空间等问题,那么在OC语言里面,又分为几种对象呢?其实平时的工作中通过[[NSObject alloc] init]这种形式创建的对象都是实例对象,另外还有两类平时接触甚少的对象,一个是类对象,一个就是元类对象。

    开篇引题 类对象分为三种:
    实例对象
    类对象
    元类对象
    这三中类型的对象之间是什么关系?每种类型的对象又有什么特点呢?

    开始一探究竟
    • 实例对象
      实现以下代码
    NSObject *object1 = [[NSObject alloc] init];
    NSObject *object2 = [[NSObject alloc] init];        
    NSLog(@"%p %p",
                  object1,
                  object2);
    
    打印:0x100648340 0x100647440
    

    总结:不多讲了,第一篇文章已经写的很详细,
    object1、object2是NSObject的instance对象(实例对象)
    它们是不同的两个对象,分别占据着两块不同的内存
    instance对象在内存中存储的信息包括
    isa指针
    其他成员变量

    存储的是各个成员变量的值,还有一个isa指针,一个类在内存中的实例对象可以有多个。

    内存存储图示


    实例对象内存存储图示.png
    • 类对象
      1.获取类对象
      两种方法
    NSObject *object1 = [[NSObject alloc] init];
    Class objectClass1 = [object1 class];
    

    还可以通过一个runtime函数

    NSObject *object1 = [[NSObject alloc] init];        
    Class objectClass3 = object_getClass(object1);
    

    object_getClass 函数:传入实例对象返回类对象,传入类对象,返回元类对象
    下面实现以下代码

    NSObject *object1 = [[NSObject alloc] init];
    NSObject *object2 = [[NSObject alloc] init];
            
    Class objectClass1 = [object1 class];
    Class objectClass2 = [object2 class];
    Class objectClass3 = object_getClass(object1);
    Class objectClass4 = object_getClass(object2);
    Class objectClass5 = [NSObject class];
            
    NSLog(@"%p %p",
                  object1,
                  object2);
            
    NSLog(@"%p %p %p %p %p",
                  objectClass1,
                  objectClass2,
                  objectClass3,
                  objectClass4,
                  objectClass5);
    
    打印:0x100648340 0x100647440
         0x7fff9dab4118 0x7fff9dab4118 0x7fff9dab4118 0x7fff9dab4118 0x7fff9dab4118
    

    会发现两个实例对象的内存地址是不一样的,而通过两个实例对象获得的5个类对象的指针都是一样的,说明,一个类在内存中只有一个类对象。

    总结:
    objectClass1 ~ objectClass5都是NSObject的class对象(类对象)
    它们是同一个对象。每个类在内存中有且只有一个class对象
    class对象在内存中存储的信息主要包括
    isa指针
    superclass指针
    类的属性信息(@property)、类的对象方法信息(instance method)
    类的协议信息(protocol)、类的成员变量信息(ivar)
    ......

    内存存储图示


    类对象内存存储图示.png
    • 元类对象
      执行以下代码获取元类对象
    // object_getClass  函数:传入实例对象返回类对象,传入类对象,返回元类对象
    Class objectClass3 = object_getClass([NSObject class]);
    

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

    元类对象内存图示.png

    关于如何证明刚刚所总结的三种类型的对象分别存储的什么信息,通过阅读苹果源码可知

    分析:实例对象,类对象和元类对象中都有isa指针,而类对象和元类对象中除了含有isa指针之外还有superclass指针,问题:isa指针和superclass指针分别有什么作用呢?

    • 分析 创建一个Person类给person类分别声明一个对象方法和一个类方法
    // Person
    @interface Person : NSObject <NSCopying>
    {
        @public
        int _age;
    }
    @property (nonatomic, assign) int no;
    - (void)personInstanceMethod;
    + (void)personClassMethod;
    @end
    
    @implementation Person
    - (void)personInstanceMethod{
    }
    + (void)personClassMethod{
    }
    @end
    
    // 调用 
    Person *person = [[Person alloc] init];
    [person personInstanceMethod];
    [Person personClassMethod];
    
    • isa指针作用
      OC语言是消息机制 当执行[person personInstanceMethod]; 这行代码时 实际会被编译成 objc_msgSend(person, @selector(personInstanceMethod))进行调用,那么刚刚说到实例对象中不存储方法,那么当调用时这个对象方法是怎么获得的呢?
      答案: 当实例对象调用对象方法时是通过isa指针,指向自己的类对象,找到类对象中的方法信息,进行调用。同理,当执行 [Person personClassMethod]; 这行代码时,实例对象通过isa指针指向自己的类对象,又通过类对象中的isa指针指向元类对象,获取到类方法信息。
      图示:

      isa指针作用.png
    • 创建一个Person类给person类分别声明一个对象方法和一个类方法,再创建一个继承于Person类的Student类给Student类分别声明一个对象方法和一个类方法

    // Person
    @interface Person : NSObject <NSCopying>
    {
        @public
        int _age;
    }
    - (void)personInstanceMethod;
    + (void)personClassMethod;
    @end
    
    @implementation Person
    
    - (void)personInstanceMethod
    {
        
    }
    + (void)personClassMethod
    {
        
    }
    @end
    
    // Student
    @interface Student : Person <NSCoding>
    {
    @public
        int _weight;
    }
    - (void)studentInstanceMethod;
    + (void)studentClassMethod;
    @end
    
    @implementation MJStudent
    - (void)studentInstanceMethod
    {
        
    }
    + (void)studentClassMethod
    {
        
    }
    @end
    
    // 调用
    Student *student = [[Student alloc] init];
    [student personInstanceMethod];
    [Student personClassMethod];     
    
    • superclass指针作用
      总结:当执行[student personInstanceMethod]这行代码时 student对象通过isa指针找到自己类对象,结果发现类对象中没有personInstanceMethod这个方法,然后利用superclass指针找到父类也就是Person的类对象,查看是否有 personInstanceMethod 方法信息,如果有 就调用,没有的话继续向上查找。同理:当执行[Student personClassMethod]; 这行代码时,student对象通过isa指针找到自己类对象,又通过类对象的isa指针找到自己的元类对象结果发现元类对象中没有personClassMethod这个方法,元类对象利用superclass指针向上逐级寻找父类的元类对象是否有此方法。直到NSobject停止,如果找到就调用,找不到就会奔溃 unrecognized selector sent to class 0x1000011a0'

    图示:


    superclass指针作用.png

    最后总结:

    instance -> 实例对象,class -> 类对象,meta-class -> 元类对象

    1.instance的isa指向class
    2.class的isa指向meta-class
    3.meta-class的isa指向基类的meta-class
    4.class的superclass指向父类的class
    5.如果没有父类,superclass指针为nil
    6.meta-class的superclass指向父类的meta-class
    7.基类的meta-class的superclass指向基类的class
    8.instance调用对象方法的轨迹
    9.isa找到class,方法不存在,就通过superclass找父类
    10.class调用类方法的轨迹
    11.isa找meta-class,方法不存在,就通过superclass找父类

    图示:


    WeChataf27bd994cb5722150a32f853afae3b5.png
    • 画一下[student personInstanceMethod]这行代码的执行顺序


      [student personInstanceMethod]执行顺序.png
    • 画一下[Student load]这行代码的执行顺序


      [Student load]执行顺序.png

    疑问???类方法再调用时先去自己的元类对象中寻找,如果没有就去父类的元类对象中寻找,在没有就去NSObject的元类对象中寻找,再就去NSObject的类对象中寻找(根据最上面的线可以看来)那是这么回事吗?下面来验证一下

    //新建一个Car类,继承自NSObject
    @interface Car : NSObject
    
    @end
    
    @implementation Car
    
    @end
    
    // 创建一个NSObject的类别
    //.h中代码
    + (void)test;
    //.m中代码
    + (void)test{
        NSLog(@"+[NSObject test] - %p", self);
    }
    //调用代码
    [Car test];
    
    输出 :[Car class] - 0x100001220
    

    别的代码都不变,将类别中.m中的代码改成

    //+号变成-号
    - (void)test{
        NSLog(@"+[NSObject test] - %p", self);
    }
    
    输出 :[Car class] - 0x100001220
    

    再调用发现还是可以成功,具体的原因吧,我也说的不是很好,就不误导别人了,如果有大神知晓,欢迎指正

    如有疑问欢迎指正~

    强烈推荐:
    iOS OC对象的本质窥探(一)
    iOS获取手机唯一标示
    iOS 高德地图实现大头针展示,分级大头针,自定制大头针,在地图上画线,线和点共存,路线规划(驾车路线规划),路线导航,等一些常见的使用场景

    相关文章

      网友评论

          本文标题:iOS OC对象的本质窥探(对象分类)(二)

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