[译] 用 Swift 枚举完成 3D touch 快捷操作

Posted by Deadline on September 1, 2016

完美实现 3D Touch

我不确定是否一开始 Swift 的创造者们能够估计到他们创造的这一门极其优美的语言,将带给开发者们如此激昂的热情。 我只想说,Swift 社区已经成长且语言已经稳定(ISH)到一个地步,现在甚至有个专有名词赞美 Swift 编程的美好未来。

Swifty.

“That code isn’t Swifty”. “This should be more Swifty”. “This is a Swifty pattern”. “We can make this Swifty”.(反正就是漂亮,美得让人窒息之类的话)

这些赞扬的话还会越来越多。虽然我不太提倡说这些赞赏的话语,但是我真的找不到其它可以替代的话来夸赞,用 Swift 为 3D touch 编写快捷操作的那种“美感”。

这周,让我们来看看在 UIApplicationShortcutItem 实现细节中,Swift 是如何让我们成为 “一等公民” 的。

实现方案

当一个用户在主屏开始一个快捷操作时,会发生下面两件事中的一个。应用程序可以调用指定的函数来处理该快捷方式,或快速休眠再启动 — — 这意味着最终还是通过熟悉的 didFinishLaunchingWithOptions 来执行。

无论哪种方式,开发人员通常根据 UIApplicationShortcutItem 类型属性来决定用哪种操作。

if shortcutItem.type == "bundleid.shortcutType"
{
    //Action triggered
}

上面代码是正确的,项目中只是用一次的话还是可以的。

可惜的是,即便在 Swiftosphere 中,switch 条件用字符串实例有额外好处的情况下,随着增加越来越多的快捷操作,这种方法还是很快令人觉得十分繁琐。同时它也被大量证明,对于这种情况使用字符串字面值可能是白费功夫:

if shortcutItem.type == "bundleid.shortcutType"
{
    //Action triggered
}
else if shortcutItem.type == "bundleid.shortcutTypeXYZ"
{
    //Another action
}
//and on and on

处理这些快捷操作就像你代码库的一小部分,尽管如此—— Swift 能处理的更好而且更安全些。所以,让我们看看 Swift 如何发挥它的“魔法”,给我们提供一个更好的选择。

Enum .Fun

讲真, Swift 的枚举很“疯狂”。当 Swift 在 14 年发布的时候,我从来没有想过在枚举中可以使用属性,进行初始化和调用函数,但现在我们已经在这样子做了。

不管怎么说,我们可以在工作中用上它们。当你考虑支持 UIApplicationShortcutItem 的实现细节时,几个关键点应该注意:

  • 必须通过 type 属性给快捷方式指定一个名称
  • 根据苹果官方指南,必须以 bundle id 作为这些操作的前缀
  • 可能会有多个快捷方式
  • 可能会在应用程序多个位置采取基于类型的特定操作

我们的游戏计划很简单。我们不采用硬编码字符串字面量,而是初始化一个枚举实例来表示这就是被调用的快捷方式。

具体实现

我们虚构两个快捷方式,每个都额外附加一个之后,现在就是由一个枚举表示。

enum IncomingShortcutItem : String
{
    case SomeStaticAction
    case SomeDynamicAction
}

如果是用 Objective-C,我们可能到这就结束了。我认为,使用枚举远远优于之前使用字符串字面量的观点,已经被大家所接受。然而,对于为应用每个操作类型属性指定 bundle id 为前缀(例如,com.dreaminginbinary.myApp.MyApp)来说,使用一些字符串插值仍是最佳解决办法。

但是,因为 Swift 枚举超级厉害,我们可以用它以一种非常简洁的方法来实现:

enum IncomingShortcutItem : String
{
    case SomeStaticAction
    case SomeDynamicAction
    private static let prefix: String = {
        return NSBundle.mainBundle().bundleIdentifier! + "."
    }()
}

看!厉害吧!我们能安全的从计算属性中获取应用的包路径。回忆起上个星期的一篇文章,在介绍闭包的最后提到了插入值,我们希望将_前缀_分配给闭包的返回语句,并不是闭包本身。

最佳模式

最终方案,将用上两个我们最喜爱的 Swift 功能。那就是为枚举创建一个可能会失败的初始化函数的时候,使用 guard 语句清除空值以确保安全。

enum IncomingShortcutItem : String
{
    case SomeStaticAction
    case SomeDynamicAction
    private static let prefix: String = {
        return NSBundle.mainBundle().bundleIdentifier! + "."
    }()

    init?(shortCutType: String)
    {
        guard let bundleStringRange = shortCutType.rangeOfString(IncomingShortcutItem.prefix) else
        {
            return nil
        }
        var enumValueString = shortCutType
        enumValueString.removeRange(bundleStringRange)
        self.init(rawValue: enumValueString)
    }
}

这个允许失败的初始化是很重要的。如果没有匹配到快捷操作对应的字符串,应该跳出。它还能告诉我,如果我是维护者,当该使用它的时候,它可能更适合使用 guard 语句。

我特别喜欢这部分,这也是我们如何能够利用枚举 rawValue 的优势,且很容易把它拼接到包路径上。这一切都在正确的地方,一个初始化函数的内部。

别忘了,一旦其初始化,我们还可以当枚举来用的。这意味着我们会有一个可读很高的 switch 语句,后面有些反对的理由。

下面可能是最终产品的样子,所有的东西都集成进来了,与线上应用相比略有删减:

static func handleShortcutItem(shortcutItem:UIApplicationShortcutItem) -> Bool
{
    //Initialize our enum instance to check for a shortcut
    guard let shortCutAction = IncomingShortcutItem(shortCutType: shortcutItem.type) else
    {
        return false
    }
    //Now we've got a valid shortcut, and can use a switch
    switch shortCutAction
    {
        case .ShowFavorites:
            return ShortcutItemHelper.showFavorites()
        case .ShowDeveloper:
            return ShortcutItemHelper.handleAction(with: developer)
    }
}

至此,通过使用这种模式,我们的快捷操作变的可分类和内容安全,这也是我为什么这么喜欢它的原因。在方法的末尾提供一个最终的 “return false” 语句其实没什么必要(甚至在 switch 语句中是默认启动),因为我们已经十分了解,最后给代码精简一下。

和之前的代码比较一下:

static func handleShortcutItem(shortcutItem:UIApplicationShortcutItem) -> Bool
{
    //Initialize our enum instance to check for a shortcut
    let shortcutAction = NSBundle.mainBundle().bundleIdentifier! + "." + shortcutItem.type

    if shortCutAction == "com.aCoolCompany.aCoolApp.shortCutOne"
    {
        return ShortcutItemHelper.showFavorites()
    }
    else if shortCutAction == "com.aCoolCompany.aCoolApp.shortCutTwo"
    {
         return ShortcutItemHelper.handleAction(with: developer)
    }
    return false
}

真的,这看起来比用 switch 简单点。但我之前见过很多类似的代码(当然是我自己写的啦),虽然能很好的运行,但我认为可以利用 Swift 特性的优势,写出更好的代码。

最后的感想

当我刚开始阅读 Swift 枚举的返回时,发现它们有点“重”。有类的 inits(),为什么我还要枚举符合协议,这看起来有点多余。多年以后,我想这种模式已经充分展示了为什么就是这样的原因。

当我看到苹果实现了这种模式,确实很开心。我觉得这是个非常好的方式来解决一个小问题,同时对于快捷操作的实现细节来说也是个“团队友好”的方法。我认为他们也会同意我的观点,毕竟这种方式也在他们两个 3D touch 示例项目中。

下次再见👋


There are no comments on this post.