swift第一章:类和结构体下

117 阅读3分钟

mutating的作用

  • struct和class中都能定义方法,因为struct是值类型,class是引用类型,所以就会有两个不同点
  • struct中mutating的作用:修饰的函数,在底层是var修饰的self,相当于var self = &p,是可以修改的,默认是let类型,相当于let self = p,是会报错的。
  • struct中mutating修饰的方法会在底层自动添加@inout,无论内部发生什么都会影响外部行为
struct Teacher{
    var age: Int
    var name: String
    init(_ *age: Int,* _ name: String) {
        self.age = age
        self.name = name
    }

    //正确
   mutating func change1(_ num: Int){
     self.age = num
}

 //错误
func change2( _ num: Int){
        self.age = num
    }
}
var t = Teacher(22,"222")
t.change(2)
print("end")
  • swiftc -emit-sil ${SRCROOT}/SwiftTest/main.swift > ./main.sil && open main.sil //生成sil文件并且打开有助与我们研究源码

方法调度 基于函数表的调度

  • oc中的方法调度:objc_msgSend(),这是一种动态的消息机制

  • 而在swift中是基于函数表的调度

  • 先看看汇编指令:

  1. mov:将某一个寄存器的值赋值到寄存器里面
  2. ldr:降内存中的值读取到寄存器
  3. cmp:比较指令
  4. b:无返回值的跳转
  5. bl:有返回值的跳转
  • swifty的方法调用:找到metaData->确定函数地址->从函数表vtable里找到相应函数,在GenMeta.cpp文件里面添加了虚函数表,addVTable()
  1. metaData里面有TargetClassDescriptor结构体,它有一个别名ClassDescriptor
  2. 通过源码分析知道,在GenMeta.cpp文件里面有一个方法layout(),这里面有addVTable()函数添加虚函数表,在这个里面会添加方法,放置在TargetClassDescriptor结构体后面,类似methods(v-table)
  • 把可执行文件放到mach-view上知道,mach-o中ASLR是程序运行的偏移,可以通过在符号表中找到TargetClassDescriptor结构体地址,然后找到相对应的函数,验证以上结论,虚函数表也是一个结构体TargetMethodDescriptor{}
  • struct和externSion是静态派发,直接使用地址调用,因为vtable在编译链接的时候已经形成,为了减少变动开销,系统优化externSion成直接调用

影响派发的方式 final:修饰的方法不能被继承,优化成静态调用,直接地址调用,不会添加到v-table中

  • dynamic:函数变量均可添加,不改变调度方式,依然是函数表调用,在子类中可以动态替换用dynamic修饰的方法,@_dynamicReplacement(for: teach)

  • @objc:该关键字讲swift方法暴漏给oc,依旧是函数表派发

  • 原生swift类@objc dynamic结合使用就变成了消息调度方式,objc_mesSend方式,意味着可以使用methodSwizzle来进行交换,如果要让oc使用到,需要继承NSObject

struct方法调用

  • struct是值类型,直接地址调用(静态调用),所以性能上是比class更优越的,扩展不管是在struct还是在class里面都是静态派发,为什么呢?假设扩展如果把新增的方法放到虚函数表中,这个过程是十分复杂和消耗性能的,得不偿失,优化成直接调用

函数内联

  • 讲确保有时内联函数,这是默认行为,我们无需执行任何操作,swift编译器可能自动内联函数作为优化
  • always-将确保始终内联函数,通过在函数前面添加@inline(_alaways)来实现此行为
  • never-将确保永远不会内联函数,这可以通过在函数前添加@inline(never)来实现
  • 如果函数很长并且想避免增加代码段大小,请使用@inline(never)