阅读 258

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.jpg 2、添加关键字“mutating”关键字后 1.jpg 可以看到参数内都会有个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文件 image.png 可以看到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来验证以上过程。 截屏2021-12-29下午5.36.02.png 类,结构体,enum地址信息都存放在Section64(__TEXT__swift5_types)里,可以计算下0xFFFFFBF4 + 0xBC68 = 0x10000B85C 0x10000在Swift里是虚拟内存的地址 截屏2021-12-29下午5.39.57.png 0xB85C 就是 Descriptor在data里的内存地址 截屏2021-12-29下午5.42.51.png 该内存地址偏移13个4字节(TargetClassDescriptor类中的13个UInt32)得到vtable的地址 0xB890(macho中的地址),再加上ASLR得到实际地址 截屏2021-12-29下午6.00.29.png 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,运行程序。 截屏2021-12-29下午6.17.44.png


作者:NSNull
链接:https://juejin.cn/post/7047010218152525861


文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐