【iOS】类对象的结构分析

news2024/9/21 2:36:53

目录

    • 对象的分类
    • object_getClass和class方法
    • isa流程和继承链分析
      • isa流程实例验证
      • 类的继承链实例验证
    • 类的结构
      • cache_t结构
      • bits分析
      • 实例验证
        • 属性properties
        • 方法methods
        • 协议protocols
        • ro
        • 类方法
      • 类结构流程图解


对象的分类

OC中的对象主要可以分为3种:实例对象(instance)、类对象(class)和元类对象(meta-class)

实例对象

通过类alloc出来的对象,每次调用alloc都会产生新的instance对象

NSObject* obj1 = [[NSObject alloc] init];
NSObject* obj2 = [[NSObject alloc] init];
NSLog(@"%p %p", obj1, obj2);
//  打印结果:0x600000180040 0x600000180050

从运行结果可看出以上是不同的两个实例对象,分别占据着两块不同的内存
实例对象在内存中存储的信息包括:isa指针、其他成员变量

类对象

#import <objc/runtime.h>
Class objectClass1 = [obj1 class];
Class objectClass2 = [obj2 class];
Class objectClass3 = [NSObject class];
Class objectClass4 = object_getClass(obj1);  //Runtime API
Class objectClass5 = object_getClass(obj2);  //Runtime API
//  打印结果:0x1d6fc6070 0x1d6fc6070 0x1d6fc6070 0x1d6fc6070 0x1d6fc6070

以上都是NSObject的类对象,从运行结果可看出它们都是同一个对象,即这些指针指向的是同一块内存,每个类在内存中有且只有一个class对象
类对象在内存中存储的信息主要包括:isa指针、superclass指针、类的属性信息(@property)、类的对象方法信息(instance method)、类的协议信息(protocol)、类的成员变量(ivar,类型、名称等描述信息而不是具体的值)

元类对象

看下面如何获取元类对象(元类对象类型仍是一个类对象,底层都是struct objc_class* Class,只是包含的信息不一样)

Class objectMetaClass = object_getClass(object_getClass(obj1));

将类对象作为参数传入,再次调用object_getClass函数

那如果调用两次class方法呢?

Class objectMetaClass2 = [[NSObject class] class];
NSLog(@"%p %p %d", objectMetaClass, objectMetaClass2, class_isMetaClass(objectMetaClass));
//  打印结果:0x1d6fc6020 0x1d6fc6070 1 0

从打印结果可以看出,class不管调多少次返回的一直是类对象,不会是元类对象
每个类只有一个元类对象,元类对象在内存中存储的信息主要包括:isa指针、superclass指针以及类方法信息

object_getClass和class方法

查看objc4源码

object_getClass方法中传入各种对象,通过访问isa,返回不同的类对象:

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

//  传入类名字符串,返回对象的类对象
Class objc_getClass(const char *aClassName)
{
    if (!aClassName) return Nil;

    // NO unconnected, YES class handler
    return look_up_class(aClassName, NO, YES);
}

class方法直接返回类对象:

//+ (id)self {
//    return (id)self;
//}
//- (id)self {
//    return self;
//}

+ (Class)class {
    return self;
}

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

//+ (Class)superclass {
//    return self->getSuperclass();
//}
//- (Class)superclass {
//    return [self class]->getSuperclass();
//}

isa流程和继承链分析

上面我们了解了对象的分类,认识到不同类型对象的差别,那么是什么让这些不同类型的对象联系起来从而构成OC对象体系的呢?

上经典老图:

请添加图片描述

isa指向链

实际上就是isa指针将它们联系起来形成 isa指向链

  • 实例对象instanceisa指向类class
  • 类对象class也有isa指向的是元类meta
  • 元类meta中也有isa指向的是根元类root meta

在这里插入图片描述
当调用对象方法时,通过实例对象的isa找到class,最后找到对象方法的实现进行调用
当调用类方法时,通过类对象的isa找到meta-class,最后找到类方法的实现进行调用

类继承链

根据superclass的指向,也可总结出OC类的继承链

  • 子类继承于父类,父类继承于根类,根类指向的是nil
  • 在元类中也存在继承,子类的元类继承于父类的元类,父类的元类继承于根元类,根元类又继承与根类

在这里插入图片描述

当Student的实例对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到Person的class,最后找到对象方法的实现进行调用

类似地,当Student的类对象要调用Person的类方法时,会先通过isa找到Student的meta-class,然后通过superclass找到Person的meta-class,最后找到类方法的实现进行调用

isa流程实例验证

Person类继承于NSObject,Student类继承于Person

@interface Person : NSObject {
    @public
    int _age;
}

- (void)personInstanceMethod;
+ (void)personClassMethod;

@end

@interface Student : Person {
    @public
    int _no;
}

- (void)studentInstanceMethod;
+ (void)studentClassMethod;

@end

打断点,通过LLDB查看isa关联类的地址:

//  打印出实例的地址
Person* person = [Person alloc];
NSLog(@"%@", person);
Student* student = [Student alloc];
NSLog(@"%@", student);

类对象的地址和实例对象isa所指向的地址有所出入,isa需要进行一次位运算,才能计算出类对象的真实地址
在获取到对象的isa值后,可以通过&(按位与)一个掩码ISA_MASK 0x007ffffffffffff8ULL来获取到对象关联的类地址:
在这里插入图片描述

根据student实例的isa地址找到关联类Student的地址0x00000001000082d8

在这里插入图片描述

同样地,根据Student类对象的isa找到Student元类的地址0x00000001000082b0

在这里插入图片描述

根据Student元类对象的isa找到关联类的地址0x00000001d6fc6020

在这里插入图片描述

找到NSObject类对象的isa关联类地址0x00000001d6fc6020,与Student元类对象的isa关联类地址一致,可以验证元类的isa指向根元类,且根元类的isa指向自己

在这里插入图片描述

类的继承链实例验证

Class tClass = [Student class];
Class pClass = class_getSuperclass(tClass);
Class nClass = class_getSuperclass(pClass);
Class rClass = class_getSuperclass(nClass);
NSLog(@"\n tClass-%@ \n pClass-%@ \n nClass-%@ \n rClass-%@ \n", tClass, pClass, nClass, rClass);

在这里插入图片描述
可看出类对象的继承链:Student->Person->NSObject->nil

Student * student = [Student alloc];
Class tClass = object_getClass(student);
Class mtClass = object_getClass(tClass);
Class mtSuperClass = class_getSuperclass(mtClass);
NSLog(@"\n student %p 实例对象 -- %p 类 -- %p 元类 -- %p 元类父类", student, tClass, mtClass, mtSuperClass);
Person * person = [Person alloc];
Class pClass = object_getClass(person);
Class mpClass = object_getClass(pClass);
Class mpSuperClass = class_getSuperclass(mpClass);
NSLog(@"\n person %p 实例对象 -- %p 类 -- %p 元类 -- %p 元类父类", person, pClass, mpClass, mpSuperClass);
NSObject * obj = [NSObject alloc];
Class objClass = object_getClass(obj);
Class mobjClass = object_getClass(objClass);
Class mobjSuperClass = class_getSuperclass(mobjClass);
NSLog(@"\n NSObject %p 实例对象 -- %p 类 -- %p 元类 -- %p 元类父类 == %p NSObject类对象", obj, objClass, mobjClass, mobjSuperClass,
[NSObject class]);

在这里插入图片描述

可看出元类的继承链:Student Meta-class -> Person Meta-class -> NSObject Meta-class -> NSObject class -> nil

类的结构

前面我们了解到了Class的类型是struct objc_class*结构体指针类型,下面就来分析一下这个结构体的定义

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
	//  ...其他代码,objc_class定义共计531行代码...
};

继承于objc_object说明:

  • 还有一个继承过来的Class类型变量isa
  • superclass:指向父类的指针
  • cache:缓存相关
  • bits:用于获取具体的类信息

cache_t结构

cache_t是一个结构体

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8字节
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask; // uint32_t 4字节
#if __LP64__
            uint16_t                   _flags;     // 2字节
#endif
            uint16_t                   _occupied;  // 2字节
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;  // 8字节
    };
 };
//  此段为部分代码,cache_t定义总共有290行

分析整个cache_t的结构,发现cache_t的内存总共为16字节,后面会对其底层进行学习

bits分析

objc_class里有一段源码是data操作

class_rw_t *data() const {
    return bits.data();
}
void setData(class_rw_t *newData) {
    bits.setData(newData);
}

dataclass_rw_t类型,下面是其部分源码:

在这里插入图片描述

ro:成员变量、methods:方法、properties:属性、protocols协议
我们在类中定义的方法、属性等就是通过调取class_rw_t结构体中的方法获取的

实例验证

下面通过实例来验证一下类的结构是否如上面一致
创建Person类继承于NSObject,定义一些属性、方法以及协议:

@protocol PersonDelegate<NSObject>

- (void)personDelegateMethod;
// 让Person类遵守并实现此协议方法
@end

@interface Person : NSObject<PersonDelegate> {
    NSString* hobby;
}

@property (nonatomic, strong)NSString* name;
@property (nonatomic, assign)NSInteger age;

- (void)sayHello;
+ (void)sayWorld;

@end

LLDB调试输出

请添加图片描述

第一个地址0x0000000100008470是类的第一个成员isa,第二个地址0x00000001d6fc6070是类的第二个成员superclass
isasuperclass都是结构体指针类型,占用8字节,cache结构体占用16字节,XYPerson的地址加上8 + 8 + 16 = 32就可以得到bits的地址

请添加图片描述

相加并强转为class_data_bits_t *类型得到bits的地址0x0000000100008270,再调用data()方法就得到类型为class_rw_t的地址

属性properties

调用class_rw_tproperties()方法,得到property_array_t类型的数组,继承于list_array_tt,找到list下的ptr

请添加图片描述

class property_array_t :
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    typedef list_array_tt<property_t, property_list_t, RawPtr> Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};

ptrproperty_list_t类型,继承于entsize_list_tt

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};

entsize_list_tt部分源码:

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;  //  数量

    uint32_t entsize() const {
        return entsizeAndFlags & ~FlagMask;
    }
    uint32_t flags() const {
        return entsizeAndFlags & FlagMask;
    }

    Element& getOrEnd(uint32_t i) const {
        ASSERT(i <= count);
        return *PointerModifier::modify(*(List *)this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
    }
    Element& get(uint32_t i) const {  //  获取元素方法
        ASSERT(i < count);
        return getOrEnd(i);
    }
    //  ...其他代码...
};

通过调用get()方法,获取元素,下面的结果就是Person类的nameageproperties()里,而实例变量hobby不在这里

请添加图片描述

方法methods

调用class_rw_tmethods()方法,得到method_array_t类型的数组,继承于list_array_tt,同样找到list下的ptr

请添加图片描述

这里看到ptrmethod_list_t类型,同样继承于entsize_list_tt,其中有count为6,调用get()方法查看输出

请添加图片描述

这里的元素为method_t类型,method_t为结构体类型,其中的一个成员变量为big的结构体,里面是方法名称等信息:

struct method_t {
    method_t(const method_t &other) = delete;

    // The representation of a "big" method. This is the traditional
    // representation of three pointers storing the selector, types
    // and implementation.
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
//  ...其他代码
};

调用big方法查看输出

请添加图片描述

这6个方法分别是:

  • 实例方法:sayHello
  • 属性nameageset/get方法
  • C++析构函数:.cxx_destruct

且都是实例方法,并没有类方法sayWorld

协议protocols

调用class_rw_tprotocols()方法,得到protocol_array_t类型的数组,继承于list_array_tt,同样找到list下的ptr

请添加图片描述

这里protocol_list_t并没有继承于entsize_list_tt

struct protocol_list_t {
    // count is pointer-sized by accident.
    uintptr_t count;
    protocol_ref_t list[0]; // variable-size

    size_t byteSize() const {
        return sizeof(*this) + count*sizeof(list[0]);
    }

    protocol_list_t *duplicate() const {
        return (protocol_list_t *)memdup(this, this->byteSize());
    }

    typedef protocol_ref_t* iterator;
    typedef const protocol_ref_t* const_iterator;

    const_iterator begin() const {
        return list;
    }
    iterator begin() {
        return list;
    }
    const_iterator end() const {
        return list + count;
    }
    iterator end() {
        return list + count;
    }
};

看到protocol_list_t的定义,我们知道count值为1,说明是有值,但是其成员是protocol_ref_tuintptr_t类型,那怎么输出查看这个count中的1到底是什么呢

在这里插入图片描述

查看protocol_ref_t的定义,通过注释信息,我们可以看到protocol_ref_t未映射到protocol_t类型,那我们就找protocol_t的定义

在这里插入图片描述
这里看到protocol_t中有mangledName以及instanceMethods等,只要得到protocol_t就可以输出我们想要的名称方法等信息,怎么才能从protocol_ref_t映射到protocol_t呢,全局找一下吧

在这里插入图片描述

这里我们看到,protocol_ref_t是可以强转protocol_t的,那我们就试试:

请添加图片描述

强转成功,调用demangledName方法,我们就得到了LGPersonDelegate,那我们再找一下协议方法

请添加图片描述

按照method查看输出的步骤,成功找到协议方法personDelegateMethod

ro

调用class_rw_tro方法,得到class_ro_t的结构体

请添加图片描述
请添加图片描述

查看ivars,也是继承于entsize_list_ttivar_list_t类型的结构体,调用get方法查看:

请添加图片描述

这6个实例变量分别是自定义hobby以及系统自动帮我们自动生成的带有_的实例变量

类方法

methods中的方法全部都存在类中,都是实例方法,那么类方法应该去在元类中找

请添加图片描述

通过类的isa指针找到元类,再根据上面的步骤找到并输出这个元类的methods

这里我们不由地想,OC的底层是C/C++实现的,不存在对象方法和类方法的区分,有的都是函数实现,在OC的设计中,一个类可以new出无数个对象,因此把方法存在类中,而不是动态创建的对象中,是合理的。
因为OC的对象方法和类方法的定义是-+的区分,那么方法名称就会有重名的存在,因此才会引入元类的概念,元类的存在就是解决类方法重名的问题

类结构流程图解

类的结构流程图解析:

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1933358.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

江协科技51单片机学习- p27 I2C AT24C02存储器

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

动手学深度学习——5.卷积神经网络

1.卷积神经网络特征 现在&#xff0c;我们将上述想法总结一下&#xff0c;从而帮助我们设计适合于计算机视觉的神经网络架构。 平移不变性&#xff08;translation invariance&#xff09;&#xff1a;不管检测对象出现在图像中的哪个位置&#xff0c;神经网络的前面几层应该对…

C++从入门到起飞之——类的定义/实例化 全方位剖析!

个人主页&#xff1a;秋风起&#xff0c;再归来~ C从入门到起飞 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克心守己&#xff0c;律己则安&#xff01; 目录 1.类的定义 1.1、类定义格式 1.2、访问限定符 1.3、类域 2.实例化 2.…

基于Vue CLI 3构建Vue3项目(Vue2也可参考)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

基于嵌入式Linux的高性能车载娱乐系统设计与实现 —— 融合Qt、FFmpeg和CAN总线技术

随着汽车智能化的发展&#xff0c;车载娱乐系统已成为现代汽车的标配。本文介绍了一个基于Linux的车载娱乐系统的设计与实现过程。该系统集成了音视频娱乐、导航、车辆信息显示等功能&#xff0c;旨在提供安全、便捷、丰富的驾驶体验。 1. 项目概述 随着汽车智能化的发展&…

基于Java的在线考试系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Java MySQL B/S架构 SpringBoot框架 工具&#xff1a;Eclipse、MySQL环境配置工具 系统展示 首…

备忘录删除了怎么恢复 备忘录误删恢复办法

备忘录作为我们日常生活中的得力助手&#xff0c;帮助我们记录重要事项和灵感&#xff0c;然而&#xff0c;使用中偶尔会出现误删的情况。若不能及时找回误删的内容&#xff0c;可能会造成重要信息的丢失&#xff0c;给我们的工作和生活带来不必要的麻烦。 如果你也担心备忘录…

【权威发布】2024年金融科技与信息技术国际会议(ICFTIT 2024)

2024年金融科技与信息技术国际会议 2024 International Conference on Financial Technology and Information Technology 【1】会议简介 2024年金融科技与信息技术国际会议将在中国成都隆重举行。本次会议围绕“金融创新”、“金融科技”与“信息技术”等核心议题&#xff0c;…

【STC89C51单片机】定时器中断系统

中断概念 中断是一种重要的硬件机制&#xff0c;用于在处理器正在执行程序时&#xff0c;能够及时响应某些外部或内部事件。中断可以临时中止当前正在执行的指令序列&#xff0c;转而去执行专门的中断服务程序&#xff08;ISR&#xff0c;Interrupt Service Routine&#xff0…

navicat15已连接忘记密码

1.导出链接 2.使用文本打开 connections.ncx UserName"root" PasswordXXXX 3.复制加密密码&#xff0c;在线解密 代码在线运行 - 在线工具 php解密代码 <?php class NavicatPassword {protected $version 0;protected $aesKey libcckeylibcckey;protected…

沙尘传输模拟教程(基于wrf-chem)

沙尘传输模拟教程(基于wrf-chem) 文章目录 沙尘传输模拟教程(基于wrf-chem)简介实验目的wrf-chem简介 软件准备wps、wrf-chem安装conda安装ncl安装ncap安装 数据准备气象数据准备下垫面数据准备 WPS数据预处理namelist.wps的设置geogrid.exe下垫面处理ungrib.exe气象数据预处理…

ppt文本框复制到word自动缩进的问题

ppt里的字是无缩进的&#xff1a; 复制粘贴到word中&#xff0c;突然出现2字符缩进&#xff1a; 微软官方嘴硬说没问题我也是无语&#xff01;&#xff01;word保留原格式复制后&#xff0c;出现莫名其妙的缩进 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直…

C++ | Leetcode C++题解之第239题滑动窗口最大值

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> maxSlidingWindow(vector<int>& nums, int k) {int n nums.size();vector<int> prefixMax(n), suffixMax(n);for (int i 0; i < n; i) {if (i % k 0) {prefixMax[i] num…

阿里云短信PHP集成api类

无需安装sdk扩展包&#xff0c;直接引入类即可使用 V3版本请求体&签名机制:自研请求体和签名机制 - 阿里云SDK - 阿里云 模版内容&#xff1a; <?phpnamespace common\components;use common\constant\UserConst; use common\models\bee\SmsReferer; use common\mode…

git镜像链接

镜像链接https://registry.npmmirror.com/binary.html?pathgit-for-windows/ CNPM Binaries Mirror 1.git init 2.克隆 IDEA集成git git分支

WSL-Ubuntu20.04部署环境配置

1.更换Ubuntu软件仓库镜像源 为了在WSL上使用TensorRT进行推理加速&#xff0c;需要安装以下环境&#xff0c;下面将按以下顺序分别介绍安装、验证以及删除环境&#xff1a; #1.C环境配置 gcc、gdb、g #2.gpu环境 cuda、cudnn #3.Cmake环境 CMake #4.OpenCV环境 OpenCV #5.Ten…

13.8和13.11哪个大?网友吵翻!多个大模型翻车

13.8和13.11哪个更大&#xff1f; 没想到 这样一道简单的数学题 竟引发网友激烈讨论 还难倒了不少大模型 13.8%和13.11%哪个大&#xff1f; 《歌手》排名引网友热议 上周 最新一期的《歌手》公布排名 孙楠得票13.8% 外国歌手香缇莫得票13.11% 引发了网友对排名的质疑…

3.动态规划.题目3

3.动态规划.题目3 题目23.买卖股票的最佳时机3-困难24.买卖股票的最佳时机425.买卖股票的最佳时机含冷冻期26.买卖股票的最佳时机含手续费27.最长递增子序列28.最长连续递增序列29.最长重复子数组30.最长公共子序列31.不相交的线 编辑距离总结 题目 23.买卖股票的最佳时机3-困…

探索LangFlow 1.0:多代理RAG应用程序的未来

在当今快速发展的科技时代&#xff0c;自动化和人工智能&#xff08;AI&#xff09;已经成为了许多企业和个人提高效率的关键工具。对于那些对科技有兴趣的用户来说&#xff0c;寻找一种既创新又易于使用的解决方案是非常重要的。今天&#xff0c;我们将介绍一款名为LangFlow的…

排序算法(4)之快速排序(1)

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 排序算法(4)之快速排序(1) 收录于专栏【数据结构初阶】 本专栏旨在分享学习数据结构学习的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目…