hiccLoghicc log by wccHipo Log

Swift中的模式匹配

iPhone 13 微距,水中花,一家人在青青世界

Swift 作为一门现代语言,除去安全,快速等特性之外,还有个明显有别于其他语言的特性,就是巨量细致入微的语言特性。类似iOS API,初学者觉得繁杂,无从下手,但是熟悉之后,绝对能少写不少代码。

其中强大的模式匹配绝对让你用的很爽。

主要整理自:pattern-matching-in-swift

迭代器中#

我们经常会在for循环中,使用if判断。

func deleteMarkedMessages() {
    for message in messages {
        if message.isMarked {
            database.delete(message)
        }
    }
}

上面绝对能工作,你可能会使用函数式的写法。

func deleteMarkedMessages() {
    messages.filter { $0.isMarked }
            .forEach(database.delete)
}

上述可能需要团队熟悉函数式编程,而且整个数组会经过两遍处理。

而Swift中where判断,则是此类情况的绝佳解决方案。

func deleteMarkedMessages() {
    for message in messages where message.isMarked {
        database.delete(message)
    }
}

而对于Swift Optional数据来说,例如类似的数据结构:

struct Match {
    var startDate: Date
    var players: [Player?]
}

假设我们需要循环players,且需要排除nil的值,我们可以使用传统的if判断,或者使用函数式的,compactMap函数。但是在Swift中有个更高阶的方法,使用for case let语法。

func makePlayerListView(for players: [Player?]) -> UIView {
    let view = PlayerListView()

    for case let player? in players {
        view.addEntryForPlayer(named: player.name,
                               image: player.image)
    }

    return view
}

这个乍看很奇怪,一般只会在switchenum声明中才能看到case。但是实际上,swift中optional值底层是Optional<Wrapped>的枚举enum,而且swift的模式匹配不是只在switch下才能工作。

switch中的optional可选判断#

例如如下的enum,

enum LoadingState {
    case loading
    case failed(Error)
}

在无状态的情况下,我们使用可选optional的LoadingState?,在switch匹配中,我们同样可以将? 使用在case的情况,以此来匹配有值的情况。

extension ContentViewController: ViewModelDelegate {
    func viewModel(_ viewModel: ViewModel,
                   loadingStateDidChangeTo state: LoadingState?) {
        switch state {
        case nil:
            removeLoadingSpinner()
            removeErrorView()
            renderContent()
        case .loading?:
            removeErrorView()
            showLoadingSpinner()
        case .failed(let error)?:
            removeLoadingSpinner()
            showErrorView(for: error)
        }
    }
}

声明式的错误处理#

错误处理,特别是http错误处理通常挺复杂的,一大堆if else代码。而在swift的强大的模式匹配下,我们可以写出声明式的代码。

func handle(_ error: Error) {
    switch error {
    // Matching against a group of offline-related errors:
    case URLError.notConnectedToInternet,
         URLError.networkConnectionLost,
         URLError.cannotLoadFromNetwork:
        showOfflineView()
    // Matching against a specific error:
    case let error as HTTPError where error == .unauthorized:
        logOut()
    // Matching against our networking error type:
    case is HTTPError:
        showNetworkErrorView()
    // Fallback for other kinds of errors:
    default:
        showGenericErrorView(for: error)
    }
}

模式匹配底层逻辑,以及自定义模式匹配#

 Swift中模式匹配部分依赖变量相关语法(例如case let), 这里值和模式匹配的真正逻辑并没有到编译那一步,甚至也不是语言语法,类似很多貌似“底层”的特性其实是在标准库中通过常规的Swift 代码来实现。

具体,Swift使用重载~=运算符号来实现模式匹配——这也就就给了我们自定义模式匹配的方法。

类似上面的判断错误,使用~=运算符号重载

func ~=<E: Error & Equatable>(rhs: E, lhs: Error) -> Bool {
    return (lhs as? E) == rhs
}

这样,上述的switch case写起来更简洁。

func handle(_ error: Error) {
    switch error {
    case URLError.notConnectedToInternet,
         URLError.networkConnectionLost,
         URLError.cannotLoadFromNetwork:
        showOfflineView()
    case HTTPError.unauthorized:
        logOut()
    case is HTTPError:
        showNetworkErrorView()
    default:
        showGenericErrorView(for: error)
    }
}

swift 模式匹配很强大,无疑会减少不少代码,不过里面高级的玩法确实看起来容易费解,这点可能就是swift 这门语言让人又爱又恨的地方。

从我角度来说,我喜欢这种复杂度,可玩性更高~^-^~。

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=s4vceg07lp5g