详细讲讲 swift 5.9 出的新语法:参数包

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

前言

Swift 5.9 出了一个新功能,Swift Parameter Packs(Swift 参数包),在之前的文章中也有提到过,不过当时没讲参数包应该如何使用,今天就来学习一下什么是参数包,以及在日常开发中有哪些应用场景。

升级 Xcode 15 之后,你能用到哪些 swift 新特性

什么是 Swift Parameter Packs

我们用一个例子来说明,当我们在函数中使用泛型时,可能是这样的:

swift 复制代码
func eachFirst<T>(_ item: T) -> T?

当我们使用两个泛型时:

swift 复制代码
func eachFirst<T1, T2>(_ item1: T1, _ item2: T2) -> (T1?, T2?)

当我们使用三个泛型时:

swift 复制代码
func eachFirst<T1, T2, T3>(_ item1: T1, _ item2: T2, _ item3: T3) -> (T1?, T2?, T3?)

大家发现问题了吗?当使用的泛型越来越多,就会使得函数越来越长,越来越丑。

在 SwiftUI 框架中 buildBlock 函数竟然多达 10 个泛型:

swift 复制代码
static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View, C9: View

这看起来是不是非常令人头疼,swift 为了解决这个问题,提出了 Swift Parameter Packs 的概念,简单来讲就是,可以用 each 关键字来代替那些重复的代码,使用 each 之后的 buildBlock 函数变为:

swift 复制代码
static func buildBlock<each Content>(_ content: repeat each Content) -> TupleView<(repeat each Content)> where repeat each Content : View

对比一下是不是简洁多了。

接下来讲几个实用的例子。

KeyPath 取值

比如我有个用户类,然后我想要取其中的属性,并以元组的方式返回:

php 复制代码
class User {
    let id: String
    let name: String
    let age: Int
    init(id: String, name: String, age: Int) {
        self.id = id
        self.name = name
        self.age = age
    }
}

func valueAt(_ object: User, keyPath1: KeyPath<User, String>, keyPath2: KeyPath<User, String>, keyPath3: KeyPath<User, Int>) -> (String, String, Int) {
    return (object[keyPath: keyPath1], object[keyPath: keyPath2], object[keyPath: keyPath3])
}

let user = User(id: "0", name: "iOS 新知", age: 1)
print(valueAt(user, keyPath1: \.id, keyPath2: \.name, keyPath3: \.age))

实际上,这种写法既不优雅,也很麻烦,还不通用。

使用参数包的形式改一下:

swift 复制代码
func valuesAt<T, each U>(_ subject: T, keyPaths keyPath: repeat KeyPath<T, each U>) -> (repeat each U) {
    (repeat (subject[keyPath: each keyPath]))
}

修改完调用只需要:

scss 复制代码
valuesAt(user, keyPaths: \.id, \.name, \.age)

后面的参数可以传任意多个,而且使用泛型之后不仅 User 可以调用,其他的类也能使用。

缓存性能开销大的函数

有一些函数可能需要时间比较久,或者性能开销比较大,这时候的优化方案通常需要把操作过的缓存起来,不要每次都重复执行,比如加载图片比较耗时,那么就需要把加载过的图片缓存起来,那么你可能有这样的函数:

swift 复制代码
func fetchImage(url: String) -> UIImage {
    if let result = storage[url] {
        return result
    } else {
        // TODO: 加载图片
        return xxx
    }
}

这个方法不好的地方在于,它只适用于你当下图片的场景,可以把这一类需求使用参数包给封装起来:

sql 复制代码
func memoize<each Argument: Hashable, Return>(
    _ function: @escaping (repeat each Argument) -> Return
) -> (repeat each Argument) -> Return {
    var storage = [AnyHashable: Return]()
    
    return { (argument: repeat each Argument) in
        var key = [AnyHashable]()
        repeat key.append(AnyHashable(each argument))
        
        if let result = storage[key] {
            return result
        } else {
            let result = function(repeat each argument)
            storage[key] = result
            return result
        }
    }
}

这个函数要求传入一个函数,并返回一个新函数,那么上边缓存图片的例子就可以用这个函数实现:

scss 复制代码
// loadImage 是加载图片的函数
let memoizedLoadImage = memoize(loadImage)
memoizedLoadImage(URL(filePath: "some-url"))
memoizedLoadImage(URL(filePath: "some-url"))
memoizedLoadImage(URL(filePath: "other-url"))

当调用 memoizedLoadImage 函数时,函数体内会先从 storage 检查,如果存在直接返回,如果不存在,则调用传入的 loadImage 方法来获取。

装饰高阶函数

可以使用参数包把高阶函数封装起来,以便通用,举个例子:

less 复制代码
func decorateAround<each Argument, Return>(
    _ function: @escaping (repeat each Argument) -> Return,
    around: @escaping ((repeat each Argument) -> Return, repeat each Argument) -> Return
) -> (repeat each Argument) -> Return {
    { (argument: repeat each Argument) in
        around(function, repeat each argument)
    }
}

它接受两个参数:functionaround,并且返回一个新的函数,这个新函数也接受与原函数相同的参数,并将它们传递给 around 函数。相当于是把 function 装饰了一下。

听起来比较绕对吧?来看个例子:

swift 复制代码
func addTwoNumbers(_ a: Int, _ b: Int) -> Int {
    return a + b
}

// 创建一个装饰函数,用于在调用原始函数之前和之后输出信息
let decoratedAdd = decorateAround(addTwoNumbers) { (originalFunction, a, b) in
    print("调用 addTwoNumbers 之前")
    let result = originalFunction(a, b)
    print("调 addTwoNumbers 之后")
    return result
}

decoratedAdd(1, 2) // 打印 3

decorateAround 函数用于创建一个装饰函数 decoratedAdd,我在该函数在调用 addTwoNumbers 的前后输出两段信息。最后,我调用了装饰后的函数,观察输出的信息。

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

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

相关推荐
2501_915918415 小时前
HTTPS 端口号详解 443 端口作用、iOS 抓包方法、常见 HTTPS 抓包工具与网络调试实践
android·网络·ios·小程序·https·uni-app·iphone
全栈技术负责人9 小时前
Hybrid应用性能优化实战分享(本文iOS 与 H5为例,安卓同理)
前端·ios·性能优化·html5
Zender Han9 小时前
Flutter 视频播放器——flick_video_player 介绍与使用
android·flutter·ios·音视频
咕噜签名分发冰淇淋12 小时前
苹果ios的系统app应用WebClip免签应用开源及方式原理
ios·开源·cocoa
2501_9151063215 小时前
App Store 软件上架全流程详解,iOS 应用发布步骤、uni-app 打包上传与审核要点完整指南
android·ios·小程序·https·uni-app·iphone·webview
开开心心loky15 小时前
[iOS] ViewController 的生命周期
macos·ui·ios·objective-c·cocoa
2501_9160137420 小时前
App 上架全流程指南,iOS App 上架步骤、App Store 应用发布流程、uni-app 打包上传与审核要点详解
android·ios·小程序·https·uni-app·iphone·webview
牛蛙点点申请出战20 小时前
仿微信语音 WaveView 实现
android·前端·ios
TheLittleBoy1 天前
iOS 26支持的设备列表
ios·ios 26
Magnetic_h1 天前
【iOS】block复习
笔记·macos·ios·objective-c·cocoa