前言

京喜APP最早在2019年引入了Swift,应用Swift实现了第一个订单模块的开发。之后一年多咱们继续在团队/公司外部推广和遍及Swift,目前Swift曾经撑持了70%+以上的业务。通过应用Swift进步了团队内同学的开发效率,同时也带来了品质的晋升,目前来自Swift的Crash的占比不到1%。在这过程中一直的学习/实际,团队内的Code Review,也对如何应用Swift来进步代码品质有更深的了解。

Swift个性

在探讨如何应用Swift进步代码品质之前,咱们先来看看Swift自身相比ObjC或其余编程语言有什么劣势。Swift有三个重要的个性别离是富裕表现力/安全性/疾速,接下来咱们别离从这三个个性简略介绍一下:

富裕表现力

Swift提供更多的编程范式个性反对,能够编写更少的代码,而且易于浏览和保护。

  • 根底类型 - 元组、Enum关联类型
  • 办法 - 办法重载
  • protocol - 不限度只反对class、协定默认实现、专属协定
  • 泛型 - protocol关联类型、where实现类型束缚、泛型扩大
  • 可选值 - 可选值申明、可选链、隐式可选值
  • 属性 - let、lazy、计算属性`、willset/didset、Property Wrappers
  • 函数式编程 - 汇合filter/map/reduce办法,提供更多规范库办法
  • 并发 - async/await、actor
  • 规范库框架 - Combine响应式框架、SwiftUI申明式UI框架、CodableJSON模型转换
  • Result builder - 形容实现DSL的能力
  • 动态性 - dynamicCallable、dynamicMemberLookup
  • 其余 - 扩大、subscript、操作符重写、嵌套类型、区间
  • Swift Package Manager - 基于Swift的包管理工具,能够间接用Xcode进行治理更不便
  • struct - 初始化办法主动补齐
  • 类型推断 - 通过编译器弱小的类型推断编写代码时能够缩小很多类型申明
提醒:类型推断同时也会减少肯定的编译耗时,不过Swift团队也在一直的改善编译速度。

安全性

代码平安
  • let属性 - 应用let申明常量防止被批改。
  • 值类型 - 值类型能够防止在办法调用等参数传递过程中状态被批改。
  • 访问控制 - 通过publicfinal限度模块外应用class不能被继承重写
  • 强制异样解决 - 办法须要抛出异样时,须要申明为throw办法。当调用可能会throw异样的办法,须要强制捕捉异样防止将异样裸露到下层。
  • 模式匹配 - 通过模式匹配检测switch中未解决的case。
类型平安
  • 强制类型转换 - 禁止隐式类型转换防止转换中带来的异样问题。同时类型转换不会带来额定的运行时耗费。。
提醒:编写ObjC代码时,咱们通常会在编码时增加类型查看防止运行时解体导致Crash
  • KeyPath - KeyPath相比应用字符串能够提供属性名和类型信息,能够利用编译器查看。
  • 泛型 - 提供泛型和协定关联类型,能够编写出类型平安的代码。相比Any能够更多利用编译时查看发现类型问题。
  • Enum关联类型 - 通过给特定枚举指定类型防止应用Any
内存平安
  • 空平安 - 通过标识可选值防止空指针带来的异样问题
  • ARC - 应用主动内存治理防止手动治理内存带来的各种内存问题
  • 强制初始化 - 变量应用前必须初始化
  • 内存独占拜访 - 通过编译器查看发现潜在的内存抵触问题
线程平安
  • 值类型 - 更多应用值类型缩小在多线程中遇到的数据竞争问题
  • async/await - 提供async函数使咱们能够用结构化的形式编写并发操作。防止基于闭包的异步形式带来的内存循环援用和无奈抛出异样的问题
  • Actor - 提供Actor模型防止多线程开发中进行数据共享时产生的数据竞争问题,同时防止在应用锁时带来的死锁等问题

疾速

  • 值类型 - 相比class不须要额定的堆内存调配/开释和更少的内存耗费
  • 办法动态派发 - 办法调用反对动态调用相比原有ObjC音讯转发调用性能更好
  • 编译器优化 - Swift的动态性能够使编译器做更多优化。例如Tree Shaking相干优化移除未应用的类型/办法等缩小二进制文件大小。应用动态派发/办法内联优化/泛型特化/写时复制等优化进步运行时性能
提醒:ObjC音讯派发会导致编译器无奈进行移除无用办法/类的优化,编译器并不知道是否可能被用到。
  • ARC优化 - 尽管和ObjC一样都是应用ARCSwift通过编译器优化,能够进行更快的内存回收和更少的内存援用计数治理
提醒: 相比ObjC,Swift外部不须要应用autorelease进行治理。

代码质量指标

以上是一些常见的代码质量指标。咱们的指标是如何更好的应用Swift编写出合乎代码质量指标要求的代码。

提醒:本文不波及设计模式/架构,更多关注如何通过正当应用Swift个性做部分代码段的重构。

一些不错的实际

利用编译查看

缩小应用Any/AnyObject

因为Any/AnyObject短少明确的类型信息,编译器无奈进行类型查看,会带来一些问题:

  • 编译器无奈查看类型是否正确保障类型平安
  • 代码中大量的as?转换
  • 类型的缺失导致编译器无奈做一些潜在的编译优化

应用as?带来的问题

当应用Any/AnyObject时会频繁应用as?进行类型转换。这如同没什么问题因为应用as?并不会导致程序Crash。不过代码谬误至多应该分为两类,一类是程序自身的谬误通常会引发Crash,另外一种是业务逻辑谬误。应用as?只是防止了程序谬误Crash,然而并不能避免业务逻辑谬误。

func do(data: Any?) {    guard let string = data as? String else {        return    }    // }do(1)do("")

以下面的例子为例,咱们进行了as?转换,当dataString时才会进行解决。然而当do办法内String类型产生了扭转函数,应用方并不知道已变更没有做相应的适配,这时候就会造成业务逻辑的谬误。

提醒:这类谬误通常更难发现,这也是咱们在一次实在bug场景遇到的。
应用自定义类型代替Dictionary

代码中大量Dictionary数据结构会升高代码可维护性,同时带来潜在的bug

  • key须要字符串硬编码,编译时无奈查看
  • value没有类型限度。批改时类型无奈限度,读取时须要反复类型转换和解包操作
  • 无奈利用空平安个性,指定某个属性必须有值
提醒:自定义类型还有个益处,例如JSON自定义类型时会进行类型/nil/属性名查看,能够防止将谬误数据丢到下一层。

不举荐

let dic: [String: Any]let num = dic["value"] as? Intdic["name"] = "name"

举荐

struct Data {  let num: Int  var name: String?}let num = data.numdata.name = "name"

适宜应用Dictionary的场景

  • 数据不应用 - 数据并不读取只是用来传递。
  • 解耦 - 1.组件间通信解耦应用HashMap传递参数进行通信。2.跨技术栈边界的场景,混合栈间通信/前后端通信应用HashMap/JSON进行通信。
应用枚举关联值代替Any

例如应用枚举革新NSAttributedStringAPI,原有APIvalueAny类型无奈限度特定的类型。

优化前

let string = NSMutableAttributedString()string.addAttribute(.foregroundColor, value: UIColor.red, range: range)

革新后

enum NSAttributedStringKey {  case foregroundColor(UIColor)}let string = NSMutableAttributedString()string.addAttribute(.foregroundColor(UIColor.red), range: range) // 不传递Color会报错
应用泛型/协定关联类型代替Any

应用泛型协定关联类型代替Any,通过泛型类型束缚来使编译器进行更多的类型查看。

应用枚举/常量代替硬编码

代码中存在反复的硬编码字符串/数字,在批改时可能会因为不同步引发bug。尽可能减少硬编码字符串/数字,应用枚举常量代替。

应用KeyPath代替字符串硬编码

KeyPath蕴含属性名和类型信息,能够防止硬编码字符串,同时当属性名或类型扭转时编译器会进行查看。

不举荐

class SomeClass: NSObject {    @objc dynamic var someProperty: Int    init(someProperty: Int) {        self.someProperty = someProperty    }}let object = SomeClass(someProperty: 10)object.observeValue(forKeyPath: "", of: nil, change: nil, context: nil)

举荐

let object = SomeClass(someProperty: 10)object.observe(.someProperty) { object, change in}

内存平安

缩小应用!属性

!属性会在读取时隐式强解包,当值不存在时产生运行时异样导致Crash。

class ViewController: UIViewController {    @IBOutlet private var label: UILabel! // @IBOutlet须要应用!}
缩小应用!进行强解包

应用!强解包会在值不存在时产生运行时异样导致Crash。

var num: Int?let num2 = num! // 谬误
提醒:倡议只在小范畴的部分代码段应用!强解包。
防止应用try!进行错误处理

应用try!会在办法抛出异样时产生运行时异样导致Crash。

try! method()
应用weak/unowned防止循环援用
resource.request().onComplete { [weak self] response in  guard let self = self else {    return  }  let model = self.updateModel(response)  self.updateUI(model)}resource.request().onComplete { [unowned self] response in  let model = self.updateModel(response)  self.updateUI(model)}
缩小应用unowned

unowned在值不存在时会产生运行时异样导致Crash,只有在确定self肯定会存在时才应用unowned

class Class {    @objc unowned var object: Object    @objc weak var object: Object?}

unowned/weak区别:

  • weak - 必须设置为可选值,会进行弱援用解决性能更差。会主动设置为nil
  • unowned - 能够不设置为可选值,不会进行弱援用解决性能更好。然而不会主动设置为nil, 如果self已开释会触发谬误.

错误处理形式

  • 可选值 - 调用方并不关注外部可能会产生谬误,当产生谬误时返回nil
  • try/catch - 明确提醒调用方须要解决异样,须要实现Error协定定义明确的谬误类型
  • assert - 断言。只能在Debug模式下失效
  • precondition - 和assert相似,能够再Debug/Release模式下失效
  • fatalError - 产生运行时解体会导致Crash,应防止应用
  • Result - 通常用于闭包异步回调返回值

缩小应用可选值

可选值的价值在于通过明确标识值可能会为nil并且编译器强制对值进行nil判断。然而不应该随便的定义可选值,可选值不能用let定义,并且应用时必须进行解包操作绝对比拟繁琐。在代码设计时应思考这个值是否有可能为nil,只在适合的场景应用可选值。

应用init注入代替可选值属性

不举荐

class Object {  var num: Int?}let object = Object()object.num = 1

举荐

class Object {  let num: Int  init(num: Int) {    self.num = num  }}let object = Object(num: 1)
防止随便给予可选值默认值

在应用可选值时,通常咱们须要在可选值为nil时进行异样解决。有时候咱们会通过给予可选值默认值的形式来解决。然而这里应思考在什么场景下能够给予默认值。在不能给予默认值的场景该当及时应用return抛出异样,防止谬误的值被传递到更多的业务流程。

不举荐

func confirmOrder(id: String) {}// 给予谬误的值会导致谬误的值被传递到更多的业务流程confirmOrder(id: orderId ?? "")

举荐

func confirmOrder(id: String) {}guard let orderId = orderId else {    // 异样解决    return}confirmOrder(id: orderId)
提醒:通常强业务相干的值不能给予默认值:例如商品/订单id或是价格。在能够应用兜底逻辑的场景应用默认值,例如默认文字/文字色彩
应用枚举优化可选值

Object构造同时只会有一个值存在:

优化前

class Object {    var name: Int?    var num: Int?}

优化后

  • 升高内存占用 - 枚举关联类型的大小取决于最大的关联类型大小
  • 逻辑更清晰 - 应用enum相比大量应用if/else逻辑更清晰
enum CustomType {    case name(String)    case num(Int)}

缩小var属性

应用计算属性

应用计算属性能够缩小多个变量同步带来的潜在bug。

不举荐

class model {  var data: Object?  var loaded: Bool}model.data = Object()loaded = false

举荐

class model {  var data: Object?  var loaded: Bool {    return data != nil  }}model.data = Object()
提醒:计算属性因为每次都会反复计算,所以计算过程须要轻量防止带来性能问题。

控制流

应用filter/reduce/map代替for循环

应用filter/reduce/map能够带来很多益处,包含更少的局部变量,缩小模板代码,代码更加清晰,可读性更高。

不举荐

let nums = [1, 2, 3]var result = []for num in nums {    if num < 3 {        result.append(String(num))    }}// result = ["1", "2"]

举荐

let nums = [1, 2, 3]let result = nums.filter { $0 < 3 }.map { String($0) }// result = ["1", "2"]
应用guard进行提前返回

举荐

guard !a else {    return}guard !b else {    return}// do

不举荐

if a {    if b {        // do    }}
应用三元运算符?:

举荐

let b = truelet a = b ? 1 : 2let c: Int?let b = c ?? 1

不举荐

var a: Int?if b {    a = 1} else {    a = 2}
应用for where优化循环

for循环增加where语句,只有当where条件满足时才会进入循环

不举荐

for item in collection {  if item.hasProperty {    // ...  }}

举荐

for item in collection where item.hasProperty {  // item.hasProperty == true,才会进入循环}
应用defer

defer能够保障在函数退出前肯定会执行。能够应用defer中实现退出时肯定会执行的操作例如资源开释等防止脱漏。

func method() {    lock.lock()    defer {        lock.unlock()        // 会在method作用域完结的时候调用    }    // do}

字符串

应用"""

在定义简单字符串时,应用多行字符串字面量能够放弃原有字符串的换行符号/引号等特殊字符,不须要应用``进行本义。

let quotation = """The White Rabbit put on his spectacles.  "Where shall I begin,please your Majesty?" he asked."Begin at the beginning," the King said gravely, "and go ontill you come to the end; then stop.""""
提醒:下面字符串中的""和换行能够主动保留。
应用字符串插值

应用字符串插值能够进步代码可读性。

不举荐

let multiplier = 3let message = String(multiplier) + "times 2.5 is" + String((Double(multiplier) * 2.5))

举荐

let multiplier = 3let message = "(multiplier) times 2.5 is (Double(multiplier) * 2.5)"

汇合

应用规范库提供的高阶函数

不举荐

var nums = []nums.count == 0nums[0]

举荐

var nums = []nums.isEmptynums.first

访问控制

Swift中默认访问控制级别为internal。编码中该当尽可能减小属性/办法/类型的访问控制级别暗藏外部实现。

提醒:同时也有利于编译器进行优化。
应用private/fileprivate润饰公有属性办法
private let num = 1class MyClass {    private var num: Int}
应用private(set)润饰内部只读/外部可读写属性
class MyClass {    private(set) var num = 1}let num = MyClass().numMyClass().num = 2 // 会编译报错

函数

应用参数默认值

应用参数默认值,能够使调用方传递更少的参数。

不举荐

func test(a: Int, b: String?, c: Int?) {}test(1, nil, nil)

举荐

func test(a: Int, b: String? = nil, c: Int? = nil) {}test(1)
提醒:相比ObjC参数默认值也能够让咱们定义更少的办法。
限度参数数量

当办法参数过多时思考应用自定义类型代替。

不举荐

func f(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) {}

举荐

struct Params {    let a, b, c, d, e, f: Int}func f(params: Params) {}
应用@discardableResult

某些办法应用方并不一定会解决返回值,能够思考增加@discardableResult标识提醒Xcode容许不解决返回值不进行warning提醒。

// 上报办法应用方不关怀是否胜利func report(id: String) -> Bool {} @discardableResult func report2(id: String) -> Bool {}report("1") // 编译器会正告report2("1") // 不解决返回值编译器不会正告

元组

防止过长的元组

元组尽管具备类型信息,然而并不蕴含变量名信息,应用方并不清晰晓得变量的含意。所以当元组数量过多时思考应用自定义类型代替。

func test() -> (Int, Int, Int) {}let (a, b, c) = test()// a,b,c类型统一,没有命名信息不分明每个变量的含意

零碎库

KVO/Notification 应用 block API

block API的劣势:

  • KVO 能够反对 KeyPath
  • 不须要被动移除监听,observer开释时主动移除监听

不举荐

class Object: NSObject {  init() {    super.init()    addObserver(self, forKeyPath: "value", options: .new, context: nil)    NotificationCenter.default.addObserver(self, selector: #selector(test), name: NSNotification.Name(rawValue: ""), object: nil)  }  override class func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {  }  @objc private func test() {  }  deinit {    removeObserver(self, forKeyPath: "value")    NotificationCenter.default.removeObserver(self)  }}

举荐

class Object: NSObject {  private var observer: AnyObserver?  private var kvoObserver: NSKeyValueObservation?  init() {    super.init()    observer = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: ""), object: nil, queue: nil) { (_) in     }    kvoObserver = foo.observe(.value, options: [.new]) { (foo, change) in    }  }}

Protocol

应用protocol代替继承

Swift中针对protocol提供了很多新个性,例如默认实现关联类型,反对值类型。在代码设计时能够优先思考应用protocol来防止臃肿的父类同时更多应用值类型。

提醒:一些无奈用protocol代替继承的场景:1.须要继承NSObject子类。2.须要调用super办法。3.实现抽象类的能力。

Extension

应用extension组织代码

应用extension公有办法/父类办法/协定办法等不同性能代码进行拆散更加清晰/易保护。

class MyViewController: UIViewController {  // class stuff here}// MARK: - Privateextension: MyViewController {    private func method() {}}// MARK: - UITableViewDataSourceextension MyViewController: UITableViewDataSource {  // table view data source methods}// MARK: - UIScrollViewDelegateextension MyViewController: UIScrollViewDelegate {  // scroll view delegate methods}

代码格调

良好的代码格调能够进步代码的可读性,对立的代码格调能够升高团队内互相了解老本。对于Swift的代码格式化倡议应用主动格式化工具实现,将主动格式化增加到代码提交流程,通过定义Lint规定对立团队内代码格调。思考应用SwiftFormatSwiftLint

提醒:SwiftFormat次要关注代码款式的格式化,SwiftLint能够应用autocorrect主动修复局部不标准的代码。

常见的主动格式化修改

  • 移除多余的;
  • 最多只保留一行换行
  • 主动对齐空格
  • 限度每行的宽度主动换行

性能优化

性能优化上次要关注进步运行时性能和升高二进制体积。须要思考如何更好的应用Swift个性,同时提供更多信息给编译器进行优化。

应用Whole Module Optimization

Xcode开启WMO优化时,编译器能够将整个程序编译为一个文件进行更多的优化。例如通过推断final/函数内联/泛型特化更多应用动态派发,并且能够移除局部未应用的代码。

应用源代码打包

当咱们应用组件化时,为了进步编译速度打包效率,通常单个组件独立编译生成动态库,最初多个组件间接应用动态库进行打包。这种场景下WMO仅针对internal以内作用域失效,对于public/open短少内部应用信息所以无奈进行优化。所以对于大量应用Swift的我的项目,应用全量代码打包更有利于编译器做更多优化。

缩小办法动静派发

  • 应用final - class/办法/属性申明为final,编译器能够优化为动态派发
  • 应用private - 办法/属性申明为private,编译器能够优化为动态派发
  • 防止应用dynamic - dynamic会使办法通过ObjC音讯转发的形式派发
  • 应用WMO - 编译器能够主动剖析推断出final优化为动态派发

应用Slice共享内存优化性能

在应用Array/String时,能够应用Slice切片获取一部分数据。Slice保留对原始Array/String的援用共享内存数据,不须要重新分配空间进行存储。

let midpoint = absences.count / 2let firstHalf = absences[..

`提醒:应防止始终持有Slice,Slice会缩短原始Array/String的生命周期导致无奈被开释造成内存透露。

protocol增加AnyObject protocol AnyProtocol {}

protocol ObjectProtocol: AnyObject {}

当protocol仅限度为class应用时,继承AnyObject协定能够使编译器不须要思考值类型实现,进步运行时性能。

应用@inlinable进行办法内联优化 // 原始代码 let label = UILabel().then {     $0.textAlignment = .center     $0.textColor = UIColor.black     $0.text = "Hello, World!" }

以then库为例,他应用闭包进行对象初始化当前的相干设置。然而 then 办法以及闭包也会带来额定的性能耗费。

内联优化 @inlinable public func then(_ block: (Self) throws -> Void) rethrows -> Self { try block(self) return self }

// 编译器内联优化后 let label = UILabel() label.textAlignment = .center label.textColor = UIColor.black label.text = "Hello, World!"

属性 应用lazy延时初始化属性 class View { var lazy label: UILabel = { let label = UILabel() self.addSubView(label) return label }() }

lazy属性初始化会提早到第一次应用时,常见的应用场景:

初始化比拟耗时

可能不会被应用到

初始化过程须要应用self

提醒:lazy属性不能保障线程平安

防止应用private let属性

private let属性会减少每个class对象的内存大小。同时会减少包大小,因为须要为属性生成相干的信息。能够思考应用文件级private let申明或static常量代替。

不举荐 class Object { private let title = "12345" }

举荐 private let title = "12345" class Object { static let title = "" }

提醒:这里并不包含通过init初始化注入的属性。

应用didSet/willSet时进行Diff

某些场景须要应用didSet/willSet属性查看器监控属性变动,做一些额定的计算。然而因为didSet/willSet并不会查看新/旧值是否雷同,能够思考增加新/旧值判断,只有当值真的扭转时才进行运算进步性能。

优化前 class Object { var orderId: String? { didSet { // 拉取接口等操作 } } }

例如下面的例子,当每一次orderId变更时须要从新拉取以后订单的数据,然而当orderId值一样时,拉取订单数据是有效执行。

优化后 class Object { var orderId: String? { didSet { // 判断新旧值是否相等 guard oldValue != orderId else { return } // 拉取接口等操作 } } }

汇合 汇合应用lazy提早序列 var nums = [1, 2, 3] var result = nums.lazy.map { String($0) } result[0] // 对1进行map操作 result[1] // 对2进行map操作

在汇合操作时应用lazy,能够将数组运算操作推延到第一次应用时,防止一次性全副计算。

提醒:例如长列表,咱们须要创立每个cell对应的视图模型,一次性创立太消耗工夫。

应用适合的汇合办法优化性能 不举荐 var items = [1, 2, 3] items.filter({ $0 > 1 }).first // 查找出所有大于1的元素,之后找出第一个

举荐 var items = [1, 2, 3] items.first(where: { $0 > 1 }) // 查找出第一个大于1的元素间接返回

应用值类型

Swift中的值类型次要是构造体/枚举/元组。

启动性能 - APP启动时值类型没有额定的耗费,class有肯定额定的耗费。

运行时性能- 值类型不须要在堆上调配空间/额定的援用计数治理。更少的内存占用和更快的性能。

包大小 - 相比class,值类型不须要创立ObjC类对应的ro\_data\_t数据结构。

提醒:class即便没有继承NSObject也会生成ro\_data\_t,外面蕴含了ivars属性信息。如果属性/办法申明为@objc还会生成对应的办法列表。

提醒:struct无奈代替class的一些场景:1.须要应用继承调用super。2.须要应用援用类型。3.须要应用deinit。4.须要在运行时动静转换一个实例的类型。

提醒:不是所有struct都会保留在栈上,局部数据大的struct也会保留在堆上。

汇合元素应用值类型

汇合元素应用值类型。因为NSArray并不反对值类型,编译器不须要解决可能须要桥接到NSArray的场景,能够移除局部耗费。

纯动态类型防止应用class

当class只蕴含静态方法/属性时,思考应用enum代替class,因为class会生成更多的二进制代码。

不举荐 class Object { static var num: Int static func test() {} }

举荐 enum Object { static var num: Int static func test() {} }

提醒:为什么用enum而不是struct,因为struct会额定生成init办法。

值类型性能优化 思考应用援用类型

值类型为了维持值语义,会在每次赋值/参数传递/批改时进行复制。尽管编译器自身会做一些优化,例如写时复制优化,在批改时缩小复制频率,然而这仅针对于规范库提供的汇合和String构造无效,对于自定义构造须要本人实现。对于参数传递编译器在一些场景会优化为间接传递援用的形式防止复制行为。

然而对于一些数据特地大的构造,同时须要频繁变更批改时也能够思考应用援用类型实现。

应用inout传递参数缩小复制

尽管编译器自身会进行写时复制的优化,然而局部场景编译器无奈解决。

不举荐 func append\_one(\_ a: [Int]) -> [Int] { var a = a a.append(1) // 无奈被编译器优化,因为这时候有2个援用持有数组 return a }

var a = [1, 2, 3] a = append_one(a)

举荐

间接应用inout传递参数

func append\_one\_in_place(a: inout [Int]) { a.append(1) }

var a = [1, 2, 3] append\_one\_in_place(&a)

应用isKnownUniquelyReferenced实现写时复制

默认状况下构造体中蕴含援用类型,在批改时只会从新拷贝援用。然而咱们心愿CustomData具备值类型的个性,所以当批改时须要从新复制NSMutableData防止复用。然而复制操作自身是耗时操作,咱们心愿能够缩小一些不必要的复制。

优化前 struct CustomData { fileprivate var \_data: NSMutableData var \_dataForWriting: NSMutableData { mutating get { \_data = \_data.mutableCopy() as! NSMutableData return data } } init( data: NSData) { self._data = data.mutableCopy() as! NSMutableData }

mutating func append(_ other: MyData) {         _dataForWriting.append(other._data as Data)}

}

var buffer = CustomData(NSData()) for _ in 0..<5 { buffer.append(x) // 每一次调用都会复制 }

优化后

应用isKnownUniquelyReferenced查看如果是惟一援用不进行复制。

final class Box { var unbox: A init(_ value: A) { self.unbox = value } }

struct CustomData { fileprivate var \_data: Box var \_dataForWriting: NSMutableData { mutating get { // 查看援用是否惟一 if !isKnownUniquelyReferenced(&\_data) { \_data = Box(_data.unbox.mutableCopy() as! NSMutableData) } return data.unbox } } init( data: NSData) { self._data = Box(data.mutableCopy() as! NSMutableData) } }

var buffer = CustomData(NSData()) for _ in 0..<5 { buffer.append(x) // 只会在第一次调用时进行复制 }

提醒:对于ObjC类型isKnownUniquelyReferenced会间接返回false。

缩小应用Objc个性 防止应用Objc类型

尽可能防止在Swift中应用NSString/NSArray/NSDictionary等ObjC根底类型。以Dictionary为例,尽管Swift Runtime能够在NSArray和Array之间进行隐式桥接须要O(1)的工夫。然而字典当Key和Value既不是类也不是@objc协定时,须要对每个值进行桥接,可能会导致耗费O(n)工夫。

缩小增加@objc标识

@objc标识尽管不会强制应用音讯转发的形式来调用办法/属性,然而他会默认ObjC是可见的会生成和ObjC一样的ro\_data\_t构造。

防止应用@objcMembers

应用@objcMembers润饰的类,默认会为类/属性/办法/扩大都加上@objc标识。

@objcMembers class Object: NSObject { }

提醒:你也能够应用@nonobjc勾销反对ObjC。

防止继承NSObject

你只须要在须要应用NSObject个性时才须要继承,例如须要实现UITableViewDataSource相干协定。

应用let变量/属性 优化汇合创立

汇合不须要批改时,应用let润饰,编译器会优化创立汇合的性能。例如针对let汇合,编译器在创立时能够调配更小的内存大小。

优化逃逸闭包

在Swift中,当捕捉var变量时编译器须要生成一个在堆上的Box保留变量用于之后对于变量的读/写,同时须要额定的内存治理操作。如果是let变量,编译器能够保留值复制或援用,防止应用Box。

防止应用大型struct应用class代替

大型struct通常是指属性特地多并且嵌套类型很多。目前swift编译器针对struct等值类型编译优化解决的并不好,会生成大量的assignWithCopy、assignWithCopy等copy相干办法,生成大量的二进制代码。应用class类型能够防止生成相干的copy办法。

提醒:不要小看这部分二进制的影响,集体在日常我的项目中遇到过简单的大型struct能生成几百KB的二进制代码。然而目前并没有好的办法去发现这类struct去做优化,只能通过相干工具去查看生成的二进制详细信息。心愿官网能够早点优化。

优先应用Encodable/Decodable协定代替Codable

因为实现Encodable和Decodable协定的构造,编译器在编译时会主动生成对应的init(from decoder: Decoder)和encode(to: Encoder)办法。Codable同时实现了Encodable和Decodable协定,然而大部分场景下咱们只须要encode或decode能力,所以明确指定实现Encodable或Decodable协定能够缩小生成对应的办法缩小包体积。

提醒:对于属性比拟多的类型构造会产生很大的二进制代码,有趣味能够用相干的工具看看生成的二进制文件。

缩小应用Equatable协定

因为实现Equatable协定的构造,编译器在编译时会主动生成对应的equal办法。默认实现是针对所有字段进行比拟会生成大量的代码。所以当咱们不须要实现==比拟能力时不要实现Equatable或者对于属性特地多的类型也能够思考重写Equatable协定,只针对局部属性进行比拟,这样能够生成更少的代码缩小包体积。

提醒:对于属性特地多的类型也能够思考重写Equatable协定,只针对局部属性进行比拟,同时也能够晋升性能。

总结

集体从Swift3.0开始将Swift作为第一语言应用。编写Swift代码并不只是简略对于ObjC代码的翻译/重写,须要对于Swift个性更多的了解能力更好的利用这些个性带来更多的收益。同时咱们须要关注每个版本Swift的优化/改良和新个性。在这过程中也会进步咱们的编码能力,加深对于一些通用编程概念/思维的了解,包含空平安、值类型、协程、不共享数据的Actor并发模型、函数式编程、面向协定编程、内存所有权等。对于新的古代编程语言例如Swift/Dart/TS/Kotlin/Rust等,很多个性/思维都是互相借鉴,当咱们了解这些概念/思维当前对于了解其余语言也会更容易。

这里举荐有趣味能够关注Swift Evolution,每个个性退出都会有一个提案,外面会具体介绍动机/应用场景/实现形式/将来方向。

扩大链接

The Swift Programming Language

Swift 进阶

SwiftLint Rules

OptimizationTips

深刻分析Swift性能优化

Google Swift Style Guide

Swift Evolution

Dictionary

Array

String

struct`