美文网首页
iOS Swift 异变方法、方法调度

iOS Swift 异变方法、方法调度

作者: TerryDev | 来源:发表于2021-12-31 17:15 被阅读0次
    再见 2021

    一、异变方法(关键字:mutating)

    值类型的属性只有在添加 mutating 关键字时才能被自身实例方法修改

    //main.swift
    struct Size {
        var width  = 0.0
        var height = 0.0
    
        func changeWidth(w newWidth: Double){
            width = newWidth // 报错 Cannot assign to property: 'self' is immutable
        }
        mutating func changeSize(w newWidth: Double,h newHeight: Double){
            width = newWidth
            height = newHeight
        }
    }
    

    通过 Swift 中间语言 SIL 来查看添加和没添加 mutating 关键字的区别
    SIL 全称 Swift Intermediate Language 可以用它来了解 Swift 底层实现细节

    步骤
    • 打开终端cd到当前main.swift目录
    • 通过下面的命令将main.swift编译成main.sil

    swiftc -emit-sil main.swift | xcrun swift-demangle >> ./main.sil

    • 使用 VS Code 或者 Sublime Text 打开 main.sil 文件

    第一次看到 main.sil 文件里面的东西,我也是一脸懵逼 😳 里面的东西很多,不要慌,通过搜索 changeWidth 和 changeSize 关键字定位到上述两个方法在 main.sil 中的位置 (内容较多 只贴出关键信息)

    sil hidden @main.Size.changeWidth(w: Swift.Double) -> () : $@convention(method) (Double, Size) -> () {
    // %1 "self"
    debug_value %1 : $Size, let, name "self", argno 2 
    } 
    
    sil hidden @main.Size.changeSize(w: Swift.Double, h: Swift.Double) -> () : $@convention(method) (Double, Double, @inout Size) -> () {
    // %2 "self" 
    debug_value_addr %2 : $*Size, var, name "self", argno 3 
    
    

    备注:
    sil: 函数以关键字 sil 开头
    hidden: 内部隐藏关键字,表明该方法仅在同一个 Swift 模块中可见
    %1 和 %2 : 代表 self

    对比发现 changeSize 方法比 changeWidth 多了一个 @inout 关键字,而且 changeWidth 里面的 self 是不可变的 (let 修饰),而 changeSize 里面的 self 是可变的 (var 修饰)
    通过 SIL 官方文档查询我们得知:@inout参数是间接的,地址必须是已初始化对象的地址。

    异变方法的本质

    传入的 self 会被标记为 inout 参数,例如 main.sil 中的 changeSize(@inout Size),不论 mutating 方法内部发生什么,都会影响外部,因为被自身的实例方法修改了

    二、方法调度

    上代码

    class Person {
        
        func sayHello(){
            print("嗨!靓仔")
        }
        
    }
    
    var p = Person()
    p.sayHello()
    

    sayHello 函数的调用过程如下:

    • 找到 Metadata
    • 确定函数地址(metadata + 偏移量)
    • 执行函数。

    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
    }
    

    方法调度总结:

    • 值类型: 静态派发
    • 类:函数派发
    • NSObject 子类:函数派发
    • 不论是值类型、类还是NSObject 子类, extension 后均为静态派发

    影响函数派发方式的关键字

    • final:添加了 final 关键字的函数无法被重写,使用静态派发,不会在 Vtable 中出现,并且对 objc runtime 不可见
    • dynamic:函数均可添加 dynamic 关键字,为非 objc 类和值类型的函数赋予动态性,但派发方式函数函数表派发
    • @objc:添加该关键字可以将 Swift 函数暴露给 objc runtime,依旧是函数表派发
    • @objc + dynamic:消息派发

    相关文章

      网友评论

          本文标题:iOS Swift 异变方法、方法调度

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