看清一个OC“对象”

410 阅读6分钟

1、Objective-C 语言中的对象

Objective-C 语言中的对象可以分为三类分别是:

  • 实例对象 —— instance
  • 类对象 —— Class
  • 元类对象 —— meta-Class

进入 #import <objc/objc.h> 文件内部可以看到:

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

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

/// A pointer to an instance of a class.
typedef struct objc_object *id;

可见 instance 对象 和 class 对象都是一个结构体 struct。

那么问题来了 ↓↓↓

1.1 什么是 instance 对象?

OC 中通过alloc 或者 new 出来的对象,就是 instance 对象,每次调用 instance 或 new 都会创建出一个新的 instance 对象。

Person *personA = [Person alloc] init];
Person *personB = [Person alloc] init];

上述的 personA 和 personB就是利用 alloc 创建出来的两个不同实例对象,并通过 init 为其分配给两者不同的存储空间。

instance 对象中包含了两个重要信息:

  • isa指针 -- Class _Nonnull isa OBJC_ISA_AVAILABILITY;
  • 成员变量 -- 如果有的话

1.2 什么是 Class 对象?

Class 类对象是用来描述一个 instance 对象的对象,进入 objc_class 结构体可以看到其内部存放的信息有:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

那么,class 对象包含的内容有:

  • isa 指针
  • superClass指针
  • 类的名字、版本、实例大小
  • 实例方法的列表 objc_method_list
  • 协议信息的列表 objc_protocol_list
  • 各个成员变量的列表 objc_ivar_list
  • cache 最近调用的方法都放在这里,提升方法查找效率

该结构体的第一个成员变量也是 isa 指针,这就说明了 Class 本身其实也是一个对象,我们称之为类对象,而且一个 instance 对象的类对象是唯一的。

1.3 什么是 meta-Class 对象?

meta-Class 对象是用来描述一个 Class 对象的对象

其内部存放的信息有:

  • isa 指针
  • superClass 指针
  • 类方法方法列表 +method

meta-class对象和class对象的内存结构是一样的,所以meta-class中也有类的属性信息,类的对象方法信息等成员变量,但是其中的值可能是空的。因为class和meta-class对象都可以通过object_getClass 获得,因此他们的类型都是一样的(同样的Class 这个机构体),都是typedef struct objc_class *Class这个 Class 类,可以理解,它们各自利用Class的一些“成员变量”来实现自己的功能。

上面说到一个“meta-Class对象是用来描述一个‘类对象’的对象呢,为什么这里会有‘类对象’这个名词?有点陌生?因为我们有时候也会用到类方法也就是+ 方法,方法调用就是在向对象发送消息的过程,所以会有“类对象”这个名词。

因为每个类在内存中有且只有一个类对象,所以每个类在内存中也有且只有一个元类对象 当你给对象发送消息时,消息是在寻找这个对象的类的方法列表 当你给类发消息时,运行时系统会寻找这个类的元类的方法列表。

我们在学习实例对象、类对象和元类的时候,都会看到下面这张图

如果看不懂没关系,我们可以通过代码来验证一下:

先基于NSObject创建一个Person类,包含属性 name

NSObject *obj = [[NSObject alloc] init];
UIResponder *resp = [[UIResponder alloc] init];
UIView *view = [[UIView alloc] init];
UILabel *lab = [[UILabel alloc] init];
Person *person = [[Person alloc] init];
    
Class objClass = object_getClass(obj); // NSObject
Class objClassMeta = object_getClass(objClass);// NSObject
Class objClassMetaMeta = object_getClass(objClassMeta);// NSObject
Class objClassSuper = class_getSuperclass(objClass);// nil
Class objClassSuperSuper = class_getSuperclass(objClassSuper);// nil
    
    
Class respClass = object_getClass(resp);// UIResponder
Class respClassMeta = object_getClass(respClass);// UIResponder
Class respClassMetaMeta = object_getClass(respClassMeta);// NSObject
Class respClassSuper = class_getSuperclass(respClass); // NSObject
Class respClassSuperSuper = class_getSuperclass(respClassSuper);// nil
    
    
Class viewClass = object_getClass(view); // UIView
Class viewClassMeta = object_getClass(viewClass); // UIView
Class viewClassMetaMeta = object_getClass(viewClassMeta);// NSObject
Class viewClassSuper = class_getSuperclass(viewClass);// UIResponder
Class viewClassSuperSuper = class_getSuperclass(viewClassSuper);// viewClassSuperSuper
    
Class labClass = object_getClass(lab);// UILabel
Class labClassMeta = object_getClass(labClass);// UILabel
Class labClassMetaMeta = object_getClass(labClassMeta);// NSObject
Class labClassSuper = class_getSuperclass(labClass);// UIView
Class labClassSuperSuper = class_getSuperclass(labClassSuper);// UIResponder
    
Class persClass = object_getClass(person); // Person
Class persClassMeta = object_getClass(persClass); // Person
Class persClassMetaMeta = object_getClass(persClassMeta);// NSObject
Class persClassSuper = class_getSuperclass(persClass);// NSObject
Class persClassSuperSuper = class_getSuperclass(persClassSuper);// nil
    
//对实例对象person添加 KVO
[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
    
//添加KVO之后
Class persClass_KVO = object_getClass(person); // NSKVONotifying_Person
Class persClassMeta_KVO = object_getClass(persClass_KVO); // NSKVONotifying_Person
Class persClassMetaMeta_KVO = object_getClass(persClassMeta_KVO);// NSObject
Class persClassSuper_KVO = class_getSuperclass(persClass_KVO);// Person
Class persClassSuperSuper_KVO = class_getSuperclass(persClassSuper_KVO);// NSObject


那么根据上面代码及结果可以得出更详实的图如下:

注意观察上图虚线代表的 isa 指针指向,可以看出:

  • 一个instance 实例对象(struct objc_object结构体)的 isa 指针指向了类对象,当调用实例对象的方法时,会通过实例对象的 isa 找到类对象以及类对象中的实例方法列表,最终找到对象方法进行调用。
  • 类对象的 isa 指针指向了元类对象 meta-Class,调用类方法时,会通过类对象的 isa 找到元类,最终找到元类的类方法列表,并对类方法进行调用。
  • 根元类的超类是NSObject,而isa指向了自己,而NSObject的超类为nil,也就是它没有超类。
  • 【此条我觉得略有不妥待更正】元类的 isa 指针指向了 RootClass,因为OC中所有对象的RootClass 是 NSObject,那么 NSObject 的isa也就是指向了自己。
  • 当子类对象要调用父类的对象方法时,会先通过子类的isa找到父类的 Class,然后再通过如上图实线箭头找到其superClass,然后再在其superClass的方法列表中找到方法进行调用。
  • 方法 class_isMetaClass(Class cls) 可以得到参数 Class cls 是不是一个元类。
  • OC的类其实也是一个对象,一个对象就要有一个它属于的类,意味着类也要有一个 isa 指针,指向他所属的类。那么类的类是什么?就是我们所说的元类 (MetaClass) ,所以,元类就是类所属的类。
  • 从消息机制的层面来说: 当你给对象发送消息时,消息是在寻找这个对象的类的方法列表。 当你给类发消息时,消息是在寻找这个类的元类的方法列表。

2、利用 Runtime 看看 Class

需要项目引入<objc/runtime.h>

我的Person类的头文件

@interface Person : NSObject
{
    int height;
}
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) NSInteger age;
@end

2.1 获取类的成员变量列表

- (void)getClassIvarList {
    unsigned int count;
    Ivar *ivarList = class_copyIvarList([Person class], &count);
    for (unsigned int i = 0; i < count; i++) {
        Ivar myIvar = ivarList[i];
        const char *ivarName = ivar_getName(myIvar);
        NSLog(@"ivar(%d) : %@",i,[NSString stringWithUTF8String:ivarName]);
    }
    free(ivarList);
}

控制台输出为:

可以得到我的 Person 类的实例中将包含三个成员变量分别是:height_name_age

2.2 获取类的属性列表

- (void)getClassPorpertyList {
    unsigned int count;
    objc_property_t *propertyList = class_copyPropertyList([Person class], &count);
    for (unsigned int i = 0; i < count; i ++) {
        const char *propertyName = property_getName(propertyList[i]);
        NSLog(@"propertyName(%d) : %@",i , [NSString stringWithUTF8String:propertyName]);
    }
    free(propertyList);
}

控制台输出为:

可以得到我的 Person 类的实例中包含的属性名分别是 nameage

2.3 获取类的实例方法列表

- (void)getClassInstanceMothodList {
    unsigned int count;
    Method *methodList = class_copyMethodList([Person class], &count);
    for (unsigned int i = 0; i < count; i ++) {
        Method method = methodList[i];
        NSLog(@"method(%d) : %@",i,NSStringFromSelector(method_getName(method)));
    }
    free(methodList);
}

可以得到我的 Person 类的实例中包含的方法列表中的方法:

2.4 获取类的协议列表

- (void)getClassProtocolList {
    unsigned int count;
    __unsafe_unretained Protocol **protocalList = class_copyProtocolList([Person class], &count);
    for (unsigned int i = 0;i < count; i ++) {
        Protocol *myProtocol = protocalList[i];
        const char *protocolName = protocol_getName(myProtocol);
        NSLog(@"protocol(%d) : %@",i ,[NSString stringWithUTF8String:protocolName]);
    }
    free(protocalList);
}

2.5 Class 和 meta-Class 的小区别

上面说到: meta-class对象和class对象的内存结构是一样的,所以meta-class中也有类的属性信息,类的对象方法信息等成员变量,但是其中的值可能是空的。

对于这句话,附上一个直观的图来学习的话,可能会更形象一点。

上图应该注意一点:利用 **class_copyMethodList**获取方法列表,当参数传入的是某个类的元类的时候,获取到的是这个类的类方法列表。

3、还有一堆"眼花缭乱"的方法:

看这堆方法之前,需要重提一下:OC中的对象有三种,分别是instance -- 实例对象、class 对象 -- 类对象和 meta-class -- 元类对象

3.1 class_isMetaClass()

class_isMetaClass(Class _Nullable cls) 入参是一个 Class 的cls,旨在获得一个 该对象是不是一个元类的 BOOL 值。

3.2 object_getClassName()

object_getClassName(id _Nullable obj) 入参是一个 id 类型的 obj,旨在获得这个 obj 的 isa 的指针指向的字符串表示。与方法 3 的区别在于这个是用字符串返回结果,方法3是用对象返回结果。

3.3 object_getClass()

object_getClass(id _Nullable obj) 入参是一个 id 类型的obj,旨在获得这个 obj 的 isa 的指针指向

  • 1> 传入的obj可能是instance对象,class对象、meta-class对象
  • 2> 返回值 a:如果是instance对象,返回class对象 b:如果是class对象,返回meta-class对象 c:如果是meta-class对象,返回NSObject(基类)的meta-class对象

3.4 objc_getClass()

objc_getClass(const char *aClassName)入参是一个char类型的类名,返回的就是这个类的类对象--Class。

3.5 [object class]

[obj class]则分两种情况:当obj为实例对象时,[obj class]中 class是实例方法:- (Class)class,返回的obj对象中的isa指针;

当 obj 为类对象(包括元类和根类以及根元类)时,调用的是类方法:+ (Class)class,返回的结果为其本身。

3.6 class_getSuperclass

class_getSuperclass(Class _Nullable cls) 入参是一个 Class 类型的 cls,旨在获得 cls 的超类。

3.7 objc_getMetaClass

objc_getMetaClass(const char * _Nonnull name)入参是一个字符串类型 name,返回值为 Class,旨在获得入参的元类对象。如:po objc_getMetaClass(class_getName(object_getClass(a object)))