swift计算机代码,如何编写高质量的 Swift 代码

本文介绍了Swift 5.3的新特性,并探讨了如何通过使用final、private等关键字,优化容器(如Array和Dictionary)的使用,理解溢出检查,泛型,大值类型以及明确类协议等方式来提升代码的运行效率。此外,还提到了逃逸闭包的影响以及编译器在优化过程中的作用。
摘要由CSDN通过智能技术生成

Swift 刚刚正式发布了 5.3 版本,增加了很多新特性,比如上一篇的多尾随闭包。从 0.9 到 5.3 横跨数年,Swift 在语法、运行效率、易用性都在不断的提升和优化。对于开发者来说,如何写出高质量的 Swift 代码将提升程序的运行效率。

预备知识

Swift 的编译流程

1c9a76cd0cbf?utm_campaign=hugo

Swift 编译流程

相对于Objective-C,Swift 语言在编译过程中增加了了 SIL 优化,是专门针对 Swift 的二次优化。从上图可以看出,在 AST 的基础上,进一步生成了 Swift 的高级中间语言 SIL,它与 LLVM IR 一起进行解析和优化,SIL 是其他语言没有的,在这里进行额外的优化。举个例子:

func test1() { print("hahaha") }

func test2() { test1() }

test2()

// 这样的代码,编译器会优化成直接调用 print("hahaha"),从而忽略对中间方法的调用。

优化虽然能提升运行效率,也会减缓编译时间(参考Debug与Release的时间),Debug 模式下默认关闭优化。

修改优化等级方式: BuildSetting - Compilation Mode。

Swift 的派发方式

1c9a76cd0cbf?utm_campaign=hugo

派发方式

在 Swift 中的派发方式分为:

静态派发/直接派发:在编译时就确定了,与动态派发相比非常快,编译器知道要执行的函数,不需要像动态派发那样直到运行时才确定调用方法,意味着不会有多余的通信开销。

动态派发-方发表:引用类型,尤其是类和协议的派发方式,其中类为V-Table(Virtual Table 虚函数表), 协议为PWT(Protocol Witness Table 协议见证表?)

动态派发-消息:最为灵活,支持运行时更新新的函数实现。

其中静态派发方法最快,可以粗暴的理解为直接取址。协议的派发方式也是使用动态派发-方发表的方式,苹果对其进行了强化以实现 Swift 中各种强大的 Protocol 特性。消息派发可参考 Objective-C。

编译过程中编译器会自动识别可优化为静态派发的部分。

Swift 中的写时复制(Copy On Write)

简单的说,在 Swift 中大量使用着值类型 (Value Type),一般情况下使用新的变量去获取值类型对象时就会触发复制的操作,在很多时候,只是持有对象并不会对对象进行修改,这时就会造成不必要的复制开销。而 Swfit 的写时复制意味着只有对象会发生更改时才会触发复制操作。

正文

1. final,private\fileprivate,internal

Swift在编译过程中会对“确定”的代码进行优化,是否“确定”与代码的派发方式有关,动态派发的代码为“不确定”,进而不能优化。在开发中对代码使用 private,final 等来标识代码,编译器能更好的的进行优化。

动态派发很强,但直接派发很快。

被 final 修饰的属性、类、方法不会被覆盖,编译器就可以知道能优化为直接调用,而不用去进行动态派发。final 能让编译器在优化时能更好的识别并优化。

private、fileprivate 均表示在其范围外部不可见,进而帮助编译器自动推断出 final 并删掉对方法和属性的间接引用。

internal 是 Swift 的默认标识,不需要显式标记。表示内部的,编译器能自动推断 final。

熟练使用 final、private 等标识并不仅仅在于给代码设定权限,更加规范。也帮助了编译器更好的优化我们的代码。

2. 容器中的类型

容器主要是指 Array 与 Dictionary,Swift 中两者均可以存入值类型和引用类型,值类型的效率比引用类型高,也是 Swift 推荐的方式。某些情况下,容器在使用值类型可能会造成不必要的开销,对此主要有以下几点优化:

1)与引用类型不同的是,值类型在容器中时,只有在递归过程中才会进行引用计数,避免了额外的保留,进而优化了容器的使用效率。

2)开发中可以将 Array 看做是 OC 中 NSArray 与 NSMutableArray 当不需要与 NSArray 有桥接关系时 使用 ContiguousArray 来当做引用类型的容器。ContiguousArray 与 Array 的不同点在于其强制在内存上连续,效率比 Array 更高。

3)容器在 写时复制 的特性下,可能造成不必要的副本,在方法中需要修改的参数使用 inout 来避免这种情况。

3. 溢出检查

Swift 会对数值运算进行溢出检查,当确定计算不会造成溢出时,溢出检查就显得多余了,在需要进行大量运算的地方溢出检查就会对运算效率造成影响。此时,我们可以使用 Wrapping operations( &+、&-、&* )来避免溢出检查。

let value = 1 &+ 1

4. 泛型

编译器会查看泛型的每一次调用,并将其转换为专门的调用(参考类型推断)。仅当泛型的的声明在当前模块中可见时,优化器才能执行特化。仅当声明与泛型调用位于同一个文件中时,才会发生这种情况,除非使用了-whole-module-optimization标志。注意标准库是一种特殊情况,标准库中的定义在所有模块中均可见,并且可以进行专门化。

5. 大值类型

值类型复制时会创建一个副本,大值类型可能很耗时,降低效率。例如,值类型的树结构。

对这样的树类型采用“写时复制”时,比如使用 Array 将其包装。但这又引入了 Array 的所有方法,以及 Array 本身与 OC 的交互,索引的访问,都降低了效率。对此,自定义结构是个不错的建议:

final class Ref {

var val: T

init(_ v: T) {val = v}

}

struct Box {

var ref: Ref

init(_ x: T) { ref = Ref(x) }

var value: T {

get { return ref.val }

set {

if !isKnownUniquelyReferenced(&ref) {

ref = Ref(newValue)

return

}

ref.val = newValue

}

}

}

自定义的 Box 简化了 Array 非必要部分,针对树结构重新设计容器。

6. 明确类协议

将仅由类满足的协议标记为类协议,编译器可以基于仅类满足该协议的特点来优化程序。

例如,如果 ARC 内存管理系统知道它正在处理类,则可以轻松保留(增加对象的引用计数)。在没有这种特点的情况下,编译器必须假定对象可以满足协议,并且需要保留或释放不确定的对象,这可能会很昂贵。

protocol Pingable: AnyObject { func ping() -> Int }

7. let/var 逃逸闭包

任何时候使用 let/var 来创建闭包绑定时,都会产生逃逸闭包,而当逃逸闭包被 var 捕获时,就会分配到堆区。当被 let 捕获时,是当做值捕获的,不必再存储副本。

如果闭包并没有逃逸,方法传递时就使用inout将其进行转义,这样就不会再被堆区那套保留/释放所影响。

总结

通过借助 编译过程优化、值类型、派发方式、类型推断 以及 特定函数 等语言特性来对 Swift 进行优化。想要达到一个好的优化水准就需要对这些特性有深入了解。对优化过程中衍生出来的问题也要有一定认知。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值