天天看点

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

上面一篇文章讲了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指针,一个类在内存中的实例对象可以有多个。

内存存储图示

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

    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)

内存存储图示

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

    执行以下代码获取元类对象

// object_getClass  函数:传入实例对象返回类对象,传入类对象,返回元类对象
Class objectClass3 = object_getClass([NSObject class]);
           

总结:

objectMetaClass是NSObject的meta-class对象(元类对象)

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

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

isa指针

superclass指针

类的类方法信息(class method)

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

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

  • 简单图示
    iOS OC对象的本质窥探(对象分类)(二)
    想要更深层次的了解类的本质还是需要自己去阅读源码
分析:实例对象,类对象和元类对象中都有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指针指向元类对象,获取到类方法信息。

    图示:
    iOS OC对象的本质窥探(对象分类)(二)
  • 创建一个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'

图示:

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

最后总结:

#####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找父类

图示:

iOS OC对象的本质窥探(对象分类)(二)
  • 画一下[student personInstanceMethod]这行代码的执行顺序
    iOS OC对象的本质窥探(对象分类)(二)
  • 画一下[Student load]这行代码的执行顺序
    iOS OC对象的本质窥探(对象分类)(二)
疑问???类方法再调用时先去自己的元类对象中寻找,如果没有就去父类的元类对象中寻找,在没有就去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获取手机唯一标示

iOS 高德地图实现大头针展示,分级大头针,自定制大头针,在地图上画线,线和点共存,路线规划(驾车路线规划),路线导航,等一些常见的使用场景

如有疑问请看:iOS OC对象的本质窥探(一)