内联优化 @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) // 每一次调用都会复