【iOS】—— 类与对象的底层

【iOS】------ 类与对象的底层

      • [1. OC编译生成C++代码的方法](#1. OC编译生成C++代码的方法)
        • [1.1 clang](#1.1 clang)
        • [1.2 xcrun](#1.2 xcrun)
      • [2. 对象的本质](#2. 对象的本质)
        • [2.1 objc_class 和 objc_object 有什么关系?](#2.1 objc_class 和 objc_object 有什么关系?)
        • [2.2 objc_object与对象的关系:](#2.2 objc_object与对象的关系:)
      • [3. 类结构](#3. 类结构)
      • [3.2 Class superclass](#3.2 Class superclass)
      • [3.3 Cache_t cache](#3.3 Cache_t cache)
        • [3.4 class_data_bits_t bits](#3.4 class_data_bits_t bits)
        • [3.5 class_rw_t](#3.5 class_rw_t)
        • [3.6 clas_rw_ext_t](#3.6 clas_rw_ext_t)
        • [3.7 class_ro_t](#3.7 class_ro_t)
        • [3.8 class_ro_t & class_rw_t & class_rw_ext_t & Class之间的关系](#3.8 class_ro_t & class_rw_t & class_rw_ext_t & Class之间的关系)
        • [3.9 category不能添加成员变量的原因](#3.9 category不能添加成员变量的原因)
      • [4. 类数据的存储](#4. 类数据的存储)

对于OC对象的底层结构来说,OC的本质底层实现转化都是C/C++代码。

1. OC编译生成C++代码的方法

先创建一个OC项目,并在里面创建继承自NSObject的类LGPerson:

1.1 clang
objective-c 复制代码
 clang -rewrite-objc main.m -o main.cpp //把⽬标⽂件编译成c++⽂件

在OC文件中.h文件为对外共享的数据文件,真正要编译的是.m文件。

编译成功之后,就会得到.cpp文件,这就是转化后的C++文件。

1.2 xcrun

Xcode安装的时候顺带安装了xcrun命令,xcrun命令在clang的基础上进⾏了⼀些封装,要更好⽤⼀些:

objective-c 复制代码
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o 
main-arm64.cpp //(模拟器) 
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o mainarm64.cpp //(⼿机) 

然后就可以得到相应的.cpp文件。

2. 对象的本质

打开生成的文件,在里面找到定义的类。

通过LGPerson可以发现:

  1. OC中的LGPerson类的底层是struct LGPerson _IMPL结构体。
  2. 其中的@interface LGPerson : NSObject,LGPerson继承NSObject,底层是typedef struct objc_object LGPerson;。

由此可见: OC对象的本质是结构体

然后我们找到struct NSObject_IMPL NSObject_IVARS;这个类型:

struct NSObject_IMPL结构体事实上是一个Class类型的isa指针,因此每一个新定义的类,它对应的结构体当中都会有一个这样的class类型的isa指针。

并且NSObject底层是struct objc_object结构体。

同时可以找到objc的源码:

objective-c 复制代码
struct objc_class : objc_object {
    ...省略无关代码
    // Class ISA;  //ISA(从objc_object继承过来的)
    Class superclass;  //指向其父类
    cache_t cache;  //缓存
    class_data_bits_t bits;  //类的数据
    ...省略无关代码
}

可以看出objc_class是继承objc_object的。

因此:

  1. 每一个类的底层都有一个Class类的isa指针。
  2. class底层是struct objc_class*类型,NSObject底层是struct objc_object结构体,id的底层是struct objc_object *类型。
  3. 然而struct objc_object的实现中可以看到里面只有一个成员变量isa,也就是说NSObject的本质是objc_class。
objective-c 复制代码
 struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};
  1. id底层实现是struct objc_object *类型,怪不得声明id类型变量时 后面不用再加"*"了,因为它定义的时候就定义为了一个Class的指针。
  2. SELstruct objc_selector *类型。
  3. IMPvoid (*)(void )函数指针类型。

然后是定义的name属性的getter,setter方法的底层实现:

  • 首先数入参LGPerson * self,SEL _cmd,所有OC方法都有这两个隐藏参数,所以我们可以在OC的方法中使用self指针,但是这两个参数对外是不显示的。
  • 然后可以看到getter和setter里是通过首地址指针+对应成员变量的地址值指针的偏移量的方式取和存的,最终通过(*(NSString **)还原为string类型。取值的过程就是:先拿到当前成员变量的地址,再去取这个地址里面所存的值。
2.1 objc_class 和 objc_object 有什么关系?
  • 结构体objc_class继承自objc_object,其中objc_object也是一个结构体,而且有一个isa属性,所以objc_class也拥有了isa属性。
  • NSObject是一个类,用它来初始化一个实例对象 objc,objc满足objc_object的特性(有isa属性),主要是因为isa是由NSObject从objc_class继承过来的,而objc_class继承自objc_object,objc_object有isa属性,所以对象都有一个isa,isa 表示指向,来自于当前的objc_object。
  • objc_object是当前的根对象,所以所有的对象都拥有isa 属性。
2.2 objc_object与对象的关系:
  • 所有对象都是以objc_object为模板继承过来的。
  • 所有对象都来自于NSObject,但是其底层是一个objc_object的结构体类型,所以objc_object与对象的关系是继承关系。

3. 类结构

首先看一下runtime底层的数据结构:

上面的objc_class大概是这样的:

objective-c 复制代码
 struct objc_class : objc_object {
    ...省略无关代码
    // Class ISA;  //ISA(从objc_object继承过来的)
    Class superclass;  //指向其父类
    cache_t cache;  //缓存
    class_data_bits_t bits;  //类的数据
    
	class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    ...省略无关代码
}  

从上面的结构体,可以看出:类是一个结构体,里面存放了isa、superClass、cache、bits等。

3.1 Class isa

继承自objc_object的isa指针,不仅实例对象中有,类对象中也有,占8字节。

(1)isa指针是什么?

isa指针保存着指向类对象的内存地址,也就是class,通过class就可以查询到这个对象的属性和方法,协议;

isa指针分为两种:

  • 指针型isa:所有位都用来存放指向的class的地址,可以通过isa的内容来获得类对象的地址。
  • 非指针型isa:isa的值的部分代表Class的地址,多出来的位可以用来存储其他相关内容,来达到节省内存的目的。
(2)isa的数据结构

每个OC对象都含有一个isa指针,__arm64__之前,isa仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__架构之后,apple对isa进行了优化,变成了一个共用体(union)结构,同时使用位域来存储更多的信息。

objective-c 复制代码
 union isa_t 
{
    Class cls;
    uintptr_t bits;
    struct {
         uintptr_t nonpointer        : 1;//->表示使用优化的isa指针
         uintptr_t has_assoc         : 1;//->是否包含关联对象
         uintptr_t has_cxx_dtor      : 1;//->是否设置了析构函数,如果没有,释放对象更快
         uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->类的指针
         uintptr_t magic             : 6;//->固定值,用于判断是否完成初始化
         uintptr_t weakly_referenced : 1;//->对象是否被弱引用
         uintptr_t deallocating      : 1;//->对象是否正在销毁
         uintptr_t has_sidetable_rc  : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1
        uintptr_t extra_rc          : 19;  //->存储引用计数,实际的引用计数减一,存储的是其对象以外的引用计数
    };
};
  • nonpointer:用来标记这个对象是不是tagpointer类型的对象,因为iOS对oc对象进行了优化处理,有些对象是tagpointer类型的,因此这些对象是没有isa指针的,tagpointer的内存一般是在栈中的,而不是在堆里面;tagpointer对象一般是NSNumber类型的数值较小的数,或NSString类型的较小的字符串。
  • has_assoc:用来标记有没有关联对象。
  • has_cxx_dtor:该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。
  • shiftcls:存储的isa指针地址,也就是类对象的地址。
  • magic:用于调试器判断当前对象是真的对象还是没有初始化的空间。
  • weakly_referenced:对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放。
  • deallocating:标志对象是否正在释放内存。
  • has_sidetable_rc:标记对象是否使用了Sidetable,当对象引用计数大于10时,则需要借用该变量存储进位。
  • extra_rc:当表示该对象的引用计数值,实际上是引用计数值减1, 例如:如果对象的引用计数为10,那么extra_rc为9。如果引用计数大于10,则需要使用到下面的 has_sidetable_rc。
(3)原理探索

进入initInstanceIsa的探索,首先查看它的源码实现:

objective-c 复制代码
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

主要是调用了initIsa,继续跳到initIsa的源码:

objective-c 复制代码
 inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 

    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif

        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

从上面可看到初始化isa有两种方法:

  • 通过cls初始化:

​ 非nonpointer,存储着Class,Meta-Class对象的内存地址信息

  • 通过bits初始化:

    nonpointer,经过一系列初始化操作。

3.2 Class superclass

superclass指向该对象或者该类的父类,class*本身也是一个指针。

3.3 Cache_t cache


cache缓存,追踪进去看一下cache_t结构体的类型,而不是结构体指针类型,所以需要计算。

objective-c 复制代码
 struct cache_t {
    struct bucket_t *_buckets; // 8  散列表
    mask_t _mask;  // 4  散列长度-1
    mask_t _occupied; // 4  已经缓存的方法数量
}
#if __LP64__
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif

struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    MethodCacheIMP _imp;//函数的内存地址
    cache_key_t _key;//SEL作为key
#else
    cache_key_t _key;//SEL作为key
    MethodCacheIMP _imp;//函数的内存地址
#endif
}

cache_t的类型,由代码中标注的出来的内存大小,可以算出最后计算出的cache类的内存大小为 12 + 2 + 2 = 16 字节。

同时这里引入了bucket_t(散列表),cache_t哈希表结构,哈希表内部存储的bucket_t,bucket_t中存储的是SEL和IMP的键值对。

3.4 class_data_bits_t bits

class_data_bits_t作为属性bits的类型,也是个结构体,其数据结构如下:

objective-c 复制代码
 struct class_data_bits_t {
    friend objc_class;//这里声明objc_class为class_data_bits_t的友元类,使得objc_class可以访问class_data_bits_t中的私有方法
    uintptr_t bits;
    ......

从源码中可也看到,class_data_bits_t 有一个属性uintptr_t bits,uintptr_t是无符号整数类型,能够存储指针,可以提高内存的利用率,它不止存储class_rw_t结构体指针,还存储其他的信息。

objective-c 复制代码
 class_rw_t *data() const {
    return bits.data();
}
void setData(class_rw_t *newData) {
    bits.setData(newData);
}
3.5 class_rw_t
objective-c 复制代码
 struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif
    explicit_atomic<uintptr_t> ro_or_rw_ext;
    Class firstSubclass;
    Class nextSiblingClass;
private:
    using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
    const ro_or_rw_ext_t get_ro_or_rwe() const {
        return ro_or_rw_ext_t{ro_or_rw_ext};
    }
    void set_ro_or_rwe(const class_ro_t *ro) {
        ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed);
    }
    void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
        // the release barrier is so that the class_rw_ext_t::ro initialization
        // is visible to lockless readers
        rwe->ro = ro;
        ro_or_rw_ext_t{rwe}.storeAt(ro_or_rw_ext, memory_order_release);
    }
    class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
public:
    void setFlags(uint32_t set)
    {
        __c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
    }
    void clearFlags(uint32_t clear) 
    {
        __c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
    }
    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear) 
    {
        ASSERT((set & clear) == 0);
        uint32_t oldf, newf;
        do {
            oldf = flags;
            newf = (oldf | set) & ~clear;
        } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
    }
    class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>();
    }
    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>();
        } else {
            return extAlloc(v.get<const class_ro_t *>());
        }
    }
    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }
    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 *>();
    }
    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            v.get<class_rw_ext_t *>()->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }
    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};
        }
    }
};

从上面可以看到结构体中有const method_array_t methods()const property_array_t properties() 等这样的方法,因此类结构中的方法列表、属性列表是从这里获取的,

接下来我们先来看看class_rw_ext_tclass_ro_t的数据结构,看看为什么class_rw_t可以从他们这里读取类相关信息。

3.6 clas_rw_ext_t

class_rw_ext_t是个结构体,其数据结构如下:

objective-c 复制代码
 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;
};

结构体class_rw_ext_t中的确有方法列表、属性列表和协议等信息,而且还有一个class_ro_t指针,接下来继续看class_ro_t

3.7 class_ro_t

以下是class_ro_t的结构体

objective-c 复制代码
 struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif
    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;
    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
    _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            return _swiftMetadataInitializer_NEVER_USE[0];
        } else {
            return nil;
        }
    }
    method_list_t *baseMethods() const {
        return baseMethodList;
    }
    class_ro_t *duplicate() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
            ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
            return ro;
        } else {
            size_t size = sizeof(*this);
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
            return ro;
        }
    }
};

可以发现class_ro_t不仅有baseMethodListbaseProtocolsbaseProperties等信息。

接下来从结构上分析一下他们之间的关系:首先class_rw_t有一个指针ro_or_rw_extro_or_rw_ext指向可能是class_rw_ext_t或者是class_ro_tro_or_rw_ext指针的作用是,当读取类相关信息时,会优先判断是否指向class_rw_ext_t,如果是class_rw_ext_t就从它读取,没有就是class_ro_t,直接从class_ro_t读取。

3.8 class_ro_t & class_rw_t & class_rw_ext_t & Class之间的关系
  • 名词解释

​ 从字面上解读:class_ro_t的ro代表的是"只读",class_rw_t中的rw代表的是"读写",class_rw_ext_t中的rw_ext表示可读可写的扩展。

干净内存和脏内存:干净内存指的是内存一旦加载就不会被改变,脏内存就是在运行时会被修改。

  • 从生成时机的角度来说,

​ ro编译阶段生成,rw运行的时候生成。

  • 存储的内容角度来讲

​ **ro中有方法、属性、协议和成员变量,而rw中并没有成员变量。**rw中的方法属性协议的取值方法中,也是通过取ro或者rwe中的值来获得。ro中的方法、属性、协议都是base,也就是只有本类中的方法属性和协议。

​ Class本身是运行时加载的,在运行时会被改变,所以本身Class就是属于脏内存。那么如果想要获取Class的干净内存,也就是编译时确定的数据结构包括方法列表、成员变量等的,该怎么办?

这其实就是class_ro_t的作用。因为class_ro_t是只读,意味着 class_ro_t是从mach-o读取类的数据之后,就不会被改变。那如果我们想在运行时修改类的信息,比如添加方法,比如加载category 怎么办呢?那这时候就有一个与之对应的class_rw_t结构,class_rw_t可以在运行时存储类的信息,可读可写的,可以在运行时修改。

3.9 category不能添加成员变量的原因

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

4. 类数据的存储

总结一下结构关系,如下图所示:


相关推荐
dnekmihfbnmv9 分钟前
好用的电容笔有哪些推荐一下?年度最值得推荐五款电容笔分享!
ios·电脑·ipad·平板
黑果魏叔31 分钟前
macOS Sequoia 正式版(24A335)黑苹果/Mac/虚拟机系统镜像
macos
tekin2 小时前
macos macport软件包管理工具 sudo port install xxx 安装的软件的路径 与 brew install xxx 软件安装路径总结
macos·brew·port·macport·port install·port软件包安装路径·brew软件包安装路径
Hellc0074 小时前
MacOS升级ruby版本
前端·macos·ruby
GEEKVIP10 小时前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑
逢生博客16 小时前
Mac 搭建仓颉语言开发环境(Cangjie SDK)
macos·华为·鸿蒙
Rverdoser19 小时前
MacOS Catalina 从源码构建Qt6.2开发库之01: 编译Qt6.2源代码
macos
Magnetic_h19 小时前
【iOS】单例模式
笔记·学习·ui·ios·单例模式·objective-c
归辞...20 小时前
「iOS」——单例模式
ios·单例模式·cocoa
GEEKVIP1 天前
如何在没有备份的情况下恢复 Mac 上丢失的数据
经验分享·笔记·安全·macos·电脑·笔记本电脑·改行学it