<ins id='aimk3'></ins>
        <span id='aimk3'></span>
        <fieldset id='aimk3'></fieldset>

        <i id='aimk3'></i>

          <acronym id='aimk3'><em id='aimk3'></em><td id='aimk3'><div id='aimk3'></div></td></acronym><address id='aimk3'><big id='aimk3'><big id='aimk3'></big><legend id='aimk3'></legend></big></address>
            <dl id='aimk3'></dl>

            <code id='aimk3'><strong id='aimk3'></strong></code>

            <i id='aimk3'><div id='aimk3'><ins id='aimk3'></ins></div></i>
          1. <tr id='aimk3'><strong id='aimk3'></strong><small id='aimk3'></small><button id='aimk3'></button><li id='aimk3'><noscript id='aimk3'><big id='aimk3'></big><dt id='aimk3'></dt></noscript></li></tr><ol id='aimk3'><table id='aimk3'><blockquote id='aimk3'><tbody id='aimk3'></tbody></blockquote></table></ol><u id='aimk3'></u><kbd id='aimk3'><kbd id='aimk3'></kbd></kbd>
          2. 【基本功】深入剖析Swift性能优化

            • 时间:
            • 浏览:1
            • 来源:124软件资讯网

              简介

              2014年 ,苹果公司在WWDC上公布Swift这一新的编程语言  。经由几年的生长  ,Swift已经成为iOS开发语言的“中流砥柱”  ,Swift提供了很是天真的高级别特征  ,例如协议、闭包、泛型等 ,而且Swift还进一步开发了强盛的SIL(Swift Intermediate Language)用于对编译器举行优化 ,使得Swift相比Objective-C运行更快性能更优 ,Swift内部怎样实现性能的优化 ,我们本文就举行一下解读  ,希望能对各人有所启发和资助  。

              针对Swift性能提升这一问题  ,我们可以从观点上拆分为两个部门:

              1. 编译器:Swift编译器举行的性能优化 ,从阶段分为编译期和运行期  ,内容分为时间优化和空间优化 。

              2. 开发者:通过使用合适的数据结构和要害字  ,资助编译器获取更多信息  ,举行优化  。

              下面我们将从这两个角度切入  ,对Swift性能优化举行剖析  。通过相识编译器对差别数据结构处置惩罚的内部实现 ,来选择最合适的算法机制  ,并使用编译器的优化特征  ,编写高性能的法式  。

              明白Swift的性能

              明白Swift的性能  ,首先要清晰Swift的数据结构  ,组件关系和编译运行方式  。

              • 数据结构

              Swift的数据结构可以大要拆分为:Class  ,Struct  ,Enum 。

              • 组件关系

              组件关系可以分为:inheritance  ,protocols  ,generics  。

              • 要领分配方式

              要领分配方式可以分为Static dispatch和Dynamic dispatch 。

              要在开发中提高Swift性能  ,需要开发者去相识这几种数据结构和组件关系以及它们的内部实现  ,从而通过选择最合适的抽象机制来提升性能  。

              首先我们对于性能尺度举行一个观点陈述  ,性能尺度涵盖三个尺度:

              • Allocation

              • Reference counting

              • Method dispatch

              接下来  ,我们会划分对这几个指标举行说明 。

              Allocation

              内存分配可以分为堆区栈区  ,在栈的内存分配速率要高于堆 ,结构体和类在客栈分配是差别的  。

              Stack

              基本数据类型和结构体默认在栈区 ,栈区内存是一连的  ,通过出栈入栈举行分配和销毁  ,速率很快 ,高于堆区 。

              我们通过一些例子举行说明:

              //示例 1
              // Allocation
              // Struct
              struct Point {
               var x, y:Double
               func draw() { … }
              }
              let point1 = Point(x:0, y:0//举行point1初始化 ,开发栈内存
              var point2 = point1 //初始化point2  ,拷贝point1内容  ,开发新内存
              point2.x = 5 //对point2的操作不会影响point1
              // use `point1`
              // use `point2`

              以上结构体的内存是在栈区分配的  ,内部的变量也是内联在栈区 。将point1赋值给point2现实操作是在栈区举行了一份拷贝 ,发生了新的内存消耗point2  ,这使得point1和point2是完全自力的两个实例 ,它们之间的操作互不影响  。在使用point1和point2之后  ,会举行销毁 。

              Heap

              高级的数据结构 ,好比类  ,分配在堆区  。初始化时查找没有使用的内存块  ,销毁时再从内存块中扫除 。由于堆区可能存在多线程的操作问题  ,为了保证线程宁静  ,需要举行加锁操作 ,因此也是一种性能消耗 。

              // Allocation
              // Class
              class Point {
               var x, y:Double
               func draw(
              { … }
              }
              let point1 = Point(x:0, y:0//在堆区分配内存  ,栈区只是存储地址指针
              let point2 = point1 //不发生新的实例  ,而是对point2增添对堆区内存引用的指针
              point2.x = 5 //由于point1和point2是一个实例 ,以是point1的值也会被修改
              // use `point1`
              // use `point2`

              以上我们初始化了一个Class类型  ,在栈区分配一块内存 ,可是和结构体直接在栈内存储数值差别 ,我们只在栈区存储了工具的指针  ,指针指向的工具的内存是分配在堆区的  。需要注重的是 ,为了治理工具内存  ,在堆区初始化时 ,除了分配属性内存(这里是Double类型的x  ,y)  ,还会有分外的两个字段 ,划分是type和refCount  ,这个包罗了type  ,refCount和现实属性的结构被称为blue box  。

              内存分配总结

              从初始化角度  ,Class相比Struct需要在堆区分配内存  ,举行内存治理  ,使用了指针  ,有更强盛的特征  ,可是性能较低  。

              优化方式:

              对于频仍操作(好比通讯软件的内容气泡展示) ,只管使用Struct替换Class  ,由于栈内存分配更快  ,更宁静  ,操作更快 。

              Reference counting

              Swift通过引用计数治理堆工具内存  ,当引用计数为0时  ,Swift确认没有工具再引用该内存  ,以是将内存释放  。对于引用计数的治理是一个很是高频的间接操作  ,而且需要思量线程宁静 ,使得引用计数的操作需要较高的性能消耗  。

              对于基本数据类型的Struct来说 ,没有堆内存分配和引用计数的治理  ,性能更高更宁静  ,可是对于庞大的结构体 ,如:

              // Reference Counting
              // Struct containing references
              struct Label {
               var text:String
               var font:UIFont
               func draw(
              { … }
              }
              let label1 = Label(text:"Hi", font:font)  //栈区包罗了存储在堆区的指针
              let label2 = label1 //label2发生新的指针  ,和label1一样指向同样的string和font地址
              // use `label1`
              // use `label2`

              这里看到 ,包罗了引用的结构体相比Class  ,需要治理双倍的引用计数  。每次将结构体作为参数通报给要领或者举行直接拷贝时  ,都市泛起多份引用计数  。下图可以比力直观的明白:

              备注:包罗引用类型的结构体泛起Copy的处置惩罚方式

              Class在拷贝时的处置惩罚方式:

              引用计数总结

              • Class在堆区分配内存 ,需要使用引用计数器举行内存治理 。

              • 基本类型的Struct在栈区分配内存  ,无引用计数治理  。

              • 包罗强类型的Struct通过指针治理在堆区的属性  ,对结构体的拷贝会建立新的栈内存 ,建立多份引用的指针 ,Class只会有一份  。

              优化方式

              在使用结构体时:

              1. 通过使用准确类型  ,例如UUID替换String(UUID字节长度牢固128字节  ,而不是String恣意长度)  ,这样就可以举行内存内联  ,在栈内存储UUID  ,我们知道  ,栈内存治理更快更宁静  ,而且不需要引用计数  。

              2. Enum替换String  ,在栈内治理内存 ,无引用计数 ,而且从语法上对于开发者更友好  。

              Method Dispatch

              我们之前在Static dispatch VS Dynamic dispatch中提到过  ,能够在编译期确定执行要领的方式叫做静态分配Static dispatch ,无法在编译期确定 ,只能在运行时去确定执行要领的分配方式叫做动态分配Dynamic dispatch  。

              Static dispatch更快 ,而且静态分配可以举行内联等进一步的优化  ,使得执行更快速 ,性能更高  。

              可是对于多态的情形 ,我们不能在编译期确定最终的类型  ,这里就用到了Dynamic dispatch动态分配  。动态分配的实现是 ,每种类型都市建立一张表 ,表内是一个包罗了要领指针的数组  。动态分配更天真  ,可是由于有查表和跳转的操作  ,而且由于许多特点对于编译器来说并不明确 ,以是相当于block了编译器的一些后期优化 。以是速率慢于Static dispatch 。

              下面看一段多态代码 ,以及剖析实现方式:

              //引用语义实现的多态
              class Drawable { func draw() {} }
              class Point :Drawable {
               var x, y:Double
               override func draw() { … }
              }
              class Line :Drawable {
               var x1, y1, x2, y2:Double
               override func draw() { … }
              }
              var drawables:[Drawable]
              for d in drawables {
               d.draw()
              }

              Method Dispatch总结

              Class默认使用Dynamic dispatch ,由于在编译期险些每个环节的信息都无法确定  ,以是阻碍了编译器的优化  ,好比inline和whole module inline  。

              使用Static dispatch取代Dynamic dispatch提升性能

              我们知道Static dispatch快于Dynamic dispatch  ,怎样在开发中去尽可能使用Static dispatch  。

              • inheritance constraints继续约束  我们可以使用final要害字去修饰Class  ,以今生成的Final class  ,使用Static dispatch  。

              • access control会见控制  private要害字修饰 ,使得要领或属性只对当前类可见  。编译器会对要领举行Static dispatch  。

              编译器可以通过whole module optimization检查继续关系  ,对某些没有标志final的类通过盘算  ,若是能在编译期确定执行的要领 ,则使用Static dispatch  。

              Struct默认使用Static dispatch  。

              Swift快于OC的一个要害是可以消解动态分配 。

              总结

              Swift提供了更天真的Struct ,用以在内存、引用计数、要领分配等角度去举行性能的优化  ,在准确的时机选择准确的数据结构  ,可以使我们的代码性能更快更宁静  。

              延伸

              你可能会问Struct怎样实现多态呢?谜底是protocol oriented programming  。

              以上剖析了影响性能的几个尺度  ,那么差别的算法机制Class  ,Protocol Types和Generic code ,它们在这三方面的体现怎样  ,Protocol Type和Generic code划分是怎么实现的呢  ?我们带着这个问题看下去  。

              Protocol Type

              这里我们会讨论Protocol Type怎样存储和拷贝变量  ,以及要领分配是怎样实现的  。不通过继续或者引用语义的多态:

              protocol Drawable { func draw() }
              struct Point :Drawable
               {
               var x, y:Double
               func draw() { … }
              }
              struct Line :Drawable {
               var x1, y1, x2, y2:Double
               func draw() { … }
              }

              var drawables:[Drawable] //遵守了Drawable协议的类型荟萃  ,可能是point或者line
              for d in drawables {
               d.draw()
              }

              以上通过Protocol Type实现多态  ,几个类之间没有继续关系  ,故不能根据老例借助V-Table实现动态分配 。

              若是想相识Vtable和Witness table实现  ,可以举行点击检察 ,这里不做细节说明 。

              由于Point和Line的尺寸差别  ,数组存储数据实现一致性存储 ,使用了Existential Container  。查找准确的执行要领则使用了 Protoloc Witness Table 。

              Existential Container

              Existential Container是一种特殊的内存结构方式 ,用于治理遵守了相同协议的数据类型Protocol Type ,这些数据类型由于不共享统一继续关系(这是V-Table实现的条件)  ,而且内存空间尺寸差别  ,使用Existential Container举行治理  ,使其具有存储的一致性  。

              结构如下:

              • 三个词巨细的valueBuffer  这里先容一下valueBuffer结构  ,valueBuffer有三个词  ,每个词包罗8个字节 ,存储的可能是值  ,也可能是工具的指针  。对于small value(空间小于valueBuffer)  ,直接存储在valueBuffer的地址内 , inline valueBuffer  ,无分外堆内存初始化 。当值的数目大于3个属性即large value  ,或者总尺寸凌驾valueBuffer的占位  ,就会在堆区开发内存 ,将其存储在堆区 ,valueBuffer存储内存指针  。

              • value witness table的引用  由于Protocol Type的类型差别  ,内存空间  ,初始化要领等都不相同  ,为了对Protocol Type生命周期举行专项治理  ,用到了Value Witness Table  。

              • protocol witness table的引用  治理Protocol Type的要领分配 。

              内存漫衍如下:

              1. payload_data_0 = 0x0000000000000004,
              2. payload_data_1 = 0x0000000000000000,
              3. payload_data_2 = 0x0000000000000000,
              4. instance_type = 0x000000010d6dc408 ExistentialContainers`type    
                     metadata for ExistentialContainers.Car,
              5. protocol_witness_0 = 0x000000010d6dc1c0 
                     ExistentialContainers protocol witness table for 
                     ExistentialContainers.Car:ExistentialContainers.Drivable 
                     in ExistentialContainers

              Protocol Witness Table(PWT)

              为了实现Class多态也就是引用语义多态  ,需要V-Table来实现  ,可是V-Table的条件是具有统一个父类即共享相同的继续关系 ,可是对于Protocol Type来说  ,并不具备此特征 ,故为了支持Struct的多态 ,需要用到protocol oriented programming机制 ,也就是借助Protocol Witness Table来实现(细节可以点击Vtable和witness table实现  ,每个结构体会缔造PWT表  ,内部包罗指针 ,指向要领详细实现)  。

              Value Witness Table(VWT)

              用于治理恣意值的初始化、拷贝、销毁  。

              • Value Witness Table的结构如上 ,是用于治理遵守了协议的Protocol Type实例的初始化  ,拷贝 ,内存消减和销毁的 。

              • Value Witness Table在SIL中还可以拆分为%relative_vwtable和%absolute_vwtable ,我们这里先不做睁开 。

              • Value Witness Table和Protocol Witness Table通太过工  ,去治理Protocol Type实例的内存治理(初始化 ,拷贝  ,销毁)和要领挪用 。

              我们来借助详细的示例举行进一步相识:

              // Protocol Types
              // The Existential Container in action
              func drawACopy(local :Drawable) {
               local.draw()
              }
              let val :Drawable = Point()
              drawACopy(val)

              在Swift编译器中  ,通过Existential Container实现的伪代码如下:

              // Protocol Types
              // The Existential Container in action
              func drawACopy(local :Drawable) {
               local.draw()
              }
              let val :Drawable = Point()
              drawACopy(val)

              //existential container的伪代码结构
              struct ExistContDrawable {
               var valueBuffer:(Int, Int, Int)
               var vwt:ValueWitnessTable
               var pwt:DrawableProtocolWitnessTable
              }

              // drawACopy要领天生的伪代码
              func drawACopy(val:ExistContDrawable) { //将existential container传入
               var local = ExistContDrawable()  //初始化container
               let vwt = val.vwt //获取value witness table  ,用于治理生命周期
               let pwt = val.pwt //获取protocol witness table  ,用于举行要领分配
               local.type = type 
               local.pwt = pwt
               vwt.allocateBufferAndCopyValue(&local, val)  //vwt举行生命周期治理  ,初始化或者拷贝
               pwt.draw(vwt.projectBuffer(&local)) //pwt查找要领  ,这里说一下projectBuffer ,由于差别类型在内存中是差别的(small value内联在栈内  ,large value初始化在堆内 ,栈持有指针)  ,以是要领简直定也是和类型相关的  ,我们知道  ,查找要领时是通过当前工具的地址  ,通过一定的位移去查找要领地址  。
               vwt.destructAndDeallocateBuffer(temp) //vwt举行生命周期治理  ,销毁内存
              }

              Protocol Type 存储属性

              我们知道  ,Swift中Class的实例和属性都存储在堆区 ,Struct实例在栈区  ,若是包罗指针属性则存储在堆区  ,Protocol Type怎样存储属性 ?Small Number通过Existential Container内联实现  ,大数存在堆区  。怎样处置惩罚Copy呢  ?

              Protocol大数的Copy优化

              在泛起Copy情形时:

              let aLine = Line(1.0, 1.0, 1.0, 3.0)
              let pair = Pair(aLine, aLine)
              let copy = pair

              会将新的Exsitential Container的valueBuffer指向统一个value即建立指针引用  ,可是若是要改变值怎么办?我们知道Struct值的修改和Class差别 ,Copy是不应该影响原实例的值的 。

              这里用到了一个手艺叫做Indirect Storage With Copy-On-Write ,即优先使用内存指针  。通过提高内存指针的使用  ,来降低堆区内存的初始化 。降低内存消耗  。在需要修改值的时间  ,会先检测引用计数检测 ,若是有大于1的引用计数  ,则开发新内存  ,建立新的实例 。在对内容举行变换的时间  ,会开启一块新的内存  ,伪代码如下:

              class LineStorage { var x1, y1, x2, y2:Double }
              struct Line :Drawable {
               var storage :LineStorage
               init() { storage = LineStorage(Point(), Point()) }
               func draw() { … }
               mutating func move() {
                 if !isUniquelyReferencedNonObjc(&storage) { //怎样存在多份引用 ,则开启新内存  ,否则直接修改
                   storage = LineStorage(storage)
                 }
                 storage 。start = ...
                 }
              }

              这样实现的目的:通过多份指针去引用统一份地址的成本远远低于开发多份堆内存  。以下对比图:

              Protocol Type多态总结

              • 支持Protocol Type的动态多态(Dynamic Polymorphism)行为 。

              • 通过使用Witness Table和Existential Container来实现 。

              • 对于大数的拷贝可以通过Indirect Storage间接存储来举行优化  。

              说到动态多态Dynamic Polymorphism ,我们就要问了  ,什么是静态多态Static Polymorphism  ,看看下面示例:

              // Drawing a copy
              protocol Drawable {
               func draw()
              }
              func drawACopy(local :Drawable)
               {
               local.draw()
              }

              let line = Line()
              drawACopy(line)
              // ...
              let point = Point()
              drawACopy(point)

              这种情形我们就可以用到泛型Generic code来实现 ,举行进一步优化  。

              泛型

              我们接下来会讨论泛型属性的存储方式和泛型要领是怎样分配的  。泛型和Protocol Type的区别在于:

              • 泛型支持的是静态多态  。

              • 每个挪用上下文只有一种类型  。检察下面的示例 ,foo和bar要领是统一种类型 。

              • 在挪用链中会通过类型降级举行类型取代  。

              对于以下示例:

              func foo<T:Drawable>(local :T) {
               bar(local)
              }
              func bar<T:Drawable>(local:T) { … }
              let point = Point()
              foo(point)

              剖析要领foo和bar的挪用历程:

              //挪用历程
              foo(point)-->foo(point)   //在要领执行时  ,Swift将泛型T绑定为挪用方使用的详细类型  ,这里为Point
               bar(local) -->bar(local) //在挪用内部bar要领时  ,会使用foo已经绑定的变量类型Point  ,可以看到  ,泛型T在这里已经被降级  ,通过类型Point举行取代

              泛型要领挪用的详细实现为:

              • 统一种类型的任何实例  ,都共享同样的实现  ,纵然用统一个Protocol Witness Table 。

              • 使用Protocol/Value Witness Table 。

              • 每个挪用上下文只有一种类型:这里没有使用Existential Container  , 而是将Protocol/Value Witness Table作为挪用方的分外参数举行通报  。

              • 变量初始化和要领挪用  ,都使用传入的VWT和PWT来执行  。

              看到这里  ,我们并不以为泛型比Protocol Type有什么更快的特征  ,泛型怎样更快呢?静态多态条件下可以举行进一步的优化 ,称为特定泛型优化  。

              泛型特化

              • 静态多态:在挪用站中只有一种类型  。Swift使用只有一种类型的特点  ,来举行类型降级取代  。

              • 类型降级后  ,发生特定类型的要领

              • 为泛型的每个类型缔造对应的要领  。这时间你可能会问  ,那每一种类型都发生一个新的要领 ,代码空间岂不爆炸?

              • 静态多态下举行特定优化specialization  。由于是静态多态  。以是可以举行很强盛的优化 ,好比举行内联实现 ,而且通过获取上下文来举行更进一步的优化  。从而降低要领数目 。优化后可以更准确和详细 。

              例如:

              func min<T:Comparable>(x:T, y:T) -> T {
                return y < x ? y : x
              }

              从通俗的泛型睁开如下 ,由于要支持所有类型的min要领 ,以是需要对泛型类型举行盘算 ,包罗初始化地址、内存分配、生命周期治理等  。除了对value的操作  ,还要对要领举行操作 。这是一个很是庞大重大的工程  。

              func min<T:Comparable>(x:T, y:T, FTable:FunctionTable) -> T {
                let xCopy = FTable.copy(x)
                let yCopy = FTable.copy(y)
                let m = FTable.lessThan(yCopy  , xCopy) ? y :x
                FTable.release(x)
                FTable.release(y)
                return m
              }

              在确定入参类型时 ,好比Int  ,编译器可以通过泛型特化  ,举行类型取代(Type Substitute)  ,优化为:

              func min<Int>(x:Int, y:Int) -> Int {
                return y < x ? y :x
              }

              泛型特化specilization是何时发生的?

              在使用特定优化时 ,挪用方需要举行类型推断  ,这里需要知晓类型的上下文  ,例如类型的界说和内部要领实现  。若是挪用方和类型是单独编译的  ,就无法在挪用方推断类型的内部实验  ,就无法使用特定优化  ,保证这些代码一起举行编译 ,这里就用到了whole module optimization 。而whole module optimization是对于挪用方和被挪用方的要领在差别文件时  ,对其举行泛型特化优化的条件  。

              泛型进一步优化

              特定泛型的进一步优化:

              // Pairs in our program using generic types
              struct Pair {
               init(_ f:T  , _ s:T) {
               first = f ; second = s
               }
               var first:T
               var second:T
              }
              let pairOfLines = Pair(Line(), Line())
              // ...

              let pairOfPoint = Pair(Point(), Point())

              在用到多种泛型  ,且确定泛型类型不会在运行时修改时 ,就可以对成对泛型的使用举行进一步优化  。

              优化的方式是将泛型的内存分配由指针指定 ,变为内存内联  ,不再有分外的堆初始化消耗  。请注重  ,由于举行了存储内联  ,已经确定了泛型特定类型的内存漫衍  ,泛型的内存内联不能存储差别类型  。以是再次强调此种优化只适用于在运行时不会修改泛型类型 ,即不能同时支持一个要领中包罗line和point两种类型  。

              ###whole module optimization

              whole module optimization是用于Swift编译器的优化机制  。可以通过-whole-module-optimization (或 -wmo)举行打开  。在XCode 8之后默认打开  。 Swift Package Manager在release模式默认使用whole module optimization  。module是多个文件荟萃  。

              编译器在对源文件举行语法剖析之后  ,会对其举行优化  ,天生机械码并输出目的文件  ,之后链接器团结所有的目的文件天生共享库或可执行文件  。

              whole module optimization通过跨函数优化  ,可以举行内联等优化操作 ,对于泛型  ,可以通过获取类型的详细实现来举行推断优化  ,举行类型降级要领内联 ,删除多余要领等操作  。

              全模块优化的优势

              • 编译器掌握所有要领的实现  ,可以举行内联泛型特化等优化  ,通过盘算所有要领的引用  ,移除多余的引用计数操作  。

              • 通过知晓所有的非公共要领  ,若是这写要领没有被使用 ,就可以对其举行消除 。

              怎样降低编译时间和全模块优化相反的是文件优化  ,即对单个文件举行编译  。这样的利益在于可以并行执行 ,而且对于没有修改的文件不会再次编译  。弱点在于编译器无法获知全貌  ,无法举行深度优化 。下面我们剖析下全模块优化怎样制止没修改的文件再次编译  。

              编译器内部运行历程分为:语法剖析  ,类型检查  ,SIL优化  ,LLVM后端处置惩罚 。

              语法剖析和类型检查一样平常很快  ,SIL优化执行了主要的Swift特定优化  ,例如泛型特化和要领内联等  ,该历程或许占用整个编译时间的三分之一 。LLVM后端执行占用了大部门的编译时间  ,用于运行降级优化和天生代码 。

              举行全模块优化后  ,SIL优化会将模块再次拆分为多个部门  ,LLVM后端通过多线程对这些拆分模块举行处置惩罚  ,对于没有修改的部门 ,不会举行再处置惩罚  。这样就制止了修改一小部门 ,整个大模块举行LLVM后端的再次执行 ,除此外 ,使用多线程并行操作也会缩短处置惩罚时间  。

              扩展:Swift的隐藏“Bug”

              Swift由于要领分配机制问题  ,以是在设计和优化后  ,会发生和我们通例明白不太一致的效果  ,这固然不能算Bug  。可是照旧要单独举行说明  ,制止在开发历程中  ,由于对机制的掌握不足 ,造成预期和执行收支导致的问题 。

              Message dispatch

              我们通过上面说明联合Static dispatch VS Dynamic dispatch对要领分配方式有了相识  。这里需要对Objective-C的要领分配方式举行说明  。

              熟悉OC的人都知道 ,OC接纳了运行时机制使用obj_msgSend发送新闻  ,runtime很是的天真 ,我们不仅可以对要领挪用接纳swizzling  ,对于工具也可以通过isa-swizzling来扩展功效 ,应用场景有我们常用的hook和各人熟知的KVO 。

              各人在使用Swift举行开发时都市问  ,Swift是否可以使用OC的运行时和新闻转发机制呢  ?谜底是可以  。

              Swift可以通过要害字dynamic对要领举行标志 ,这样就会告诉编译器  ,此要领使用的是OC的运行时机制 。