Swift类与结构体(swift类和结构体的区别)
一、异变方法
Swift 中 class 和 struct 都能定义方法。但是有一点区别的是默认情况下,struct内属性不能被自身的实例方法修改。
struct Point { var x = 0.0, y = 0.0 func movePoint(x deltaX: Double, y deltaY: Double) { x += deltaX y += deltaY } } 复制代码
编译运行,会看到错误警告“Left side of mutating operator isn't mutable: 'self' is immutable”。此时可以在方法前加“mutating”关键字来进行修改。
//添加 mutating关键字 struct Point { var x = 0.0, y = 0.0 mutating func movePoint(x deltaX: Double, y deltaY: Double) { x += deltaX y += deltaY } } 复制代码
我们通过生成的.sil文件来看下添加mutating后的方法与之前的方法有什么区别。(生成Swift sil文件的脚本命令:swiftc main.swift -emit-sil),可以使用Xcode打开生成的.sil文件。
1、未添加“mutating”关键字 2、添加关键字“mutating”关键字后 可以看到参数内都会有个Point,但是添加关键字“mutating”后Point前多了一个@inout ,@inout是什么呢,sil文档的说明:An @inout parameter is indirect. The address must be of an initialized object.(当前参数类型是间接的,传递的是已经初始化过的地址)。接着看箭头%5所指的地方,未添加“mutating”前是let self = Ponit,添加“mutating”后是var self = &Point。
通过以上分析,我们可以得出异变方法的本质:对于变异方法, 传入的 self 被标记为 inout 参数。无论在 mutating 方法 内部发生什么,都会影响外部依赖类型的一切。
二、输入输出参数
如果我们想函数能够修改一个形式参数的值,而且希望这些改变在函数结束之后依然生效,那么就需要将形式参数定义为输入输出形式参数。在形式参数定义开始的时候在前边 添加一个"inout"关键字可以定义一个输入输出形式参数:
var age = 26 func editeAge(age: inout Int) { //修改完后 会直接影响外部age的数值 age = 18; } 复制代码
三、方法调用
我们知道OC中的方法调用本质是objc_msgSend, 那Swift中的方法调用是个什么过程呢?
class Teacher{ func teach(){ print("teach") } func teach1(){ print("teach1") } func teach2(){ print("teach2") } } 复制代码
我们看下生成的.sil文件 可以看到3个方法是在vtable中,我们打开Swift源码文件。metadata数据结构如下
struct Metadata{ var kind: Int var superClass: Any.Type var cacheData: (Int, Int) var data: Int var classFlags: Int32 var instanceAddressPoint: UInt32 var instanceSize: UInt32 var instanceAlignmentMask: UInt16 var reserved: UInt16 var classSize: UInt32 var classAddressPoint: UInt32 var typeDescriptor: UnsafeMutableRawPointer var iVarDestroyer: UnsafeRawPointer 复制代码
这里我们有一个东⻄需要关注“typeDescriptor”,不管是 Class,Struct, Enum 都有自己的 Descriptor,就是对类的一个详细描述
struct TargetClassDescriptor{ var flags: UInt32 var parent: UInt32 var name: Int32 var accessFunctionPointer: Int32 var fieldDescriptor: Int32 var superClassType: Int32 var metadataNegativeSizeInWords: UInt32 var metadataPositiveSizeInWords: UInt32 var numImmediateMembers: UInt32 var numFields: UInt32 var fieldOffsetVectorOffset: UInt32 var Offset: UInt32 var size:UInt32 //V-Table } 复制代码
1、打开Swift源码后我们先找到metadata
struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> { using StoredPointer = typename Runtime::StoredPointer; using StoredSize = typename Runtime::StoredSize; ...... private: /// An out-of-line Swift-specific description of the type, or null /// if this is an artificial subclass. We currently provide no /// supported mechanism for making a non-artificial subclass /// dynamically. //类的描述信息 TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description; .... } 复制代码
2、点击进入类的描述信息“TargetClassDescriptor”内
class TargetClassDescriptor final : public TargetTypeContextDescriptor<Runtime>, public TrailingGenericContextObjects<TargetClassDescriptor<Runtime>, TargetTypeGenericContextDescriptorHeader, ..... } 复制代码
在这个类内部发现没有vtable,我们尝试内部搜索下“TargetClassDescriptor”,发现有个
using ClassDescriptor = TargetClassDescriptor<InProcess>; 复制代码
3、全局搜索“ClassDescriptor”,定位“GenMeta.cpp”文件
class ClassContextDescriptorBuilder: public TypeContextDescriptorBuilderBase<ClassContextDescriptorBuilder, ClassDecl>, public SILVTableVisitor<ClassContextDescriptorBuilder> { using super = TypeContextDescriptorBuilderBase; ClassDecl *getType() { return cast<ClassDecl>(Type); } // Non-null unless the type is foreign. ClassMetadataLayout *MetadataLayout = nullptr; Optional<TypeEntityReference> ResilientSuperClassRef; SILVTable *VTable; bool Resilient; bool HasNonoverriddenMethods = false; .... void layout() { super::layout(); addVTable(); addOverrideTable(); addObjCResilientClassStubInfo(); maybeAddCanonicalMetadataPrespecializations(); } } 复制代码
layout先调用父类“TypeContextDescriptorBuilderBase”的layout创建Descriptor
class TypeContextDescriptorBuilderBase: public ContextDescriptorBuilderBase<Impl> { ...... void layout() { asImpl().computeIdentity(); super::layout(); asImpl().addName(); asImpl().addAccessFunction(); asImpl().addReflectionFieldDescriptor(); asImpl().addLayoutInfo(); asImpl().addGenericSignature(); asImpl().maybeAddResilientSuperclass(); asImpl().maybeAddMetadataInitialization(); } } 复制代码
接着调用自己的vatable方法。 B代表当前的Descriptor,先设置vtable的size,接着遍历vatble,添加函数指针。
void addVTable() { ...... if (VTableEntries.empty()) return; auto offset = MetadataLayout->hasResilientSuperclass() ? MetadataLayout->getRelativeVTableOffset() : MetadataLayout->getStaticVTableOffset(); B.addInt32(offset / IGM.getPointerSize()); B.addInt32(VTableEntries.size()); for (auto fn : VTableEntries) emitMethodDescriptor(fn); } 复制代码
经过以上分析,Descriptor加上偏移量就是方法的起始地址。下面我们可以通过分析macho来验证以上过程。 类,结构体,enum地址信息都存放在Section64(__TEXT__swift5_types)里,可以计算下0xFFFFFBF4 + 0xBC68 = 0x10000B85C 0x10000在Swift里是虚拟内存的地址 0xB85C 就是 Descriptor在data里的内存地址 该内存地址偏移13个4字节(TargetClassDescriptor类中的13个UInt32)得到vtable的地址 0xB890(macho中的地址),再加上ASLR得到实际地址 0x0000000100044000 + 0xB890 = 0x10004F890(teach函数的TargetMethodDescriptor地址)
struct TargetMethodDescriptor { MethodDescriptorFlags Flags; // 4 TargetRelativeDirectPointer<Runtime, void> Impl; // offset }; 复制代码
0x10004F890 + 0x4(Flags) = 0x10004F894 0x10004F894 + 0xFFFFC220 = 0x20004BAB4(0x10004BAB4 teach函数地址)
打开Debug-Debug Workflow-Always ShowDisassembly,运行程序。
作者:NSNull
链接:https://juejin.cn/post/7047010218152525861