Swift 派发机制

Posted CoderStar

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift 派发机制相关的知识,希望对你有一定的参考价值。

对于编译型语言来看,有主要三种类型的函数派发方式,分别为:

  • Direct Dispatch:直接派发
  • Table Dispatch:函数表派发
  • Message Dispatch:消息派发
  • 分析三种派发方式主要从性能动态性两方面讨论,这两个特性相对而言是矛盾的,性能要求高,则动态性差,反之亦然,其中直接派发又被称为静态派发,函数表派发与消息派发称为动态派发,大多数语言都会支持上面派发方式的一种到多种。如

  • C 使用直接派发;
  • Java 默认使用函数表派发,可以通过 final 修饰符修改成直接派发;
  • C++ 默认使用直接派发,但可以通过加上 virtual 修饰符来改成函数表派发;
  • OC 使用直接派发、消息派发方式;(普通方法采用消息派发的方式,load 方法使用直接派发的方式)
  • 直接派发是三种形式里面最快速的,在编译时就确定了方法的调用地址,汇编代码中,直接跳到方法的地址执行,生成的汇编指令最少。

    优点:编译器可以对这种派发方式进行更多优化,比如函数内联等。
    缺点:缺乏动态性,无法实现继承等;

    函数表是编译型语言常见的派发方式,函数表使用数组来存储类中声明的每个函数的指针。对于这个表,大部分语言叫 Virtual table(虚函数表) 。根据 Swift 编译生成的 SIL 文件分析,Swift 中存在两种函数表,其中协议使用的是 witness_table (SIL 文件中名为 sil_witness_table),类使用的是 virtual_table(SIL 文件中名为 sil_vtable)。

    每一个类都会维护一个函数表,里面记录着类所有的函数,如果父类函数被 override,表里面只会保存被 override 之后的函数。一个子类新添加的函数,都会被插入到这个数组的最后。运行时会根据这一个表去决定实际要被调用的函数;

    一个函数被调用时会先去读取对象的函数表(读取第一次),再根据类的地址加上该的函数的偏移量得到函数地址(读取第二次),最后跳到那个地址上去(跳转一次)。整个过程是两次读取一次跳转,比直接派发慢一些。


    消息派发是动态性最强的派发方式,也是性能最差的一种方式;方法调用包装成消息,发给运行时(相当于中间人),运行时会找到类对象,类对象会保存类的数据信息,或通过父类查找,直到命中执行,如果没找到方法,抛出异常,运行时提供了很多动态的方法用于改变消息派发的行为,相比函数表派发有很强的动态性,由于运行时支持的功能很多,方法查找的过程比较长,所以性能比较低;

    OC 消息派发过程在这不展开说,后续有博文专门说这个。

    分析SIL文件,我们可以分析出Swift中派发方式的规律,关于SIL相关知识,可以参照该文 ios编译简析 。

    本文只给出关键命令 swiftc main.swift -emit-sil | xcrun swift-demangle > main.sil

    派发方式与 SIL 文件中关键指令对应关系

  • sil_witness_table/sil_vtable:函数表派发
  • objc_method:消息机制派发
  • 不在上述范围内的属于直接派发;
  • Swift 语言支持三种派发方式。采用何种方式跟以下四种因素相关:

  • 声明的位置
  • 引用类型
  • 指定行为
  • 显式地优化

  • 直接派发函数表派发消息派发
    NSObject@nonobjc 或者 final 修饰的方法声明作用域中方法扩展方法及被 dynamic 修饰的方法
    Class不被 @objc 修饰的扩展方法及被 final 修饰的方法声明作用域中方法dynamic 修饰的方法或者被 @objc 修饰的扩展方法
    Protocol扩展方法声明作用域中方法@objc 修饰的方法或者被 objc 修饰的协议中所有方法
    Value Type所有方法
    其他全局方法,staic 修饰的方法;使用 final 声明的类里面的所有方法;使用 private 声明的方法和属性会隐式 final 声明;

    通过该表格你大概就可以理解一下 Swift 语言中的一些限制了:

  • extension 中定义的方法如果想 overrite,需要在方法上加上 @objc 修饰符;因为如果不加 @objc,走的是直接派发,无法重写方法。
  • 声明这个函数 never 永远不被编译成 inline 的形式,即使开启了编译器优化;
  • @inline(__always) 声明这个函数总是编译成 inline 的形式, 这个修饰符只对函数体过长这种不会被内联优化的情况生效,其他情况也不生效;
  • 内联除了可以提高运行效率这个优点之外,还有另外一个好处,将部分关键函数进行内联优化,可以增大逆向难度。

    检查继承关系,对某些没有标记 final 的类通过计算,如果能在编译期确定执行的方法,则使用直接派发。比如一个函数没有 override,Swift 就可能会使用直接派发的方式。


    有一个技术的圈子与一群志同道合的朋友非常重要,来我的技术公众号及博客,这里只聊技术干货。

  • 微信公众号:CoderStar

  • 博客:CoderStar\'s Blog

  • Swift之从SIL深入分析函数的派发机制

    一、引言

    	protocol Drawing {
    	  func render()
    	}
    	
    	extension Drawing {
    	  func circle() { print("protocol") }
    	  func render() { circle() }
    	}
    	
    	class SVG: Drawing {
    	  func circle() { print("class") }
    	}
    	
    	SVG().render()
    	
    	// what's the output?
    
    	swiftc -emit-silgen -O demo.swift -o demo.sil
    
    	// main
    	sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
    	bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
    	  %2 = metatype $@thick SVG.Type                  // user: %4
    	  // function_ref SVG.__allocating_init()
    	  %3 = function_ref @$s4demo3SVGCACycfC : $@convention(method) (@thick SVG.Type) -> @owned SVG // user: %4
    	  %4 = apply %3(%2) : $@convention(method) (@thick SVG.Type) -> @owned SVG // user: %6
    	  %5 = alloc_stack $SVG                           // users: %10, %9, %8, %6
    	  store %4 to [init] %5 : $*SVG                   // id: %6
    	  // function_ref Drawing.render()
    	  %7 = function_ref @$s4demo7DrawingPAAE6renderyyF : $@convention(method) <τ_0_0 where τ_0_0 : Drawing> (@in_guaranteed τ_0_0) -> () // user: %8
    	  %8 = apply %7<SVG>(%5) : $@convention(method) <τ_0_0 where τ_0_0 : Drawing> (@in_guaranteed τ_0_0) -> ()
    	  destroy_addr %5 : $*SVG                         // id: %9
    	  dealloc_stack %5 : $*SVG                        // id: %10
    	  %11 = integer_literal $Builtin.Int32, 0         // user: %12
    	  %12 = struct $Int32 (%11 : $Builtin.Int32)      // user: %13
    	  return %12 : $Int32                             // id: %13
    	} // end sil function 'main'
    
    	// Drawing.render()
    	sil hidden [ossa] @$s4demo7DrawingPAAE6renderyyF : $@convention(method) <Self where Self : Drawing> (@in_guaranteed Self) -> () {
    	// %0 "self"                                      // users: %3, %1
    	bb0(%0 : $*Self):
    	  debug_value_addr %0 : $*Self, let, name "self", argno 1 // id: %1
    	  // function_ref Drawing.circle()
    	  %2 = function_ref @$s4demo7DrawingPAAE6circleyyF : $@convention(method) <τ_0_0 where τ_0_0 : Drawing> (@in_guaranteed τ_0_0) -> () // user: %3
    	  %3 = apply %2<Self>(%0) : $@convention(method) <τ_0_0 where τ_0_0 : Drawing> (@in_guaranteed τ_0_0) -> ()
    	  %4 = tuple ()                                   // user: %5
    	  return %4 : $()                                 // id: %5
    	} // end sil function '$s4demo7DrawingPAAE6renderyyF'
    

    二、派发机制

    ① 静态派发
    ② 函数表派发
    	class A {
    	    func method1() {}
    	}
    	class B: A {
    			func method2() {}
    	}
    	class C: B {
    	    override func method2() {}
    	    func method3() {}
    	}
    
    offset0xA00A0xB00B0xC00C
    00x121A.method10x121A.method10x121A.method1
    10x222B.method20x322C.method2
    20x323C.method3
    	let obj = C()
    	obj.method2()
    
    ③ 消息派发
    	id returnValue = [obj messageName:param];
    	// 底层代码
    	id returnValue = objc_msgSend(obj, @selector(messageName:), param);
    

    三、Swift 的派发机制

    ① 数据类型
    类型初始声明扩展
    值类型静态派发静态派发
    协议函数表派发静态派发
    函数表派发静态派发
    NSObject子类函数表派发静态派发
    	class MyClass {
    	    func testOfClass() {}
    	}
    	
    	struct MyStruct {
    	    func testOfStruct() {}
    	}
    
    	class MyClass {
    	  func testOfClass()
    	  @objc deinit
    	  init()
    	}
    	
    	struct MyStruct {
    	  func testOfStruct()
    	  init()
    	}
    	
    	// MyClass.testOfClass()
    	sil hidden [ossa] @$s4demo7MyClassC06testOfC0yyF : $@convention(method) (@guaranteed MyClass) -> () {
    	...
    	} // end sil function '$s4demo7MyClassC06testOfC0yyF'
    	
    	// MyClass.deinit
    	sil hidden [ossa] @$s4demo7MyClassCfd : $@convention(method) (@guaranteed MyClass) -> @owned Builtin.NativeObject {
    	...
    	} // end sil function '$s4demo7MyClassCfd'
    	
    	// MyClass.__deallocating_deinit
    	sil hidden [ossa] @$s4demo7MyClassCfD : $@convention(method) (@owned MyClass) -> () {
    	...
    	} // end sil function '$s4demo7MyClassCfD'
    	
    	// MyClass.__allocating_init()
    	sil hidden [exact_self_class] [ossa] @$s4demo7MyClassCACycfC : $@convention(method) (@thick MyClass.Type) -> @owned MyClass {
    	...
    	} // end sil function '$s4demo7MyClassCACycfC'
    	
    	// MyClass.init()
    	sil hidden [ossa] @$s4demo7MyClassCACycfc : $@convention(method) (@owned MyClass) -> @owned MyClass {
    	...
    	} // end sil function '$s4demo7MyClassCACycfc'
    	
    	// MyStruct.testOfStruct()
    	sil hidden [ossa] @$s4demo8MyStructV06testOfC0yyF : $@convention(method) (MyStruct) -> () {
    	...
    	} // end sil function '$s4demo8MyStructV06testOfC0yyF'
    	
    	// MyStruct.init()
    	sil hidden [ossa] @$s4demo8MyStructVACycfC : $@convention(method) (@thin MyStruct.Type) -> MyStruct {
    	...
    	} // end sil function '$s4demo8MyStructVACycfC'
    	
    	sil_vtable MyClass {
    	  #MyClass.testOfClass: (MyClass) -> () -> () : @$s4demo7MyClassC06testOfC0yyF	// MyClass.testOfClass()
    	  #MyClass.init!allocator: (MyClass.Type) -> () -> MyClass : @$s4demo7MyClassCACycfC	// MyClass.__allocating_init()
    	  #MyClass.deinit!deallocator: @$s4demo7MyClassCfD	// MyClass.__deallocating_deinit
    	}
    
    ② 函数声明的位置
    	protocol MyProtocol {
    	    func testOfProtocol()
    	}
    	
    	extension MyProtocol {
    	    func testOfProtocolInExtension() {}
    	}
    	
    	class MyClass: MyProtocol {
    	    func testOfClass() {}
    	    func testOfProtocol() {}
    	}
    	
    	extension MyClass {
    	    func testOfClassInExtension() {}
    	}
    
    	protocol MyProtocol {
    	  func testOfProtocol()
    	}
    	
    	extension MyProtocol {
    	  func testOfProtocolInExtension()
    	}
    	
    	class MyClass : MyProtocol {
    	  func testOfClass()
    	  func testOfProtocol()
    	  @objc deinit
    	  init()
    	}
    	
    	extension MyClass {
    	  func testOfClassInExtension()
    	}
    	
    	// MyProtocol.testOfProtocolInExtension()
    	sil hidden [ossa] @$s4demo10MyProtocolPAAE06testOfC11InExtensionyyF : $@convention(method) <Self where Self : MyProtocol> (@in_guaranteed Self) -> () {
    	...
    	} // end sil function '$s4demo10MyProtocolPAAE06testOfC11InExtensionyyF'
    	
    	// MyClass.testOfClass()
    	sil hidden [ossa] @$s4demo7MyClassC06testOfC0yyF : $@convention(method) (@guaranteed MyClass) -> () {
    	...
    	} // end sil function '$s4demo7MyClassC06testOfC0yyF'
    	
    	// MyClass.testOfProtocol()
    	sil hidden [ossa] @$s4demo7MyClassC14testOfProtocolyyF : $@convention(method) (@guaranteed MyClass) -> () {
    	...
    	} // end sil function '$s4demo7MyClassC14testOfProtocolyyF'
    	
    	// MyClass.deinit
    	sil hidden [ossa] @$s4demo7MyClassCfd : $@convention(method) (@guaranteed MyClass) -> @owned Builtin.NativeObject {
    	...
    	} // end sil function '$s4demo7MyClassCfd'
    	
    	// MyClass.__deallocating_deinit
    	sil hidden [ossa] @$s4demo7MyClassCfD : $@convention(method) (@owned MyClass) -> () {
    	...
    	} // end sil function '$s4demo7MyClassCfD'
    	
    	// MyClass.__allocating_init()
    	sil hidden [exact_self_class] [ossa] @$s4demo7MyClassCACycfC : $@convention(method) (@thick MyClass.Type) -> @owned MyClass {
    	...
    	} // end sil function '$s4demo7MyClassCACycfC'
    	
    	// MyClass.init()
    	sil hidden [ossa] @$s4demo7MyClassCACycfc : $@convention(method) (@owned MyClass) -> @owned MyClass {
    	...
    	} // end sil function '$s4demo7MyClassCACycfc'
    	
    	// protocol witness for MyProtocol.testOfProtocol() in conformance MyClass
    	sil private [transparent] [thunk] [ossa] @$s4demo7MyClassCAA0B8ProtocolA2aDP06testOfD0yyFTW : $@convention(witness_method: MyProtocol) (@in_guaranteed MyClass) -> () {
    	...
    	} // end sil function '$s4demo7MyClassCAA0B8ProtocolA2aDP06testOfD0yyFTW'
    	
    	// MyClass.testOfClassInExtension()
    	sil hidden [ossa] @$s4demo7MyClassC06testOfC11InExtensionyyF : $@convention(method) (@guaranteed MyClass) -> () {
    	...
    	} // end sil function '$s4demo7MyClassC06testOfC11InExtensionyyF'
    	
    	sil_vtable MyClass {
    	  #MyClass.testOfClass: (MyClass) -> () -> () : @$s4demo7MyClassC06testOfC0yyF	// MyClass.testOfClass()
    	  #MyClass.testOfProtocol: (MyClass) -> () -> () : @$s4demo7MyClassC14testOfProtocolyyF	// MyClass.testOfProtocol()
    	  #MyClass.init!allocator: (MyClass.Type) -> () -> MyClass : @$s4demo7MyClassCACycfC	// MyClass.__allocating_init()
    	  #MyClass.deinit!deallocator: @$s4demo7MyClassCfD	// MyClass.__deallocating_deinit
    	}
    	
    	sil_witness_table hidden MyClass: MyProtocol module demo {
    	  method #MyProtocol.testOfProtocol: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4demo7MyClassCAA0B8ProtocolA2aDP06testOfD0yyFTW	// protocol witness for MyProtocol.testOfProtocol() in conformance MyClass
    	}
    
    	// protocol witness for MyProtocol.testOfProtocol() in conformance MyClass
    	sil private [transparent] [thunk] [ossa] @$s4demo7MyClassCAA0B8ProtocolA2aDP06testOfD0yyFTW : $@convention(witness_method: MyProtocol) (@in_guaranteed MyClass) -> () {
    	// %0                                             // user: %1
    	bb0(%0 : $*MyClass):
    	  %1 = load_borrow %0 : $*MyClass                 // users: %5, %3, %2
    	  %2 = class_method %1 : $MyClass, #MyClass.testOfProtocol : (MyClass) -> () -> (), $@convention(method) (@guaranteed MyClass) -> () // user: %3
    	  %3 = apply %2(%1) : $@convention(method) (@guaranteed MyClass) -> ()
    	  %4 = tuple ()                                   // user: %6
    	  end_borrow %1 : $MyClass                        // id: %5
    	  return %4 : $()                                 // id: %6
    	} // end sil function '$s4demo7MyClassCAA0B8ProtocolA2aDP06testOfD0yyFTW'
    	
    	sil_witness_table hidden MyClass: MyProtocol module demo {
    	  method #MyProtocol.testOfProtocol: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4demo7MyClassCAA0B8ProtocolA2aDP06testOfD0yyFTW	// protocol witness for MyProtocol.testOfProtocol() in conformance MyClass
    	}
    
    ③ 指定派发方式
    	class Test {
    	    final func foo() {}
    	}
    	Test().foo()
    	
    	sil_vtable Test {
    	  #Test.init!allocator: (Test.Type) -> () -> Test : @$s4demo4TestCACycfC	// Test.__allocating_init()
    	  #Test.deinit!deallocator: @$s4demo4TestCfD	// Test.__deallocating_deinit
    	}
    	
    	// main
    	sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
    	bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
    	  %2 = metatype $@thick Test.Type                 // user: %4
    	  // function_ref Test.__allocating_init()
    	  %3 = function_ref @$s4demo4TestCACycfC : $@convention(method) (@thick Test.Type) -> @owned Test // user: %4
    	  %4 = apply %3(%2) : $@convention(method) (@thick Test.Type) -> @owned Test // users: %7, %6
    	  // function_ref Test.foo()
    	  %5 = function_ref @$s4demo4TestC3fooyyF : $@convention(method) (@guaranteed Test) -> () // user: %6
    	  %6 = apply %5(%4) : $@convention(method) (@guaranteed Test) -> ()
    	  destroy_value %4 : $Test                        // id: %7
    	  %8 = integer_literal $Builtin.Int32, 0          // user: %9
    	  %9 = struct $Int32 (%8 : $Builtin.Int32)        // user: %10
    	  return %9 : $Int32                              // id: %10
    	} // end sil function 'main'
    
    	class Test {
    	    dynamic func foo() {
    	        print("bar")
    	    }
    	}
    	
    	extension Test {
    	    @_dynamicReplacement(for: foo())
    	    func foo_new() {
    	        print("bar new")
    	    }
    	}
    	    
    	Test().foo() // bar new
    
    	class Test {
    	    @objc func foo() {}
    	}
    	
    	// Test.foo()
    	sil hidden [ossa] @$s4demo4TestC3fooyyF : $@convention(method) (@guaranteed Test) -> () {
    	// %0 "self"                                      // user: %1
    	bb0(%0 : @guaranteed $Test):
    	  debug_value %以上是关于Swift 派发机制的主要内容,如果未能解决你的问题,请参考以下文章

    Swift之从SIL深入分析函数的派发机制

    多角度体会 Swift 方法派发

    Swift中的函数调用

    swift与oc的关系

    swift class的动态派发

    swift protocol 见证容器 虚函数表 与 动态派发