ios

在Swift里的自定义模式匹配(译)

2018-08-21  本文已影响4人  lkkwxy

原文地址
模式匹配在swift里是随处可见的,虽然switch case是匹配模式最常见的用法,但是Swift有多种类型的模式,这些模式可以混合甚至在switches外部使用,从而写出真正酷而短的代码。我特别感兴趣的事模式匹配可以用作于各种各样的事情。

乍一看下面的代码,很容易把模式匹配看做是简单的等值检查

switch 80 {
case 100:
    //
case 80:
    //匹配, 因为 80 == 80
default:
    break
}

鉴于下面的代码这种情况,你可能会认为像 case "eighty" 这样的将不会被编译,毕竟"eighty"和80不是相同的类型,如果你现在尝试一下的确会编译不通过

if case "eighty" = 80 {
    //error: expression pattern of type 'String' cannot match values of type 'Int'
}

但事实并非如此,在项目开发中,你可能已经注意到某些类型之间存在着特殊的交互,例如Ranges它们及其相关类型

switch 80 {
case 0...20:
    break
case 21...50:
    break
case 51...100:
    //匹配, 因为 80 在 51...100 之间
default:
    break
}

究其原因是~=这个匹配模式运算符,这个操作符看起来在常规项目里没什么用(你之前可能已经看到过这个确切的数字范围内的例子),但是他在swift内部使用很多,而且他是用于确认case语句
很多情况下,这个运算符是一个简单的等值检查的包装器(例如Int),但是Range有一个特殊的实现,当针对其自己的关联类型使用时,允许他在模式匹配时具有自定义的行为

extension RangeExpression {
    @inlinable
    public static func ~= (pattern: Self, value: Bound) -> Bool {
      return pattern.contains(value)
    }
}

并且由于~=的作用域是全局的,为了编写自己的模式匹配的逻辑,你可以重载它。

例如,要让"eighty"匹配80,你需要做的是,重载一个~=,使它将String模式和Int值匹配

func ~= (pattern: String, value: Int) -> Bool {
    if pattern == "eighty" {
        return value == 80
    } else if pattern == "not eighty" {
        return value != 80
    } else {
        return false
    }
}

switch 80 {
case "eighty":
    //编译通过并且匹配
case "not eighty":
    //
default:
   break
}

现在假设我的APP收到了字符串形式的深层链接,我需要知道这个深层链接属于哪一个控制器

enum AppTab: String {
    case home
    case orderHistory
    case profile
}

let deepLink = DeepLink(path: "home", parameters: [:])

这里有很多种方法来做这到这个功能,包括向DeepLink添加属性,如correspondingTab或者对DeepLink进行子类化,也可以使用自定义模式匹配,只需要一行代码而且无需修改DeepLink。

func ~= (pattern: AppTab, value: DeepLink) -> Bool {
    return value.path.hasPrefix(pattern.rawValue)
}

switch deepLink {
case .home:
    homeViewController.handle(deepLink: deepLink)
case .orderHistory:
    historyViewController.handle(deepLink: deepLink)
case .profile:
    profileViewController.handle(deepLink: deepLink)
default:
    break
}

这允许你可以绕过将更广泛的类型映射到更具体的类型,例如如何将Int映射到工作日枚举。在这种情况下,您的后端将返回一周作为一个Int或一个String,并使用自定义模式匹配,您可以使用这种更广泛的类型,同时仍然将其视为映射到更特定的枚举类型。当您想尝试使用而不想引用某些方法或类型时,这可能是有用的:

enum WeekDay: Int {
    case sunday
    case monday
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturday
}

func ~= (pattern: WeekDay, value: Int) -> Bool {
    return pattern.rawValue == value
}

// Server returns:
// { nextHoliday: { weekDay: 5 } }

if case .friday? = nextHoliday?.weekDay {
    print("Woohoo!")
}

创建自定义模式是编写更干净代码的一种简单方法,不需要太多的努力,因为您可以通过cases直接跳到这一点而无需为类型添加其他属性 - 同时确保您的代码不会变得难以理解。

我很想知道你是否做过类似重载过~=这样的事情,如果你有任何建议,意见和反馈,请随时与我联系,@rockthebruno

参考和推荐阅读

Apple Docs:模式

上一篇 下一篇

猜你喜欢

热点阅读