Swift 5.9 新特性揭秘:非复制类型的安全与高效

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

前言

在 Swift 中,类型默认是可复制的。这种设计简化了开发过程,因为它允许值在赋值给新变量或传递给函数时轻松复制。

然而,这种便利有时会导致意想不到的问题。例如,复制单次使用的票据或重复连接数据库可能会导致无效状态或资源冲突。

为了解决这些问题,在 Swift 5.9 中引入了非复制类型。通过将类型标记为~Copyable 来实现,我们可以显式地阻止 Swift 复制它。

这保证了值的唯一所有权,并施加了更严格的约束,从而降低了出错的风险。接下来让我们详细了解一下非复制类型。

非复制类型的示例

以下是一个非复制类型的简单示例:

rust 复制代码
struct SingleUseTicket: ~Copyable {
    let ticketID: String
}

与常规值类型的行为不同,当我们将非复制类型的实例分配给新变量时,值会被移动而不是复制。如果我们尝试在稍后使用原始变量,会得到编译时错误:

bash 复制代码
let originalTicket = SingleUseTicket(ticketID: "S645")
let newTicket = originalTicket

print(originalTicket.ticketID) // 报错 'originalTicket' used after consume

需要注意的是,类不能被声明为非复制类型。所有类类型仍然是可复制的,通过保留和释放对对象的引用来实现。

非复制类型中的方法

在非复制类型中,方法可以读取、修改或消费self

借用方法

非复制类型中的方法默认是借用borrowing 的。这意味着它们只能读取实例,允许安全地检查实例而不影响其有效性。

swift 复制代码
struct SingleUseTicket: ~Copyable {
    let ticketID: String
    
    func describe() {
        print("This ticket is \(ticketID).")
    }
}

let ticket = SingleUseTicket(ticketID: "A123")

// 打印 `This ticket is A123.`
ticket.describe()

可变方法

可变方法mutating 提供了对self 的临时写访问,允许在不使实例无效的情况下进行修改。

swift 复制代码
struct SingleUseTicket: ~Copyable {
    var ticketID: String

    mutating func updateID(newID: String) {
        ticketID = newID
        print("Ticket ID updated to \(ticketID).")
    }
}

var ticket = SingleUseTicket(ticketID: "A123")

// 打印 `Ticket ID updated to B456.`
ticket.updateID(newID: "B456")

消费方法

消费方法consuming 接管self 的所有权,一旦方法完成就使实例无效。这对于完成或处置资源的任务非常有用。在调用方法后,任何尝试访问实例的操作都会导致编译错误。

scss 复制代码
struct SingleUseTicket: ~Copyable {
    let ticketID: String
    
    consuming func use() {
        print("Ticket \(ticketID) used.")
    }
}

func useTicket() {
    let ticket = SingleUseTicket(ticketID: "A123")
    ticket.use()
    
    ticket.use() // 报错 'ticket' consumed more than once
}

useTicket()

需要注意的是,我们不能消费存储在全局变量中的非复制类型,因此在我们的示例中我们将代码包装在useTicket() 函数中。

非复制类型在函数参数中的应用

当将非复制类型作为参数传递给函数时,Swift 要求我们为该函数指定所有权模型。我们可以将参数标记为借用borrowing、输入输出inout 或消费consuming,每种标记提供不同级别的访问权限,类似于类型内部的方法。

借用参数

借用所有权允许函数临时读取值,而不消耗或修改它。

swift 复制代码
func inspectTicket(_ ticket: borrowing SingleUseTicket) {
    print("Inspecting ticket \(ticket.ticketID).")
}

输入输出参数

输入输出参数inout 提供了对值的临时写访问,允许函数修改它,同时将所有权返回给调用者。

swift 复制代码
func updateTicketID(_ ticket: inout SingleUseTicket, to newID: String) {
    ticket.ticketID = newID
    print("Ticket ID updated to \(ticket.ticketID).")
}

消费参数

当一个参数被标记为消费时,函数完全接管该值的所有权,使其对于调用者无效。例如,如果我们有一个消费方法,我们可以在函数中使用它,而无需担心在函数外部使用该值。

swift 复制代码
func processTicket(_ ticket: consuming SingleUseTicket) {
    ticket.use()
}

析构函数和丢弃操作符

非复制结构体和枚举可以像类一样拥有析构函数deinit,它们会在实例生命周期结束时自动运行。

swift 复制代码
struct SingleUseTicket: ~Copyable {
    let ticketID: Int
    
    deinit {
        print("Ticket deinitialized.")
        
        // 清理逻辑
    }
}

然而,当一个消费方法和一个析构函数都执行清理时,可能会有冗余操作的风险。为了解决这个问题,Swift 引入了丢弃操作符discard

通过在消费方法中使用discard self,我们可以显式阻止调用析构函数,从而避免重复逻辑:

swift 复制代码
struct SingleUseTicket: ~Copyable {
    let ticketID: Int
    
    consuming func invalidate() {
        print("Ticket \(ticketID) invalidated.")
        
        // 清理逻辑
        
        discard self
    }
    
    deinit {
        print("Ticket deinitialized.")
        
        // 清理逻辑
    }
}

另外需要注意的是,只有当我们的类型包含可轻松销毁的存储属性时,才能使用discard。不能包含引用计数、泛型。

总结

最近几年,swift 出了很多新特性,非复制类型是其中之一,实际开发中,非复制类型很少用到,但是了解这些特性,可以让我们在开发中更加得心应手。随着 Swift 的不断发展,这些类型代表了语言在性能和正确性方面的重大进步。

但是这些越来越复杂的特性也让 swift 初学者望而却步,希望这篇文章能帮助大家了解非复制类型,在实际开发中,如果需要使用非复制类型,可以参考这篇文章。

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

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

相关推荐
ZFJ_张福杰2 小时前
【Flutter】GetX最佳实践与避坑指南
android·flutter·ios·getx
阿蓝85817 小时前
iOS代码架构
ios
非专业程序员17 小时前
从0到1自定义文字排版引擎:原理篇
前端·ios
2501_9159184119 小时前
Video over HTTPS,视频流(HLSDASH)在 HTTPS 下的调试与抓包实战
网络协议·http·ios·小程序·https·uni-app·iphone
2501_916013741 天前
iOS 26 系统流畅度检测 从视觉特效到帧率稳定的实战策略
android·macos·ios·小程序·uni-app·cocoa·iphone
咕噜企业签名分发-淼淼1 天前
App防止恶意截屏功能的方法:iOS、Android和鸿蒙系统的实现方案
android·ios·harmonyos
Dream_Ji1 天前
Swift入门(二 - 基本运算符)
服务器·ssh·swift
游戏开发爱好者82 天前
iOS 26 崩溃日志深度解读,获取方式、系统变动、定位策略
android·macos·ios·小程序·uni-app·cocoa·iphone
深盾科技2 天前
如何读懂Mach-O:构建macOS和iOS应用安全的第一道认知防线
安全·macos·ios
FreeBuf_2 天前
iOS 0Day漏洞CVE-2025-24085相关PoC利用细节已公开
macos·ios·cocoa