【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. 类数据的存储

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


相关推荐
用户0917 小时前
SwiftUI Charts 函数绘图完全指南
ios·swiftui·swift
YungFan17 小时前
iOS26适配指南之UIColor
ios·swift
权咚1 天前
阿权的开发经验小集
git·ios·xcode
用户091 天前
TipKit与CloudKit同步完全指南
ios·swift
小溪彼岸1 天前
macOS自带截图命令ScreenCapture
macos
法的空间2 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
2501_915918412 天前
iOS 上架全流程指南 iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传 ipa 与审核实战经验分享
android·ios·小程序·uni-app·cocoa·iphone·webview
TESmart碲视2 天前
Mac 真正多显示器支持:TESmart USB-C KVM(搭载 DisplayLink 技术)如何实现
macos·计算机外设·电脑
00后程序员张2 天前
iOS App 混淆与加固对比 源码混淆与ipa文件混淆的区别、iOS代码保护与应用安全场景最佳实践
android·安全·ios·小程序·uni-app·iphone·webview
Magnetic_h2 天前
【iOS】设计模式复习
笔记·学习·ios·设计模式·objective-c·cocoa