Swift类对象、实例对象底层那些事

背景

iOS类对象、实例对象本质我们应该都很清楚,但是swift类、对象底层又是怎么样的呢?,这篇文章我们一起研究下。

目标

  • 明白实例对象的创建过程以及内存布局
  • 明白类对象的内部布局以及在oc和swift上的差异

探究

1. 探究对象

初始化过程

示例代码

swift 复制代码
class ClassAndInstence {
    var age: Int = 18
    var name: String = "小李"
}

func main() {
    //类和对象
    let classAndIntence = ClassAndInstence()
    
}

main()

在创建classAndIntence对象的那一行下一个断点

运行... 可以发现创建ClassAndInstence示例调用了__allocating_init函数

因为__allocating_init在swift源码里是看不到的,所以我们需要再往下看看有什么证据可寻

swift源码编译可以参考我的这篇文章

我们可以在xcode下一个符号断点进入__allocating_init的内部实现

再次运行,__allocating_init内部逻辑如下

  • 第一步:执行swift_allocObject创建对象
  • 第二步:执行ClassAndInstence的init方法

swift_allocObject方法在swift源码中的定义如下:

字段 含义
metadata 包名.类名
requiredSize 需要大小
requiredAlignmentMask requiredAlignmentMask

这里的CALL_IMPL的定义主要是用于分发事件,后续可以看到swift源码里的方法调用基本都是通过它进行的

断点继续走就走到CALL_IMPL派发的swift_allocObject 方法里了

  1. 第一步:通过swift_slowAlloc分配内存,分配给
c++ 复制代码
// Apple malloc is always 16-byte aligned. 
#define MALLOC_ALIGN_MASK 15

// When alignMask == ~(size_t(0)), allocation uses the "default"
// _swift_MinAllocationAlignment. This is different than calling swift_slowAlloc
// with `alignMask == _swift_MinAllocationAlignment - 1` because it forces
// the use of AlignedAlloc. This allows manually allocated to memory to always
// be deallocated with AlignedFree without knowledge of its original allocation
// alignment.
//
// For alignMask > (_minAllocationAlignment-1)
// i.e. alignment == 0 || alignment > _minAllocationAlignment:
//   The runtime must use AlignedAlloc, and the standard library must
//   deallocate using an alignment that meets the same condition.
//
// For alignMask <= (_minAllocationAlignment-1)
// i.e. 0 < alignment <= _minAllocationAlignment:
//   The runtime may use either malloc or AlignedAlloc, and the standard library
//   must deallocate using an identical alignment.
//堆中开辟空间(空间大小,内存对齐大小)
void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
  void *p;
  // This check also forces "default" alignment to use AlignedAlloc.
  // 内存对其大小 <= 15 :MAlloc使用默认的内存空间开辟空间,也是16字节对齐
  if (alignMask <= MALLOC_ALIGN_MASK) {
#if defined(__APPLE__) && SWIFT_STDLIB_HAS_DARWIN_LIBMALLOC
    p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
    p = malloc(size);
#endif
  } else { // 内存对齐大小 > 15 : MAlloc使用传入的内存大小进行开辟,也是外部传入的字节对齐
    size_t alignment = (alignMask == ~(size_t(0)))
                           ? _swift_MinAllocationAlignment
                           : alignMask + 1;
    p = AlignedAlloc(size, alignment);
  }
  if (!p) swift::crash("Could not allocate memory.");
  return p;
}
  • 通过判断内存对齐掩码是否大于15 判断不同的开辟内存方式,mac工程传入内存对齐掩码是7,通过以下方式创建内存 如果大于16字节位数,那么使用AlignedAlloc方法
  1. 第二步:通过HeapObject方法构造一个HeapObject对象,并且绑定到object上

  2. 第三步:最后返回一个HeapObject类型的对象

到这里我们知道,swift的类示例对象在底层都是创建一个heapObject类型的对象,我们需要深入研究下heapObject...

HeapObject定义如下:

成员 类型 含义
metadata HeapMetadata 指向元类的指针 占8个字节
refCounts InlineRefCounts 引用计数相关 占8个字节
HeapObject带参数构造 -- --
HeapObject不带参数构造 -- --

看代码是通过HeapObject构造函数调用refCounts初始化引用计数

refCounts 看如下调用栈是调用到了RefCountBitsT类的构造函数设置 后续研究内存管理会重点介绍这里

总结:

  • 对象在底层都是HeapObject类型
  • HeapObject包含信息:
    • metadata指向类对象信息信息
    • refCounts保存引用计数相关

一个对象内存分局

js 复制代码
class ClassAndInstence {
    var age: Int = 18
    var name: String = "葛高召"
}

let classAndIntence = ClassAndInstence()
print(MemoryLayout<Int>.stride)
print(MemoryLayout<String>.stride)
print(class_getInstanceSize(ClassAndInstence.self))
    
  • metadata占8个字节
  • refCounts占8个字节
  • 再加上age的8个字节
  • name占16个字节
  • 所以总共是40个字节

2. 探究类对象

对象在底层中的结构是HeapObject结构体,其第一个属性为metadata,因此从这个属性出发来查看类的结构

查找HeapMetadata

js 复制代码
template <typename Target> struct TargetHeapMetadata;
using HeapMetadata = TargetHeapMetadata<InProcess>;
  • 上文可知对象结构体HeapObject包含有HeapMetadata结构体,对象通过它来查找对应的类信息
  • 点击进入HeapMetadata的定义,发现它是TargetHeapMetaData类型的别名
  • 并且接收了一个参数Inprocess

在HeapObject构造时对HeapMetadata进行定义,传入TargetHeapMetaData的参数Inprocess

TargetHeapMetaData

js 复制代码
template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> {
  using HeaderType = TargetHeapMetadataHeader<Runtime>;

  TargetHeapMetadata() = default;
  constexpr TargetHeapMetadata(MetadataKind kind)
    : TargetMetadata<Runtime>(kind) {}
  constexpr TargetHeapMetadata(TargetAnyClassMetadataObjCInterop<Runtime> *isa)
    : TargetMetadata<Runtime>(isa) {}
};
using HeapMetadata = TargetHeapMetadata<InProcess>;

说明:

  • TargetHeapMetaData其本质是一个模板类型,其中定义了一些所需的数据结构
  • 这个结构体中没有属性,只有初始化方法
  • 初始化方法中传入了一个MetadataKind类型的参数,之后就可以返回TargetMetaData对象
  • 同时可以看到这里传入的kind也就是上面的inprocess了
  • 最后把传入kind传入基础结构体TargetMetadata的初始化方法

TargetMetaData

  • 在TargetMetaData中可以看到有一个Kind属性,这是在构建对象时传入的那个参数

查看MetadataKind

js 复制代码
/// Kinds of Swift metadata records.  Some of these are types, some
/// aren't.
enum class MetadataKind : uint32_t {
#define METADATAKIND(name, value) name = value,
#define ABSTRACTMETADATAKIND(name, start, end)                                 \
  name##_Start = start, name##_End = end,
#include "MetadataKind.def" //包含所有类型的定义
  
  /// The largest possible non-isa-pointer metadata kind value.
  ///
  /// This is included in the enumeration to prevent against attempts to
  /// exhaustively match metadata kinds. Future Swift runtimes or compilers
  /// may introduce new metadata kinds, so for forward compatibility, the
  /// runtime must tolerate metadata with unknown kinds.
  /// This specific value is not mapped to a valid metadata kind at this time,
  /// however.
  LastEnumerated = 0x7FF,
};
  • 可以看到它是uint32_t类型
  • MetadataKind文件定义所有类型

查看MetadataKind

TargetMetaData结构体定义中有一个方法getClassObject,它就可以用来获取类对象

js 复制代码
template <>
  inline const ClassMetadata *Metadata::getClassObject() const {
    switch (getKind()) {
    case MetadataKind::Class: {//如果kind是class
      // Native Swift class metadata is also the class object.
      //将当前指针强转为ClassMetadata类型
      return static_cast<const ClassMetadata *>(this);
    }
#if SWIFT_OBJC_INTEROP
    case MetadataKind::ObjCClassWrapper: {
      // Objective-C class objects are referenced by their Swift metadata wrapper.
      auto wrapper = static_cast<const ObjCClassWrapperMetadata *>(this);
      return wrapper->Class;
    }
#endif
    // Other kinds of types don't have class objects.
    default:
      return nullptr;
    }
  }
  • 在方法中的核心逻辑是通过kind来判断当前是哪种类型
  • 里我们需要的是类类型,因此判断为MetadataKind::Class,就会返回ClassMetadata类型

验证:

命令:

  • po metadata->getKind()
    • 得到其kind是Class
    • po metadata->getClassObject() + x/8g 0x0000000110efdc70
    • 这个地址中存储的是元数据信息!

说明:

  • 传递进来的Kind发现可以判断为类
  • 通过方法调用最后得到的是一个类对象,也就是类
  • 通过x/8g查看类信息,里面就是存储的元数据信息

注意:

  • TargetMetadata 和 TargetClassMetadata 本质上是一样的
  • 因为在内存结构中,可以直接进行指针的转换,所以可以说,我们认为的结构体,其实就是TargetClassMetadata

TargetClassMetadata

js 复制代码
template <typename Runtime>
struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
    ...
    //swift特有的标志
    ClassFlags Flags;
    //实力对象内存大小
    uint32_t InstanceSize;
    //实例对象内存对齐方式
    uint16_t InstanceAlignMask;
    //运行时保留字段
    uint16_t Reserved;
    //类的内存大小
    uint32_t ClassSize;
    //类的内存首地址
    uint32_t ClassAddressPoint;
  ...
}

说明:

  • 如果运行时支持Objective-C互操作性,这个类就会继承TargetAnyClassMetadataObjCInterop,否则它继承自TargetAnyClassMetadata。
  • 包含了很多属性,这些都属于类结构信息

TargetAnyClassMetadataObjCInterop

js 复制代码
struct TargetAnyClassMetadataObjCInterop
    : public TargetAnyClassMetadata<Runtime> {
    constexpr TargetAnyClassMetadataObjCInterop(
      TargetAnyClassMetadataObjCInterop<Runtime> *isa, //isa信息
      TargetClassMetadataObjCInterop *superclass) //superclass信息
      : TargetAnyClassMetadata<Runtime>(isa, superclass),
        CacheData{nullptr, nullptr}, //cache信息
        Data(SWIFT_CLASS_IS_SWIFT_MASK) {} //data信息
...
}
  • 如果支持oc则TargetClassMetadata继承TargetAnyClassMetadataObjCInterop
  • 类中也会存在isa、superclass、cache、data,和OC的底层类结构完全一样

而TargetClassMetadata继承TargetAnyClassMetadata再看下TargetAnyClassMetadata

TargetAnyClassMetadata

js 复制代码
/// The portion of a class metadata object that is compatible with
/// all classes, even non-Swift ones.
template <typename Runtime>
struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
  using StoredPointer = typename Runtime::StoredPointer;
  using StoredSize = typename Runtime::StoredSize;
  using TargetClassMetadata = TargetClassMetadataType<Runtime>;

protected:
  constexpr TargetAnyClassMetadata(
      //isa信息
      TargetAnyClassMetadataObjCInterop<Runtime> *isa,
      //superclass信息
      TargetClassMetadata *superclass)
      : TargetHeapMetadata<Runtime>(isa), Superclass(superclass) {}
public:
  constexpr TargetAnyClassMetadata(TargetClassMetadata *superclass)
      : TargetHeapMetadata<Runtime>(MetadataKind::Class),
        Superclass(superclass) {}

  // Note that ObjC classes do not have a metadata header.

  /// The metadata for the superclass.  This is null for the root class.
  TargetSignedPointer<Runtime, const TargetClassMetadata *
                                   __ptrauth_swift_objc_superclass>
      Superclass;

  /// Is this object a valid swift type metadata?  That is, can it be
  /// safely downcast to ClassMetadata?
  bool isTypeMetadata() const {
    return true;
  }
  /// A different perspective on the same bit.
  bool isPureObjC() const {
    return !isTypeMetadata();
  }
};
  • TargetAnyClassMetadata是所有的类结构,不单单是给Swift用的
  • 继承自TargetHeapMetadata,这也证明类本身也是对象

总结:

  • 类对象在底层都是属于TargetAnyClassMetadata
    • oc时底层最基础类型是TargetAnyClassMetadataObjCInterop,用有和oc一样的成员
    • swift时底层最基础类型是TargetAnyClassMetadata

到这里就完毕,感谢您的阅读,欢迎阅读我的其他文章

相关推荐
missmisslulu15 小时前
电容笔值得买吗?2024精选盘点推荐五大惊艳平替电容笔!
学习·ios·电脑·平板
GEEKVIP16 小时前
手机使用技巧:8 个 Android 锁屏移除工具 [解锁 Android]
android·macos·ios·智能手机·电脑·手机·iphone
GEEKVIP17 小时前
如何在 Windows 10 上恢复未保存/删除的 Word 文档
macos·ios·智能手机·电脑·word·笔记本电脑·iphone
奇客软件17 小时前
iPhone使用技巧:如何恢复变砖的 iPhone 或 iPad
数码相机·macos·ios·电脑·笔记本电脑·iphone·ipad
奇客软件2 天前
如何从相机的记忆棒(存储卡)中恢复丢失照片
深度学习·数码相机·ios·智能手机·电脑·笔记本电脑·iphone
GEEKVIP2 天前
如何修复变砖的手机并恢复丢失的数据
macos·ios·智能手机·word·手机·笔记本电脑·iphone
一丝晨光2 天前
继承、Lambda、Objective-C和Swift
开发语言·macos·ios·objective-c·swift·继承·lambda
GEEKVIP2 天前
iPhone/iPad技巧:如何解锁锁定的 iPhone 或 iPad
windows·macos·ios·智能手机·笔记本电脑·iphone·ipad
KWMax3 天前
RxSwift系列(二)操作符
ios·swift·rxswift