用Swift列舉完美實現3Dtouch快捷操作

玄學醬發表於2017-10-19
本文講的是用 Swift 列舉完美實現 3D touch 快捷操作,

完美實現 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 示例專案中。

下次再見





原文釋出時間為:2016年09月10日

本文來自雲棲社群合作伙伴掘金,瞭解相關資訊可以關注掘金網站。


相關文章