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

一.异变方法

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调度方式为函数表派发
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容