OC原理--对象、类的本质

OC原理--对象、类的本质

IOS小彩虹2021-07-18 22:59:2940A+A-

一、oc类和对象的底层实现

我们平时编写的OC代码,底层实现其实都是C\C++代码。OC中的对象和类都是基于C\C++的结构体来实现的。

@interface Person : NSObject
{
    @public
    int _age;
    NSString *_name;
}
@property(nonatomic,assign)float height;
@end
@implementation Person
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *ojb = [[NSObject alloc] init];
    }
    return 0;
}

我们可以利用Xcode内置的clang编译器将上面oc代码转化成C++文件

clang -rewrite-objc main.m -o main.cpp //这样生成的c++文件是跨平台的,会过于庞大,建议要指定平台
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp //指定iphone平台64位 这样生成的c++文件就会小很多

转化后在cpp文件中很容易找代表Person类的结构体,结构体里面存放着person类的属性

struct Person_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	int _age;
	NSString *_name;
	float _height;
};

代表Person类的结构体中存储的第一个变量还是一个结构体

struct NSObject_IMPL {
	Class isa;
};

而在NSObject.h中我们找到NOObject类的声明

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

我们可以得知NSObject类也是由结构体来实现的。 通过上图我们可以得知oc中的继承关系在c++中是这样表达的:子类的结构体中第一个变量是其父类的结构体。

结论:OC中声明一个类,对应到C++就是声明一个结构体,结构体内存放类的成员变量,OC中创建一个对象,对象到C++就是用创建该类对象的结构体的变量,结构体变量中存放了类的成员变量的值。

二、instance对象、类对象、元类对象

  • instance对象通过类alloc出来的对象,每次调用alloc都会产生新的instance对象
//ob1 ob2 是两个instance对象,分别占据着两块不同的内存空间
NSObject *ob1 = [[NSObject alloc] init];
NSObject *ob2 = [[NSObject alloc] init];

instance对象在内存中存储的信息包括:isa指针和其他成员变量的值

  • 每个类在内存中有且只有一个类对象(class对象)
//类对象的获取方式
NSObject *ob1 = [[NSObject alloc] init];
Class objectClass1 = [ob1 class];
Class objectClass2 = [NSObject class];
Class objectClass3 = object_getClass(ob1); 
//通过打印可以发现objectClass1 objectClass2 objectClass3的内存地址一样

类对象在内存中存储的信息:isa指针、superclass指针、类的属性信息(@property)、类的对象方法信息(instance method)、类的协议信息(protocol)、类的成员变量信息(ivar)

注意:这里的成员变量的信息和属性信息指的是成员变量的名字、类型。。。

  • 每个类在内存中有且只有一个元类对象(meta-class对象)
//将类对象当做参数转入,获得元类对象
Class objectMetaClass = object_getClass(类对象);

meta-class对象和class对象的内存结构是一样的,因为他们都是Class类型,只是meta-class对象中一下这些都没有存储值【类的属性信息(@property)、类的对象方法信息(instance method)、类的协议信息(protocol)、类的成员变量信息(ivar)】 元类对象在内存中主要存储的信息:isa指针、superClass指针、类的类方法信息(class method)

Q1:成员变量的值存在实例对象中:因为每个实例对象的成员变量的值是可以不同的。

Q2:类的属性信息(@property)、类的对象方法信息(instance method)、类的协议信息(protocol)、类的成员变量信息(ivar)存在类对象中:这些信息类是确定且唯一

Q3:类的类方法信息(class method)存在元类对象中:???

下面从源码层面证明下class对象和meta-class对象的结构,源码下载地址 我们看到Class本质上就是一个objc_class类型的结构体指针。下面是objc_class的大体源码

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // 方法缓存
    class_data_bits_t bits;    // 用户获取类的具体信息  
	
    class_rw_t *data() const {
        return bits.data();
    }
 
}

struct class_rw_t {
	//获取只读信息表
    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>()->ro;
        }
        return v.get<const class_ro_t *>();
    }
	
    
	//获取方法数组 这是获取到的是一个二维数组 包括了分类和原有类的方法
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
        }
    }
	//获取属性数组 这是获取到的是一个二维数组 包括了分类和原有类的属性
    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->baseProperties};
        }
    }
	//获取协议数组  这是获取到的是一个二维数组 包括了分类和原有类的协议
    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }
};

struct class_rw_ext_t {
    const class_ro_t *ro;
    method_array_t methods; //二维的方法数组
    property_array_t properties; //二维的属性数组
    protocol_array_t protocols; //二维的协议数组
    char *demangledName;
    uint32_t version;
};
//这里的信息是只读的
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  //类占用内存大小
    const uint8_t * ivarLayout;
    const char * name; //类的名字
    method_list_t * baseMethodList; //类的初始方法数组 一维数组
    protocol_list_t * baseProtocols; //类的初始协议数组 一维数组
    const ivar_list_t * ivars;  //类的初始成员变量数组 一维数组

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

我们看到上面的源码,装方法、属性、协议的数组各有一个一维数组和二维数组,其中一维数组是自读的,用来存储类的初始信息,二维数组是可以读写的,用来存储分类和本类的信息,这个可以参考OC原理-Category

三、对象isa指针

我们发现instance对象、class对象、meta-class对象在内存中都存储了一个isa指针。

//给Person类声明一个类方法和实例方法 调用 转成C++文件 
Person *p = [[Person alloc] init];
[p instanceMethod];
[Person classMethod];

//转化后
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("instanceMethod"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("classMethod"));
//精简下
objc_msgSend(p,sel_registerName("instanceMethod"));
objc_msgSend(objc_getClass("Person"),sel_registerName("instanceMethod"));

调用对象方法:给instance对象发送一个消息!那么instance对象又是如何找到存储在class对象中的实例方法呢。 调用类方法:给class对象发送一个消息!那么class对象又是如何找到存储在meta-class对象中的类方法呢。

答案就是通过isa指针: instance对象的isa指针指向class对象,当调用对象方法时,通过instance对象的isa指针找到class对象,进而找到对象方法进行调用; class对象的isa指针指向meta-class对象,当调用类方法是,通过class对象的isa指针找到meta-class对象,进而找到类方法进行调用。

这就解释了类方法为何存放在meta-class对象中。

  • 通过isa查找的具体过程

通过上图,我们看到了class对象中的isa的值跟meta-class对象的内存地址一致,是简单的指向关系,而instance对象中的isa的值跟class对象的内存地址不一致,说明通过instance对象的isa指针查找class对象还有一个过程。

从64bit开始,isa需要进行一次位运算才能拿到class对象meta-class对象的真实地址。

ios系统和macos系统中ISA_MASK值不同
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

我这里用的模拟器运行的demo 也就是macos系统 所以是 & 0x00007ffffffffff8

四、类对象和元类对象的superClass指针

类对象的superClass指针指向了其父类的类对象; 这也是OC中能实现继承的关键。 当Student类的instance对象要调用其父类Person类的对象方法时,会通过instance对象的isa指针先找到Student的class对象,然后再通过Student的class对象的superClass指针找到Person的class对象,进行实例方法调用。

元类对象的superClass指针指向了其父类的元类对象; 这也是OC中能实现继承类方法的关键。 当Student类的class对象要调用其父类Person类的类方法时,会通过class对象的isa指针先找到Student的meta-class对象,然后再通过Student的meta-class对象的superClass指针找到Person的mete-class对象,进行类方法调用。

五、isa指针和superClass指针总结

总结下isa指针superclass指针就会有下面一张经典的图

  • instance对象的isa指针指向class对象
  • class对象的isa指针指向meta-class对象
  • meta-class对象的isa指针指向基类的meta-class对象
  • class对象的superClass指针指向父类的class对象 基类没有父类,superClass指针为nil
  • meta-class对象的superClass指针指向父类的meta-class对象 基类的meta-class对象的superClass指针指向基类的class对象

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们