美文网首页
Swift -- 2.类与结构体(下)

Swift -- 2.类与结构体(下)

作者: MissStitch丶 | 来源:发表于2022-01-07 01:29 被阅读0次

    一.异变方法

    1.值类型方法

    Swift中的class和struct都能定义方法。但是有一点区别的是默认情况下,值类型的属性不能被自身的实例方法修改。

    struct Point {
        var x = 0.0, y = 0.0
        func moveBy(x deltaX: Double, y deltaY: Double) {
            x += deltaX
            y += deltaY
        }
    }
    

    代码会报错,因为在moveBy方法内修改x或y值相当于修改其本身(self)

    要想使得值类型在实例方法里修改其属性,需要加上关键字mutating

    struct Point {
        var x = 0.0, y = 0.0
        mutating func moveBy(x deltaX: Double, y deltaY: Double) {
            x += deltaX
            y += deltaY
        }
    }
    

    2.分析mutating关键字

    这里给Point添加了一个普通方法test

    struct Point {
        var x = 0.0, y = 0.0
        
        func test(){
            let tmp = self.x
        }
        
        mutating func moveBy(x deltaX: Double, y deltaY: Double) {
            x += deltaX
            y += deltaY
        }
    }
    

    生成SIL文档

    // Point.test()
    sil hidden @$s4main5PointV4testyyF : $@convention(method) (Point) -> () {
    // %0 "self"                                      // users: %2, %1
    bb0(%0 : $Point):
      debug_value %0 : $Point, let, name "self", argno 1 // id: %1
      %2 = struct_extract %0 : $Point, #Point.x       // user: %3
      debug_value %2 : $Double, let, name "tmp"       // id: %3
      %4 = tuple ()                                   // user: %5
      return %4 : $()                                 // id: %5
    } // end sil function '$s4main5PointV4testyyF'
    
    • 函数默认参数类型为Point,也就是self。(OC中函数默认的参数,self、_cmd
    • debug_value %0 : $Point, let, name "self", argno 1,相当于let self = Point
    // Point.moveBy(x:y:)
    sil hidden @$s4main5PointV6moveBy1x1yySd_SdtF : $@convention(method) (Double, Double, @inout Point) -> () {
    // %0 "deltaX"                                    // users: %10, %3
    // %1 "deltaY"                                    // users: %20, %4
    // %2 "self"                                      // users: %16, %6, %5
    bb0(%0 : $Double, %1 : $Double, %2 : $*Point):
      //声明一个deltaX赋值给%0
      debug_value %0 : $Double, let, name "deltaX", argno 1 // id: %3
      //声明一个deltaY赋值给%1
      debug_value %1 : $Double, let, name "deltaY", argno 2 // id: %4
      debug_value_addr %2 : $*Point, var, name "self", argno 3 // id: %5
      debug_value_addr %2 : $*Point, var, name "self", argno 3 // id: %5
      ...
    } // end sil function '$s4main5PointV6moveBy1x1yySd_SdtF'
    
    • 加上mutating默认传入的参数变为@inout Point
    • debug_value_addr %2 : $*Point, var, name "self", argno 3,相当于var self = &Point

    SIL文档对@inout的解释
    An @inout parameter is indirect. The address must be of an initialized object.(当前参数类型是间接的,传递的是已经初始化过的地址)

    异变方法的本质:对于变异方法,传入的self被标记为inout参数。无论在mutating方法内部发生什么,都会影响外部依赖类型的一切。也就是说,mutating标记的方法,可以修改本身的值

    输入输出参数:如果我们想函数能够修改一个形式参数的值,并且希望这些改变在函数结束之后依然生效,那么久需要将形式参数定义为输入输出形式参数。在形式参数定义开始的时候在前边添加一个inout关键字可以定义一个输入输出形式参数

    var age = 10
    
    //函数的形式参数都是let类型的
    func modifyAge(_ age: inout Int) {
        age += 1
    }
    
    modifyAge(&age) //传入的是age地址
    
    print(age) // 11
    

    3.代码案例来理解mutating

    struct Point {
        var x = 0.0, y = 0.0
        
        func test(){
            let tmp = self.x
        }
        
        mutating func moveBy(x deltaX: Double, y deltaY: Double) {
            x += deltaX
            y += deltaY
        }
    }
    
    var p = Point()
    
    //相当于test方法传入的p, let self = Point
    let x1 = p
    
    //相当于moveBy方法传入的&p, var self = &Point
    var x2 = withUnsafePointer(to: &p){return $0}
    
    var x3 = p // 相当于值拷贝,2个独立的结构体实例
    
    p.x = 30.0
    //x2和x3的x值会发生变化吗?
    //x2的会发生变化,x3的值不会发生变化
    //x2和p是2个一模一样的实例,指向同一一块内存空间
    
    print(x2.pointee.x) // 30.0
    print(x3.x) // 0
    

    对应SIL

    debug_value_addr %0 : $*Int, var, name "age", argno 1 // id: %1
    相当于 var age = &age
    

    二.方法调度

    对于Objective-C来说通过objc_msgSend进行方法调度
    那么,在Swift中的方法调度是怎么样的呢

    1.汇编分析

    class LGTeacher{
        func teach(){
            print("teach")
            
        }
    }
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            var t = LGTeacher()
            t.teach()
        }
    }
    

    创建项目,使用真机调试,查看汇编来分析方法调度
    t.teach()打上断点,进入汇编调试模式

    我们知道在汇编代码中blr代表着有返回值的跳转,将断点移到blr x8表示跳转到x8寄存器中的地址

    LGTeacher.teach

    进来后发现,这里面就是方法teach的调用
    此时,虽然找到了teach的函数调用,那么这和方法调度有什么关系呢?我们继续往下

    在Teacher中再添加2个teach方法

    class LGTeacher{
        func teach(){
            print("teach")
        }
        
        func teach1(){
            print("teach1")
        }
        
        func teach2(){
            print("teach2")
        }
    }
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            var t = LGTeacher()
            t.teach()
            t.teach1()
            t.teach2()
        }
    }
    

    teach函数的调用过程:找到metadata,确定函数地址(metadata + 偏移量),执行函数。
    teach、teach1、teach2相差的就是函数指针的大小,在内存地址上是连续的内存空间

    一些汇编指令
    bl:(branch)跳转到某地址(无返回)
    blr:跳转到某地址(有返回)
    
    mov:将某一寄存器的值复制到另一寄存器(只能用于寄存器与寄存器或者寄存器于常量之间传值,不能用于内存地址):
    mov x1, x0  将寄存器x0的值复制到寄存器x1中
    
    add: 将某一寄存器的值和另一寄存器的值 相加 并将结果保存在另一寄存器中:
    add x0, x1, x2   将寄存器x1和x2的值相加后保存到寄存器x0中
    
    sub: 将某一寄存器的值和另一寄存器的值 相减 并将结果保存在另一寄存器中:
    sub x0, x1, x2   将寄存器x1和x2的值相减后保存到寄存器x0中
    
    and:将某一寄存器的值和另一个寄存器的值 按位与 并将结果保存到另一寄存器中:
    and x0, x0, #0x1   将寄存器x0的值和常量1按位与后保存到寄存器x0中
    位与符号是&,真值表达式为: 1&1=1,1&0=0,0&1=0,0&0=0
    
    orr:将某一寄存器的值和另一寄存器的值 按位或 并将结果保存到另一寄存器中:
    orr x0, x0, #0x1   将寄存器x0的值和常量1按位或后保存到寄存器x0中
    位或符号是|,真值表达式为: 1|1=1,1|0=1,0|1=1,0|0=0
    
    str:将寄存器中的值写入到内存中:
    str x0, [x0, x8]    将寄存器x0的值保存到栈内存[x0 + x8]处
    
    ldr:将内存中的值读取到寄存器中:
    ldr x0, [x1, x2]    将寄存器x1和寄存器x2的值相加作为地址,取该内存地址的值放入寄存器x0中
    
    cbz: 和 0 比较,如果结果为零就转移(只能跳到后面的指令)
    
    cbnz: 和非 0 比较,如果结果非零就转移(只能跳到后面的指令)
    
    cmp: 比较指令
    
    ret: 子程序(函数调用)返回指令,返回地址已默认保存在寄存器 lr (x30) 中
    
    

    2.SIL上查看sil_vtable

    sil_vtable LGTeacher {
      #LGTeacher.teach: (LGTeacher) -> () -> () : @$s14ViewController9LGTeacherC5teachyyF   // LGTeacher.teach()
      #LGTeacher.teach1: (LGTeacher) -> () -> () : @$s14ViewController9LGTeacherC6teach1yyF // LGTeacher.teach1()
      #LGTeacher.teach2: (LGTeacher) -> () -> () : @$s14ViewController9LGTeacherC6teach2yyF // LGTeacher.teach2()
      #LGTeacher.init!allocator: (LGTeacher.Type) -> () -> LGTeacher : @$s14ViewController9LGTeacherCACycfC // LGTeacher.__allocating_init()
      #LGTeacher.deinit!deallocator: @$s14ViewController9LGTeacherCfD   // LGTeacher.__deallocating_deinit
    }
    

    3.源码找到vtable

    类与结构体(上)总结的Metadata中有一个重要的字段typeDescriptor,类型描述

    //TargetClassMetadata中
    TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
    
    //进入TargetClassDescriptor,发现它有一个别名,全局搜索ClassDescriptor查找在哪里构建这个结构体
    using ClassDescriptor = TargetClassDescriptor<InProcess>;
    
    //进入GenMeta.cpp,找到ClassContentDescriptorBuilder
    void layout() {
       super::layout();
       addVTable();
       addOverrideTable();
       addObjCResilientClassStubInfo();
       maybeAddCanonicalMetadataPrespecializations();
     }
    
    //super.layout
    void layout() {
      asImpl().computeIdentity();
    
      super::layout();
      asImpl().addName();
      asImpl().addAccessFunction();
      asImpl().addReflectionFieldDescriptor();
      asImpl().addLayoutInfo();
      asImpl().addGenericSignature();
      asImpl().maybeAddResilientSuperclass();
      asImpl().maybeAddMetadataInitialization();
    }
    
    //addVTable()
    void addVTable() {
      LLVM_DEBUG(
        llvm::dbgs() << "VTable entries for " << getType()->getName() << ":\n";
        for (auto entry : VTableEntries) {
          llvm::dbgs() << "  ";
          entry.print(llvm::dbgs());
          llvm::dbgs() << '\n';
        }
      );
    
      // Only emit a method lookup function if the class is resilient
      // and has a non-empty vtable, as well as no elided methods.
      if (IGM.hasResilientMetadata(getType(), ResilienceExpansion::Minimal)
          && (HasNonoverriddenMethods || !VTableEntries.empty()))
        IGM.emitMethodLookupFunction(getType());
    
      //VTableEntries可以理解为数组
      if (VTableEntries.empty()) 
        return;
      
      //计算了一个偏移量
      auto offset = MetadataLayout->hasResilientSuperclass()
                      ? MetadataLayout->getRelativeVTableOffset()
                      : MetadataLayout->getStaticVTableOffset();
      //B就是TargetClassDescriptor
      //添加偏移量
      B.addInt32(offset / IGM.getPointerSize());
      //添加vtable的size
      B.addInt32(VTableEntries.size());
      //遍历数组,添加函数指针
      for (auto fn : VTableEntries)
        emitMethodDescriptor(fn);
    }
    
    //通过TargetClassDescriptor、TargetTypeContextDescriptor、TargetContextDescriptor的源码总结出的TargetClassDescriptor
    //TargetClassDescriptor继承自TargetTypeContextDescriptor
    //TargetTypeContextDescriptor继承自TargetContextDescriptor
    
    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
    
        //对应上面添加的size =》B.addInt32(VTableEntries.size());
        var size: UInt32
    
        //V-Table
    }
    

    4.Mach-O验证TargetClassDescriptor

    Mach-O:其实是Mach Object文件格式的缩写,是mac以及iOS上可执行文件的格式,类似于Windows上的PE(Portable Executable)Linux上的elf格式(Executable and Linking Format)。常见的.o.a.dylib Frameworkdylib.dsym

    Mach-O文件格式:


    Mach-O
    • 首先是文件头,表明该文件是Mach-O格式,制定目标架构,还有一些其他的文件属性信息,文件头信息影响后续文件的文件结构
    • Load commands是一张包含很多内容的表。内容包括区域的位置、符号表、动态表等。
    name info
    LC_SEGMENT_64 将文件中(32位或64位)的段映射到进程地 址空间中
    LC_DYLD_INFO_ONLY 动态链接相关信息
    LC_SYMTAB 符号地址
    LC_DYSYMTAB 动态符号表地址
    LC_LOAD_DYLINKER dyld加载
    LC_UUID 文件的UUID
    LC_VERSION_MIN_MACOSX 支持最低的操作系统版本
    LC_SOURCE_VERSION 源代码版本
    LC_MAIN 设置程序主线程的入口地址和栈大小
    LC_LOAD_DYLIB 依赖库的路径,包含三方库
    LC_FUNCTION_STARTS 函数起始地址表
    LC_CODE_SIGNATURE 代码签名
    • Data区主要就是负责代码和数据记录的。Mach-O是以Segment这种结构来组织数据的,一个Segment可以包含0个或多个Section。根据Segment是映射的哪一个Load Command,Segment中section就可以被解读为是代码、常量或者一些其他的数据类型。在装载在内存中时,也是根据Segment做内存映射的
    Mach64 Header.png Load Commands
    • __PAGEZERO主要是将低地址占用,防止用户访问。这里的VM Size就是Mach-o的基地址
    • 程序在运行时,会加上ASLR(地址空间布局随机化),来保证程序运行的安全
    Memory Data
    • OC的类存放在Section64(_DATA_CONST,__objc_classlist)
    • Swift的类、结构体、枚举存放在Section64(__TEXT,__swift5_types),每4字节作为一个区分,具体存放的是TargetClassDescriptor地址信息。

    在这里第一个4字节就是我们今天要验证的LGTeacherTargetClassDescriptor

    swift_types
    这里的第一个4字节,小端模式,读取为0xFFFFFBA8
    0xFFFFFBA8   +  0xBBCC  = 0x10000B774(Descriptor在Mach-O文件的内存地址)
    
    减去虚拟内存基地址(VM Address): 0x100000000
    得出地址为:0xB774(Descriptor在Data数据区的内存地址)
    

    找到0xB774,熟悉Mach-O的应该知道,这肯定在_TEXT_const

    0xB774
    • 这个地方就是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
    
        //对应上面添加的size =》B.addInt32(VTableEntries.size());
        var size: UInt32
    
        //V-Table
    }
    

    这个是我们之前查看源码总结的TargetClassDescriptor数据结构,根据该数据结构,在0xB774进行偏移得到vtable的内存地址,偏移12个4字节

    vtable起始位置
    • size为0x00000004
    • 0xB7A8就是teachMach-O中的信息

    验证这个0xFFFFC04400000010就是我们的teach信息

    1.使用image list找到ASLR(地址空间布局随机性,Address Space Layout Randomization),也就是程序在启动的时候随机偏移了一个地址,也称为程序的基地址。

    (lldb) image list
    [  0] 12C1648A-0E28-3BB8-A1F1-CCD13EDCBE38 0x0000000104a68000 /Users/zt/Library/Developer/Xcode/DerivedData/projectTest-bmemtiaawffbzbcslwyavvzqqwmh/Build/Products/Debug-iphoneos/projectTest.app/projectTest 
    
    • 0x0000000104a68000就是程序运行的基地址
    1. 0x0000000104a68000 + 0xB7A8(偏移量) = 0x104A737A8 就是函数在内存的地址
      此时0x104A737A8指向的就是Mach-O中的0xFFFFC04400000010,也就是上图中的vtable起始位置

    3.源码查看swift函数在内存的数据结构

    struct TargetMethodDescriptor {
      /// Flags describing the method.
      MethodDescriptorFlags Flags; //标识方法的类型,4字节
    
      /// The method implementation.
      TargetRelativeDirectPointer<Runtime, void> Impl; //imp的指针,存储的offset
    
      // TODO: add method types or anything else needed for reflection.
    };
    
    此时算出的0x104DA37A8就是指向的teach(TargetMethodDescriptor)
    
    偏移Flags(4字节),加上offset(0xFFFFC044)
    0x104A737A8 + 0x4 + 0xFFFFC044  = 0x204A6F7F0
    减去虚拟基地址0x100000000(VM Address)
    得到0x104A6F7F0
    

    4.汇编验证函数地址
    在执行到第一个x8寄存器时,读取x8的值

    (lldb) register read x8
          x8 = 0x0000000104a6f7f0  projectTest`projectTest.LGTeacher.teach() -> () at ViewController.swift:11
    

    5.至此证明了TargetClassDescriptor数据结构

    Swift代码

    import UIKit
    
    class LGTeacher{
        func teach(){
            print("teach")
        }
        
        func teach1(){
            print("teach1")
        }
        
        func teach2(){
            print("teach2")
        }
    }
    
    class ViewController: UIViewController {
    
        //ASLR:地址空间分布随机性(offset)
        
        //使用image list获取程序基地址(ASLR offset + __PAGEZERO) 0x0000000104a68000
        //0x0000000104a68000 + 0xB7A8 = 0x104A737A8
        
        //加上偏移0x4,加上offset, 减去程序的基地址0x100000000得到方法函数的内存地址
        //0x104A737A8 + 0x4 + 0xFFFFC044 - 0x100000000 = 0x104A6F7F0
        
        /*
         (lldb) register read x8
               x8 = 0x0000000104a6f7f0  projectTest`projectTest.LGTeacher.teach() -> () at ViewController.swift:11
         */
        
        override func viewDidLoad() {
            let t = LGTeacher()
            t.teach()
            t.teach1()
            t.teach2()
        }
    }
    

    三.影响函数派发方式

    1.结构体派发

    struct LGTeacher{
        func teach(){
            print("teach")
        }
        
        func teach1(){
            print("teach1")
        }
        
        func teach2(){
            print("teach2")
        }
    }
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            let t = LGTeacher()
            t.teach()
            t.teach1()
            t.teach2()
        }
    }
    
    • 可以看到,这里的3个方法在汇编代码中是直接派发的。代码编译后,函数地址就确定了。

    • 为什么?
      值类型没有继承关系,因此也就不需要记录我们的函数,编译器就优化为了静态调用,类似于静态函数

    • 源码查看Struct是否存在vtable

    进入GenMeta.cpp,找到StructContextDescriptorBuilder(与ClassContextDescriptorBuilder对应)
    
    //laout
    void layout() {
      super::layout();
      maybeAddCanonicalMetadataPrespecializations();
    }
    
    void addLayoutInfo() {
      // uint32_t NumFields;
      B.addInt32(getNumFields(getType()));
    
      // uint32_t FieldOffsetVectorOffset;
      B.addInt32(FieldVectorOffset / IGM.getPointerSize());
    }
    
    并没有vtable的相关方法
    

    2.使用extension添加的方法

    1.使用extension给Struct添加一个方法

    extension LGTeacher {
        func teach3() {
            print("teach3")
        }
    }
    

      Struct通过extension添加的方法是静态派发

    2.使用extension给Class添加一个方法

    image.png

      类通过extension添加的方法也是静态派发

    3.理解为什么类使用extension不写入函数表里面

    vtable示意图

      这里Teacher有2个函数teachteach1,LGPartTeacher有4个函数(继承了2个函数teachteach1和自身的teach2teach3)。假如这2个类在A.swift文件中,在B.swift文件中给LGTeacher添加teach4函数。当程序在编译A.swift时,编译了这2个类,此时vtable就生成了。此时程序再编译到B.swift时,LGTeacher中有一个extension,如果要在vtable添加的话,就应该在teach1下追加一个teach4。对于LGPartTeacher来说就应该加载teach1下面,但是LGPartTeacher已经确定了,再在中间插入一个MethodDescriptor,需要移动之前的teach2teach3指针,还需要有一个下标来记录位移的index来保证extension添加的方法能够找到位置插入,此番操作代价太昂贵,因此编译器将extension添加的方法优化为静态调用不会影响原有的vtable结构

    3.方法调度的总结

    类型 调用方式 extension
    值类型 静态派发 静态派发
    Swift类 函数表派发 静态派发
    NSObject类 函数表派发 静态派发

    4.影响函数派发方式

    • staticprivatefileprivate添加后,会变成静态派发
    • final添加了final关键字的函数无法被重写,使用静态派发,不会在vtable中出现,且对objc运行时不可见
    //实际开发过程中属性、方法、类不需要被重载的时候,使用final
    class LGTeacher{
        //写上了final关键字,静态派发
        final func teach(){
            print("teach")
        }
    }
    
    let t = LGTeacher()
    t.teach()
    
    ->  0x102bf7bd8 <+56>:  bl     0x102bf771c               ; projectTest.LGTeacher.teach() -> () at ViewController.swift:12
    
    • dynamic函数均可添加dynamic关键字,为非objc类和值类型的函数赋予动态性,但派发方式还是函数表派发。也就是可以动态的替换,使用了该字段才能使用编译器字段@_dynamicReplecement(for:xx)来替换方法的imp。注意,@_dynamicReplecement只能在extension里使用
    class LGTeacher{
        //依然还是函数表的调度
        dynamic func teach(){
            print("teach")
        }
    }
    
    extension LGTeacher {
        //将teach的imp改为了tech3
        //具体可以查看SIL代码。实际上是保留了2个代码的imp,一个orignal_entry,一个forward_to_replace
        @_dynamicReplacement(for: teach)
        func teach3() {
            print("teach3")
        }
    }
    
    let t = LGTeacher()
    t.teach()
    
    执行结果为teach3
    
    • @objc该关键字可以将Swift函数暴露给Objc运行时,依旧是函数表派发。

    • @objc + dynamic消息派发的方式,objc_msgSend。能够使用Runtime Api来动态修改。

    class LGTeacher {
    
        //@objc,将方法暴露给OC,函数表派发
        //dynamic,方法具有动态性,函数表派发
        //二者结合过后变为,消息派发(objc_msgSend)
    
        //加上@objc + dynamic变为消息调度的机制,objc_msgSend
        //此时就可以使用Runtime Api,method-swizzling、...
        @objc dynamic func teach(){
            print("teach")
        }
      
    }
    

    此时此刻,对于这个teach函数OC来说,能调用得到吗?
    答案肯定是调用不到的,因为它是一个纯粹的Swift类。我们也可以在Xcode中查看xxxx-swift.h(Swift暴露给OC的声明文件)中是否有这个类。

    进入发现没有LGTeacher类的相关信息。但是可以在Swift代码中对@objc dynamic标记的函数使用Runtime Api

    比如当前类使用method-swizzling
    
    class LGTeacher {
        
        //加上@objc变为消息调度的机制,objc_msgSend
        //此时就可以使用Runtime Api,method-swizzling、...
        @objc dynamic func teach(){
            print("teach")
        }
        
        @objc dynamic func teach1(){
            print("teach1")
        }
        
        func teach2(){
            print("teach2")
        }
    }
    
    extension LGTeacher {
        //将teach的imp改为了tech3
        //具体可以查看SIL代码。实际上是保留了2个代码的imp,一个orignal_entry,一个forward_to_replace
        @_dynamicReplacement(for: teach)
        func teach3() {
            print("teach3")
        }
    }
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            
            let teachSel = #selector(LGTeacher.teach)
            let teach1Sel = #selector(LGTeacher.teach1)
            
    //        let teachImp = class_getMethodImplementation(LGTeacher.self, teachSel)
    //        let teach1Imp = class_getMethodImplementation(LGTeacher.self, teach1Sel)
    //
    //        //替换方法
    //        class_replaceMethod(LGTeacher.self, teachSel, teach1Imp!, nil)
    //        class_replaceMethod(LGTeacher.self, teach1Sel, teachImp!, nil)
            
            let teachMethod = class_getInstanceMethod(LGTeacher.self, teachSel)
            let teach1Method = class_getInstanceMethod(LGTeacher.self, teach1Sel)
            
            method_exchangeImplementations(teachMethod!, teach1Method!)
            
            let t = LGTeacher()
            t.teach()
            t.teach1()
            
    //        执行结果
    //        teach1
    //        teach3
        }
    }
    
    

    如果想让OC使用到这个类,必须让这个类继承自NSObject

    此时在xxx-swift.h中就有了相关的声明
    
    SWIFT_CLASS("_TtC11projectTest9LGTeacher")
    @interface LGTeacher : NSObject
    - (void)teach;
    - (void)teach1;
    - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
    @end
    

    四.函数内联

    函数内联是一种编译器优化技术,它通过使用方法的内容替换直接调用该方法,从而优化性能。

    • 将确保有时内联函数。这是默认行为,我们无需执行任何操作,Swift编译器可能会自动内联函数作为优化
    • always将确保始终内联函数。通过在函数前添加@inline(__always)来实现此行为
    • never将确保永远不会内联函数。这可以通过在函数前添加@inline(never)来实现
    • 如果函数很长并且想避免增加代码段大小,请使用@inline(never)
    //始终内联函数
    @inline(__always) func test() {
        print("test")
    }
    
    //永远不内联函数
    @inline(never) func test1() {
        print("test1")
    }
    
    工程中优化选项

    1.分析代码在不同优化等级下汇编代码

    class ViewController: UIViewController {
    
        override func viewDidLoad() {
    
            let a = sum(a: 1, b: 2)
        
        }
        
    }
    
    func sum(a:Int, b:Int) -> Int {
        return a + b
    }
    
    • 默认Not Optimization
      Not Optimization
    0x102ce3a9c <+428>: mov    w8, #0x1   将1复制到w8寄存器
    0x102ce3aa4 <+436>: mov    w8, #0x2   将2复制到w8寄存器
    0x102ce3aac <+444>: bl     0x102ce3af0               ; 
    projectTest.sum(a: Swift.Int, b: Swift.Int) -> Swift.Int at ViewController.swift:90  执行sum函数
    
    • 修改Optimization LevelOptimize for Speed
      此时细心的你就会发现如果还是将断点断在let a = sum(a: 1, b: 2)下一行的话,此时不会进入断点。因为编译已经优化掉了,此时的let a = sum(a: 1, b: 2)其实就是3。因此,加上打印语句,断点下载print
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
    
            let a = sum(a: 1, b: 2)
            print(a)
        }
        
    }
    
    func sum(a:Int, b:Int) -> Int {
        return a + b
    }
    
    
    Optimize for speed
    0x102bfd4e4 <+188>: mov    w8, #0x3   //将3复制到w8寄存器中
    此时,在汇编里面已经没有关于sum函数调用了。编译器直接将1+2=3计算出来了
    

    这样的行为就称为编译器优化技术

    2.private、fileprivate对函数派发的影响

    • 如果对象只在声明的文件中可见,可以用privatefileprivate进行修饰。编译器会对privatefileprivate对象进行检查,确保没有其他继承关系的情形下,自动打上final标记,进而使得对象获得静态派发的特性(fileprivate只允许在定义的源文件中访问,private定义的声明 中访问)
    class LGPerson{
        
        private var sex: Bool
        
        private func unpdateSex(){
            self.sex = !self.sex
        }
        
        init(sex innerSex: Bool) {
            self.sex = innerSex
        }
        
        func test() {
            self.unpdateSex()
        }
    }
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
    
            let t = LGPerson(sex: true)
            t.test()
        }
        
    }
    

    在函数test中的self.unpdateSex()下一个断点

    private_静态派发
    • 此时可以发现函数unpdateSex静态派发了,那是否vtable里没有这个函数了?
    此时我们发现vtable里依然有unpdateSex,只是编译器针对private将函数调用方式优化了
    
    sil_vtable LGPerson {
      #LGPerson.sex!getter: (LGPerson) -> () -> Bool : @$s14ViewController8LGPersonC3sex33_37ACD668159BB52851391EE68C0B8918LLSbvg   // LGPerson.sex.getter
      #LGPerson.sex!setter: (LGPerson) -> (Bool) -> () : @$s14ViewController8LGPersonC3sex33_37ACD668159BB52851391EE68C0B8918LLSbvs // LGPerson.sex.setter
      #LGPerson.sex!modify: (LGPerson) -> () -> () : @$s14ViewController8LGPersonC3sex33_37ACD668159BB52851391EE68C0B8918LLSbvM // LGPerson.sex.modify
      #LGPerson.unpdateSex: (LGPerson) -> () -> () : @$s14ViewController8LGPersonC10unpdateSex33_37ACD668159BB52851391EE68C0B8918LLyyF  // LGPerson.unpdateSex()
      #LGPerson.init!allocator: (LGPerson.Type) -> (Bool) -> LGPerson : @$s14ViewController8LGPersonC3sexACSb_tcfC  // LGPerson.__allocating_init(sex:)
      #LGPerson.test: (LGPerson) -> () -> () : @$s14ViewController8LGPersonC4testyyF    // LGPerson.test()
      #LGPerson.deinit!deallocator: @$s14ViewController8LGPersonCfD // LGPerson.__deallocating_deinit
    }
    

    此时将函数unpdateSex声明的private去掉,再次运行

    去掉private_函数表派发
    • 函数unpdateSex调度方式为函数表派发

    相关文章

      网友评论

          本文标题:Swift -- 2.类与结构体(下)

          本文链接:https://www.haomeiwen.com/subject/uhfhcrtx.html