iOS——类与对象底层探索

news2024/11/19 16:28:02

类和对象的本质

当我们使用OC创建一个testClass类并在main函数创建它的实例对象的时候,OC的底层到底是什么样的呢?

首先,我们要了解OC对象的底层结构,那么我们就得知道:OC本质底层实现转化其实都是C/C++代码。
使用下面这行命令将main.m文件转化为c++文件:

clang -rewrite-objc main.m

然后打开c++文件,我们可以看见TestClass的定义实际是:

#ifndef _REWRITER_typedef_TestClass
#define _REWRITER_typedef_TestClass
typedef struct objc_object TestClass;
typedef struct {} _objc_exc_TestClass;
#endif

struct TestClass_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
};

可以看出:

  • OC中的TestClass类底层是struct TestClass_IMPL结构体。
  • OC中@interface TestClass : NSObject,TestClass继承NSObject底层是typedef struct objc_object TestClass;这样体现的。
  • 首先数入参TestClass * self,SEL _cmd,所有OC方法都有这两个隐藏参数,所以我们可以在OC的方法中使用self指针,但是这两个参数对外是不显示的。
  • 看到getter和setter里是通过首地址指针+对应成员变量的地址值指针的偏移量的方式取和存的,最终通过(*(NSString **)还原为string类型。取值的过程就是:先拿到当前成员变量的地址,再去取这个地址里面所存的值。

struct NSObject_IMPL的具体实现:
通过在代码中查找发现:

#ifndef _REWRITER_typedef_NSObject
#define _REWRITER_typedef_NSObject
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
#endif

struct NSObject_IMPL {
	Class isa;
};

该结构体中只有一个Class isa
我们进一步查看Class:

typedef struct objc_class *Class;

发现Class实际上就是一个指针,指向了objc_class类型的结构体。关于objc_class,我们在下面的类结构中讲。

编译发现:

  • TestPerson这个类在底层编译成了TestPerson_IMP结构体。

  • 函数_I_TestPerson_name,实际上就是getter方法,包含两个默认参数self、_cmd。

  • 函数_I_TestPerson_setName_,实际上就是setter方法,包含两个默认参数self、_cmd,与一个形参name。
    根据编译的结构我们可以得出下面结论:

  • 对象的本质在底层就是一个objc_object结构体。

  • 属性与成员变量的区别,属性是由成员变量+getter方法+setter方法组成。

类结构

现在我们来看objc_class,在将obj4源码搜索后可以发现:

struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // Class ISA;
    Class superclass;
    cache_t cache;             // 方法缓存 formerly cache pointer and vtable
    class_data_bits_t bits;    // 用于获取具体的类信息 class_rw_t * plus custom rr/alloc flagsflags

在这段代码中可以看出:类是一个结构体,其中存放了superclass、cache和bits等。objc_class继承于objc_object

  • objc_class继承于objc_object,换句话说,类的本质也是个对象,其中isa指针式其继承自objc_object的,所以代码里才注释了// Class ISA;
    继续搜索objc_object,我们可以找到:
struct objc_object {
private:
    isa_t isa;

public:
//...

会发现isa_t类型的属性isa。

isa

我们去搜一下isa_t,可以看见它的源码:

#include "isa.h"

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

#if ISA_HAS_INLINE_RC
    bool isDeallocating() const {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif // ISA_HAS_INLINE_RC

#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated) const;
    Class getDecodedClass(bool authenticated) const;
};
# if  __ARM_ARCH_7K__ >= 2  ||  (__arm64__ && !__LP64__)
    // armv7k or arm64_32

#   define ISA_BITFIELD                         \
      // 定义 ISA_BITFIELD 宏,用于设置 isa 位域结构
      uintptr_t nonpointer        : 1;
      uintptr_t has_assoc         : 1;
      uintptr_t indexcls          : 15;//indexcls位,用于存储类索引
      uintptr_t magic             : 4;//magic位,用于存储魔术值
      uintptr_t has_cxx_dtor      : 1;//has_cxx_dtor位,表示是否有 C++ 析构函数
      uintptr_t weakly_referenced : 1;//weakly_referenced位,表示是否有弱引用
      uintptr_t unused            : 1//unused位,未使用
      uintptr_t has_sidetable_rc  : 1;//has_sidetable_rc位,表示是否有附加引用计数
      uintptr_t extra_rc          : 7//extra_rc位,表示额外的引用计数

在这个定义中,isa_t 是一个联合体,包含了以下几个部分:

  1. bits:一个无符号整数类型,表示整个 isa 值。
  2. cls:一个类指针,指向对象的类。
  3. 位域结构:包含了多个字段,每个字段表示 isa 指针的一部分。

** 什么是isa**
isa指针保存着指向类对象的内存地址,类对象全局只有一个,因此每个类创建出来的对象都会默认有一个isa属性,保存类对象的地址,也就是class,通过class就可以查询到这个对象的属性和方法,协议等;

isa 的作用

  • 类指针:isa 指针的主要作用是指向对象的类。通过这个指针,运行时可以找到对象的类定义,并调用相应的类方法。
  • 标志位:在现代 Objective-C 运行时中,isa 指针还包含了一些额外的标志位,用来存储对象的元数据。例如,nonpointer 标志表示 isa 是否包含额外的元数据,has_assoc 标志表示对象是否有关联的对象,extra_rc 表示对象的引用计数等。

__arm64__之前,isa仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__架构之后,apple对isa进行了优化,变成了一个共用体(union)结构,同时使用位域来存储更多的信息。
所以现在的isa是结构体,其中会存储类的地址,它已经并不是指针了,“isa指向”这句话严格来说是不正确的,但是方便理解

isa的初始化流程
在这里插入图片描述

bits

在objc_class中我们可以看见:

class_data_bits_t bits;

bits是class_data_bits_t类型的结构体,其源码:


public:

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    void setData(class_rw_t *newData)
    {
        ASSERT(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        // Set during realization or construction only. No locking needed.
        // Use a store-release fence because there may be concurrent
        // readers of data and data's contents.
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }

    // Get the class's ro data, even in the presence of concurrent realization.
    // fixme this isn't really safe without a compiler barrier at least
    // and probably a memory barrier when realizeClass changes the data field
    const class_ro_t *safe_ro() const {
        class_rw_t *maybe_rw = data();
        if (maybe_rw->flags & RW_REALIZED) {
            // maybe_rw is rw
            return maybe_rw->ro();
        } else {
            // maybe_rw is actually ro
            return (class_ro_t *)maybe_rw;
        }
    }


这段代码用于处理类对象的数据,有三个主要的方法:data(), setData(class_rw_t *newData), 和 safe_ro()

  1. data():这个方法返回类对象的数据。 bits & FAST_DATA_MASK 是一个位操作,用于获取存储在 bits 中的类数据。
  2. setData(class_rw_t *newData):这个方法用于设置类对象的数据。它首先检查新数据是否有效,然后使用原子操作更新 bits
  3. safe_ro():这个方法用于安全地获取类对象的只读数据。它首先获取类的数据,然后检查数据的状态。如果数据已经被实现(RW_REALIZED),那么它返回数据的只读版本。如果数据还没有被实现,那么它直接返回数据。
    在这之中比较重要的是class_rw_t class_ro_t
class_rw_t

该结构体用于存储类对象的可读写数据,包括类的实例方法列表、协议列表、属性列表等。当你向一个类添加方法或协议,或者修改属性时,这些信息会被存储在class_rw_t结构体中。
通过runtime动态修改类的方法时,其实是修改在class_rw_t区域中存储的方法列表。
在这里插入图片描述

class_ro_t

该结构体用于存储类对象的只读数据,包括类的名称、父类、实例大小等信息。这些信息在类对象创建时就已经确定,不会在运行时改变,所以被存储在class_ro_t结构体中。
在这里插入图片描述

chche_t

cache_t是一个结构体,用于存储类对象的方法缓存。每个类对象都有一个cache_t成员,这个成员会缓存类对象最近使用的方法。当你向一个类对象发送消息时,Runtime会首先在这个缓存中查找方法。如果找到了,就直接调用缓存的方法,这样可以大大提高方法调用的效率。如果没有找到,Runtime会在类的方法列表中查找,然后将找到的方法添加到缓存中。

指针内存平移

指针内存平移是指通过操作指针的值,移动指针指向的内存位置。在 Objective-C 运行时,这种技术被广泛用于访问对象的成员变量、方法列表等。

为什么category不能添加成员变量

因为category是运行时添加的,他只能操作class_rw_t结构,但是class_rw_t结构没有添加成员变量的入口。成员变量是存储在class_ro_t中的,是无法被修改的,所以category就不能添加成员变量。

对象、类、父类、元类的关系

  1. 对象(Instance)
  • 每个对象是某个类的实例,它包含该类的成员变量和方法。
  • 对象在运行时通过 isa 指针指向它所属的类。
  1. 类(Class)
  • 类是对象的蓝图,定义了对象的成员变量和方法。
  • 类本身也是一个对象,它是 Class 类型的实例。
  • 每个类有一个 isa 指针,指向它的元类(metaclass)。
  1. 父类(Superclass)
  • 类可以继承自另一个类,称为父类或超类。
  • 继承使得子类(subclass)可以使用父类的成员变量和方法,同时可以添加新的成员变量和方法,或重写父类的方法。
  • 每个类有一个 super_class 指针,指向它的父类。
  1. 元类(Metaclass)
  • 元类是类的类,定义了类对象的行为和方法。
  • 每个元类也是一个类,并且有自己的 isa 指针,指向根元类(root metaclass)。
  • 元类也有一个 super_class 指针,指向父类的元类。

元类的定义

其原理就是OC对象在发送消息时,运行时库会追寻着对象的isa指针得到对象所属的类。这个类包含了能应用于这个类的所有实例方法以及指向父类的指针,以便可以找到父类的实例方法。运行时库检查这个类和其父类的方法列表,找到与消息对应的方法。编译器会将消息转换为消息函数objc_msgSend进行调用(这里在消息传递和消息转发的博客里有说)

如图:每一个实例变量的isa都指向自己所属的类,每一个类的isa都指向自己所属的元类,同时,父类的元类也是子类的元类的父类,父类的元类也是根元类的子类。而父类的元类和子类的元类都属于根元类。根元类的isa指向自己,同时根元类的父类也是自己的下属类(NSObject元类的父类是NSObject类):
在这里插入图片描述

从消息机制的层面来讲:

  • 当你给对象发消息时,消息会寻找这个对象的类的方法列表
  • 当你给类发消息时,消息是在寻找这个类的元类的方法列表

类对象

Objective-C中所有对象可以分为3类:实例对象,类对象,元类对象。其中我们开发者常用的继承自NSObject都属于实例对象,实例对象通过isa指针指向的是类对象。类对象通过isa指向的是元类对象。类对象和元类对象拥有同样的结构,都是来自objc_class。

class方法

class方法是一种特殊的类方法。它的主要作用是返回调用该方法的类的类对象。类对象是一个特殊的对象,它包含了类的元数据,如类的名称,类的父类,类的实例变量(ivar)等信息。
class方法一些注意的点:

  • class ObjectClass = [[nsobject class]class]; 返回的还是class对象,并不是meta-class对象。- (Class)class, +(Class)class返回的就是类对象。

class ObjectClass = [[NSObject class] class];这行代码的含义是获取NSObject类对象的类对象。首先,[NSObject class]获取了NSObject的类对象,然后再次调用class方法获取该类对象的类对象,也就是元类对象,然后将这个元类对象赋值给ObjectClass变量。
然而,Objective-C在设计上对这一点进行了特殊处理。当我们调用一个类对象的class方法时,返回的仍然是类对象,而不是元类对象。这也就是为什么class ObjectClass = [[NSObject class] class];这行代码返回的还是类对象,而不是元类对象。
至于- (Class)class和+ (Class)class,它们分别是实例方法和类方法。- (Class)class是对象的实例方法,调用这个方法会返回该对象的类对象。+ (Class)class是类方法,调用这个方法会返回调用它的类的类对象。在Objective-C中,类方法和实例方法是可以有相同的名称的,它们被归类到不同的命名空间中。

  • 在Objective-C中,元类对象和类对象的内存结构是一样的,但它们的用途不同。

类对象包含了类的元数据信息,例如实例变量的布局信息、属性信息、对象方法列表等。当我们创建一个新的对象实例时,这些信息会被用到。
而元类对象则主要包含了类方法的信息。当我们调用一个类方法时,这些信息会被用到。除了类方法的信息,元类对象的其他部分,如实例变量的布局信息、属性信息等,通常都为空的。


//instance实例对象
        NSObject *object1 = [[NSObject alloc] init];
        NSObject *object2 = [[NSObject alloc] init];

        //class对象,类对象
        //objectClass1~5都是NSObject的类对象
        Class objectClass1 = [object1 class];
        Class objectClass2 = [object2 class];
        Class objectClass3 = [NSObject class];
        Class objectClass4 = object_getClass(object1);
        Class objectClass5 = object_getClass(object2);
        
        //元类对象(将类对象当作参数传入进去)
        Class objectMetaClass = object_getClass([NSObject class]);
        Class objectMetaClass2 = [[NSObject class] class];
        
        //判断是不是元类对象

        NSLog(@"instance - %p %p", object1, object2);
        NSLog(@"class - %p %p %p %p %p %d", objectClass1,objectClass2, objectClass3, objectClass4, objectClass5, class_isMetaClass(objectClass3 ));
        NSLog(@"mateClass - %p %p %d",objectMetaClass, objectMetaClass2, class_isMetaClass(objectMetaClass));

输出情况:
instance - 0x100511920 0x10050e840
class - 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0
mateClass - 0x7fff91da20f0 0x7fff91da2118 1

无论多少次class方法得到的都还是类函数。

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

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

相关文章

【scikit-learn010】sklearn算法模型清单实战及经验总结(已更新)

1.一直以来想写下基于scikit-learn训练AI算法的系列文章,作为较火的机器学习框架,也是日常项目开发中常用的一款工具,最近刚好挤时间梳理、总结下这块儿的知识体系。 2.熟悉、梳理、总结下scikit-learn框架模型算法包相关技术点及经验。 3.欢迎批评指正,欢迎互三,跪谢一键…

【重磅丨教育设备】推动大规模设备更新和消费品以旧换新行动方案

近日,国务院印发《推动大规模设备更新和消费品以旧换新行动方案》(以下简称《行动方案》)。《行动方案》提出:实施设备更新行动。提升教育文旅医疗设备水平,推动符合条件的高校、职业院校(含技工院校&#…

数据资产入表-数据治理-标签设计标准

前情提要:数据价值管理是指通过一系列管理策略和技术手段,帮助企业把庞大的、无序的、低价值的数据资源转变为高价值密度的数据资产的过程,即数据治理和价值变现。上一讲介绍了数据清洗标准设计的基本逻辑和思路。 上一讲介绍了其他的通用标…

JVM类加载机制详解(JDK源码级别)

提示:从JDK源码级别彻底剖析JVM类加载机制、双亲委派机制、全盘负责委托机制、打破双亲委派机制的程序、Tomcat打破双亲委派机制、tomcat自定义类加载器详解、tomcat的几个主要类加载器、手写tomcat类加载器 文章目录 前言一、loadClass的类加载大概有如下步骤二、j…

centos8stream 编译安装 php-rabbit-mq模块

官方GitHub:https://github.com/php-amqp/php-amqp 环境依赖安装 dnf install cmake make -y 1.安装rabbitmq-c cd /usr/local/src/ wget https://github.com/alanxz/rabbitmq-c/archive/refs/tags/v0.14.0.tar.gz tar xvf v0.14.0.tar.gz cd rabbitmq-c-0.14.0/…

MYSQL数据库细节详细分析

MYSQL数据库的数据类型(一般只需要用到这些) 整型类型:用于存储整数值,可以选择不同的大小范围来适应特定的整数值。 TINYINTSMALLINTMEDIUMINTINTBIGINT 浮点型类型:用于存储带有小数部分的数值,提供了单精度(FLOA…

调用上传文件接口出现格式错误

一、造成这种错误的可能有很多 1.检查一下传递格式 2.检查一下接口要求的格式 二、举个例子 这两个有什么区别? 那就是json、和form-data,一定要看仔细接口 如果还是按照json的方式去传就会报错 三、更改header里Content-Type的类型 json等的heade…

iOS18 新变化提前了解,除了AI还有这些变化

iOS 18即将在不久的将来与广大iPhone用户见面,这次更新被普遍认为是苹果历史上最重要的软件更新之一。据多方报道和泄露的消息,iOS 18将带来一系列全新的功能和改进,包括在人工智能领域的重大突破、全新的设计元素以及增强的性能和安全性。现…

【成都信息工程大学】只考程序设计!成都信息工程大学计算机考研考情分析!

成都信息工程大学(Chengdu University of Information Technology),简称“成信大”,由中国气象局和四川省人民政府共建,入选中国首批“卓越工程师教育培养计划”、“2011计划”、“中西部高校基础能力建设工程”、四川…

SASAM软件架构静态分析法-系统架构师(六)

1、体系结构权衡分析法(Architecture Tradeoff Analysis Method ATAM)包含四个主要活动领域,分别是 场景和需求的收集、体系结构视图和场景的实现、()、折中。基于场景的架构分析方法(Scenarios-based Arch…

React常见的一些坑

文章目录 两个基础知识1. react的更新问题, react更新会重新执行react函数组件方法本身,并且子组件也会一起更新2. useCallback和useMemo滥用useCallback和useMemo要解决什么3. react的state有个经典的闭包,导致拿不到最新数据的问题.常见于useEffect, useMemo, useCallback4. …

Crosslink-NX器件应用连载(11): 图像(数据)远程传输

作者:Hello,Panda 大家下午好,晚上好。这里分享一个Lattice Crosslink-NX器件实现图像或数据(卫星数据、雷达数据、ToF传感器数据等)远程传输的案例(因为所描述的内容颇杂,晒图不好晒&#xff…

618数码好物推荐!精选便宜又实用的数码产品推荐!

着618购物盛宴的脚步日益临近,你是否已经锁定了心仪的宝贝?那些曾让你心动不已的数码产品,现在正是以最低价收入囊中的绝佳时机。618不仅是一场购物狂欢,更是各大电商平台竞相推出优惠政策的盛宴。为了满足大家的需求,…

C语言笔记第13篇:自定义类型(联合union和枚举enum)

1、联合体 1.1 联合体类型的声明 像结构体一样,联合体也是由一个或多个成员构成,这些成员可以是不同的类型。 但是编译器只为最大的成员分配足够的内存空间,联合体的特点是所有成员共用一块内存空间,所以联合体也叫&#xff1a…

HTML+CSS+JS实现2048经典小游戏(附完整源码)

2048 小游戏的目标是通过合并数字单元格&#xff0c;最终在 4x4 的棋盘上创建一个值为 2048 的单元格。 一、预览效果 二、程序源码 html代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"…

Vue——组件数据传递与props校验

文章目录 前言组件数据传递的几种类型简单字符串类型数据专递其他类型数据(数字、数组、对象)传递注意事项 数据传递值校验限定数据类型 type给定默认值 default指定必选项 required 前言 组件与组件之间并不是完全独立的&#xff0c;他们之间可以进行一些数据的传递操作。传递…

宇宙探索:假如把银河系缩小到一个电子那么小,那宇宙会有多大?

“你知道银河系有多大吗?”“我们可以看到的宇宙有多大呢?”“那么假如把银河系缩小到一个电子那么小&#xff0c;那么我们可以看到的宇宙会有多大呢?”这样一个看似简单的问题却没有一个简单的答案&#xff0c;那么蕴藏其中的玄机是什么? 一、光是无法照亮宇宙的角落。 从…

思维,1209G1 - Into Blocks (easy version)

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 Problem - 1209G1 - Codeforces 二、解题报告 1、思路分析 考虑&#xff1a; 最终状态为若干段相同数字&#xff0c;且任意两段数字不同 每个数字出现的最左下标和最右下标构成一个区间 连锁反应—…

月薪12万招聘AI人才,AI已成为大公司的竞争,小公司难以生存

前言 AI已经成为了时代的风口&#xff0c;这个风口的风力有多大呢&#xff1f; 一个月12-20万。 对&#xff0c;一个月120000-200000元&#xff01; 如果说&#xff0c;步入2024年最火的是什么&#xff0c;那一定就是ChatGPT所引领的开放式AI了&#xff0c;这个东西有多火呢…

AI绘画入门指南!一遍就会!AI绘画Stable Diffusion新手入门教程

我们尝试了一段时间Midjourney&#xff0c;发现其对图片的可控性较弱。于是研究起了Stable Diffusion。 SD的主要优势在于开源&#xff0c;因为开源会有很多无私的大佬分享自己的模型、插件及脚本等&#xff0c;让SD有了更丰富的扩展。在画面统一性和更像本人方面要比MJ容易实…