如何在 Swift 中使用 @autoclosure 来提高性能

关注我,每天分享一个关于 iOS 的新知识

前言

在 Swift,有一种不常用的闭包类型 -- @autoclosure,这个关键字可以让我们将一个表达式封装到一个闭包中,在需要该表达式结果的时候才执行该闭包(类似于懒加载)。这为我们提供了一种延迟执行代码的方式,可以有效提高性能。

虽然业务开发中不经常使用,但是在 swift 标准库中使用场景非常多,关于怎么使用我们来用一些示例来详细聊聊。

使用方法

举个例子,在此示例中,我们创建了一个 log 方法和一个 Person 结构体,当调用 description 属性时,将会打印日志:

swift 复制代码
struct Person {
    let name: String
    
    var description: String {
        print("调用了 Person description.")
        return "Person name is \(name)"
    }
}

func log(_ message: String) {
    if false {
        print("\(message)")
    }
}

let person = Person(name: "iOS 新知")
log(person.description)

执行上边的代码,将会看到控制台输出"调用了 Person description."

因为我的代码中写了 if false,所以永远不可能执行 if 条件下的打印,也就不会访问 message 这个参数,从代码效率上来说这时候 Person 的 description 属性执行就是无效的(如果 description 中有大量的运算,会导致性能下降)。这是因为调用 person.description 的时候就直接执行了 description 这个计算属性。

让我们改一下代码,把 message 参数改成一个闭包,只有函数内调用这个闭包才执行 description 函数:

swift 复制代码
struct Person {
    let name: String
    
    var description: String {
        print("调用了 Person description.")
        return "Person name is \(name)"
    }
}

func log(_ message: () -> String) {
    if false {
        print("\(message())")
    }
}

let person = Person(name: "iOS 新知")
log({ person.description })

我把 log 函数的 message 参数由 String 类型,改成了 () -> String 闭包类型,调用的时候也改成了 log({ person.description }),此时再运行代码,没有执行 description 属性,符合我们的预期。

但是每次调用 log 函数都要将参数用大括号包裹,用起来十分的不便,这时候 @autoclosure 就要登场了 ,在 log 函数的 message 参数中增加这个关键字,调用的时候就不需要大括号了,最终代码如下:

swift 复制代码
struct Person {
    let name: String
    
    var description: String {
        print("调用了 Person description.")
        return "Person name is \(name)"
    }
}

func log(_ message: @autoclosure () -> String) {
    if false {
        print("\(message())")
    }
}

let person = Person(name: "iOS 新知")
log(person.description)

这份代码看起来就清爽多了,既实现了我们的需求(访问 message 的时候才调用 description),调用方式又和之前一样。

一些其他例子

1、延迟实例化对象

less 复制代码
struct Canvas {
    let width: CGFloat
    let height: CGFloat
    
    func drawShape() {}
}

func draw(canvas: @autoclosure () -> Canvas) {
    if shouldDraw {
        canvas().drawShape()
    }
}

// 调用时才创建 Canvas 实例
draw(canvas: Canvas(width: 200, height: 100))

Canvas 对象只在需要绘制时才创建调用 init 方法创建。

2、减少内存使用

less 复制代码
func decode(image: @autoclosure () -> UIImage) -> Image? {
    if shouldDecode {
        return decodeImage(image())
    } else {
        return nil
    }
}
let img = decode(image: loadImageFromDisk())

图片解码可能很占内存,应该只在需要时(shouldDecode)解码图片,避免不必要的内存占用。

3、避免冗余计算

less 复制代码
func hash(value: @autoclosure () -> String) -> Int {
    if let hashValue = self.hashValue {
        return hashValue
    }
    return hashFunction(value())
}
let hash1 = hash(value: someString)
let hash2 = hash(value: someString)

有些操作开销比较大,可以使用 @autoclosure 避免重复计算。

4、字典取值时的默认值计算

之前的文章中介绍过字典取值时可以设置默认值,其实在 swift 底层就用到了 @autoclosure:

less 复制代码
public subscript(key: Key, default defaultValue: @autoclosure () -> Value) -> Value

因为在字典取不到 key 对应的值的情况下,才需要计算默认值,而不是每次都计算。

这里每天分享一个 iOS 的新知识,快来关注我吧

本文同步自微信公众号 "iOS新知",每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!

相关推荐
Anhty15 小时前
2026最新免费手机音频处理工具 !!
android·功能测试·ios·智能手机·音视频
2501_9159090617 小时前
iOS应用从开发到上架App Store的完整发布流程与步骤指南
android·ios·小程序·https·uni-app·webview
初雪云18 小时前
UniApp 云打包拿到 IPA 后,真正的发布工作才刚开始
ios·自动化·编辑器·产品经理·iphone
Lancker20 小时前
没有 Mac 也能上架 iOS:GitHub Actions 全自动构建 + 签名 + TestFlight 送审实录
macos·ios·github
茶底世界之下20 小时前
晚霞要多暖?用 Awaking 只改色温做一次取舍
ios
酒神dnspup21 小时前
多地区网站可用性监控怎么做?DNSPup 从基线到告警的完整实战
运维·ios·云计算·iphone·dns·网络监控
福兮说1 天前
浏览器里把 iPhone 的 HEIC 转成 JPG:Chrome 解不开、转完大了七成、拍摄时间全丢,六个坑实测
前端·javascript·图像处理·chrome·ios·iphone·heic
程序员老刘2 天前
Flet 1.0 思路很好,可惜来晚了
flutter·ai编程·客户端
茶底世界之下2 天前
时间线效果层,为什么不能只复用一个处理器?
ios·swift
ZZH_AI项目交付2 天前
实时字幕负责快,本地录音负责不丢:一个 iOS 录音功能为什么需要两条链路
ios·ai编程