递归枚举在Swift中的妙用,开发者必学的高级技巧!

850 阅读4分钟

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

前言

作为一个 Swift 开发者,枚举(enum)肯定再熟悉不过了。但你有听说过递归枚举吗?

大家都知道,枚举是一种将相关值组合在一起的类型,而递归枚举则更进一步,允许枚举中的某些情况包含自身的实例。这种特性在建模嵌套或递归性质的数据时非常有用。通过允许类型引用自身,递归枚举为表示复杂关系(如文件系统或组织结构)提供了一种简单且类型安全的方式。

接下来让我们仔细讲讲递归枚举的使用和实现。

递归枚举的实现

为了在枚举中实现递归,Swift 使用了 indirect 关键字。这个关键字表示 Swift 以支持安全递归的方式处理枚举的内存,防止因循环引用可能引发的问题。

可能不太好理解,我们来举个例子。

假设我们要用枚举实现一个文件系统,你可能会这么设计:

enum FileSystemItem {
    case file(name: String)
    case folder(name: String)
}

但这并不能完全实现一个文件夹的表示,因为文件夹中不仅可以文件,还可以包含文件夹,文件夹中又可以包含其他文件和文件夹。

这种层级结构是使用递归枚举的完美应用场景。我们重新改写一下上边的代码:

enum FileSystemItem {
    case file(name: String)
    case folder(name: String, items: [FileSystemItem])
    indirect case alias(name: String, to: FileSystemItem)
}

在这个例子中,FileSystemItem 有三种情况。

  • file 表示单个文件。

  • folder 表示一个目录,可以嵌套 FileSystemItem 实例的数组,允许它包含子文件和其他文件夹。

  • alias 通过 indirect 标记,因为 to 参数是一个 FileSystemItem 实例。

使用的时候,我们可以这么写:

var photos = [FileSystemItem]()
let photoFile = FileSystemItem.file(name: "photo.png")
photos.append(FileSystemItem.file(name: "photo.png"))
photos.append(FileSystemItem.alias(name: "test", to: photoFile))
let fileItem = FileSystemItem.folder(name: "photos", items: photos)

创建文件系统

再举个例子,使用这个递归枚举,我们可以轻松创建一个简单的文件系统。

假设我们有两个文件要放在 "Documents" 文件夹中,另外我们还要在 "Desktop" 文件夹中创建一个指向其中一个文件的别名:

let imageFile = FileSystemItem.file(name: "photo.png")
let textFile = FileSystemItem.file(name: "notes.txt")

let documentsFolder = FileSystemItem.folder(
    name: "Documents",
    items: [imageFile, textFile]
)

let profileImageAlias = FileSystemItem.alias(name: "ProfileImage", to: imageFile)

let desktopFolder = FileSystemItem.folder(
    name: "Desktop",
    items: [documentsFolder, profileImageAlias]
)

在这个设置中,imageFiletextFile 是单个文件,而 documentsFolder 是一个包含这些文件的文件夹。

profileImageAlias 是一个指向 imageFile 的别名,desktopFolder 则包含 documentsFolder 和别名。

这种结构展示了如何使用递归枚举创建层次化且易于管理的文件系统,同时具有别名的灵活性。

计算总项目数

我们再来扩展一下,你可能想要知道某个文件夹中(包括子文件夹和别名)所有项目(文件、文件夹和别名)的总数。

我们来写个函数计算这个:

func countItems(in item: FileSystemItem) -> Int {
    switch item {
    case .file:
        return 1
    case .folder(_, let items):
        return items.map(countItems).reduce(0, +)
    case .alias(_, let to):
        return countItems(in: to)
    }
}

let totalItems = countItems(in: desktopFolder)
print("Total items: \(totalItems)")

解释一下:

  • 如果当前项是文件,返回 1。

  • 如果当前项是文件夹,递归计算文件夹中的项目数,并将这些计数相加。

  • 如果当前项是别名,递归计算别名指向的项目的计数。

reduce() 函数汇总这些计数,不论它们嵌套多深或有多少别名,最后都能算出总项目数。

递归枚举的优势是什么?

通过本文的讲解,你应该对递归枚举有了一个基本的了解。

alias 情况下使用 indirect 关键字是实现递归枚举的关键。

当一个情况直接引用枚举自身时,需要使用 indirect 关键字,如 alias(name: String, to: FileSystemItem)

主要注意的是,当引用是通过集合类型(如 items: [FileSystemItem] 中的数组)进行时,我们并没有加 indirect 标记,因为这里数组本身引入了必要的处理,编译器不需要 indirect

另外,也可以把 indirect 标记在枚举上,这样枚举中的所有情况都会被递归处理。

indirect enum FileSystemItem {
    case file(name: String)
    case folder(name: String, items: [FileSystemItem])
    case alias(name: String, to: FileSystemItem)
}

递归枚举是 Swift 中强大的特性,允许我们清晰且精确地建模复杂的层次数据结构。通过理解如何以及何时使用 indirect 关键字,我们可以利用递归枚举创建各种应用的健壮且可维护的模型。

如果你是一名经验丰富的 Swift 开发者,想学习高级技巧,请持续关注我,让我们一起探索这些高级编程技巧吧。

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

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