用Swift列舉完美實現3Dtouch快捷操作
完美實現 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 示例專案中。
下次再見
相關文章
- Swift,列舉Swift
- Swift-列舉Swift
- 用列舉簡化登入操作
- Swift列舉的全用法Swift
- 列舉GCRoots的實現GC
- bootstrap完美實現5列布局boot
- Swift4 的變化列舉Swift
- 【轉】命令列操作快捷鍵命令列
- OC中列舉寫法 以及 字串型別列舉實現探索字串型別
- c++11 實現列舉值到列舉名的轉換C++
- 遞迴實現指數型列舉遞迴
- js模擬實現列舉效果JS
- C#中實現列舉數C#
- Swift遞迴列舉與紅黑樹Swift遞迴
- Java 利用列舉實現單例模式Java單例模式
- 透過列舉enum實現單例單例
- 程式設計與演算法--(列舉-完美立方)程式設計演算法
- OC 和 Swift 中是如何用列舉的?Swift
- 7.1 實現程式記憶體塊列舉記憶體
- 小技巧分享:在 Go 如何實現列舉?Go
- Swift之列舉Swift
- Swift列舉關聯值的記憶體探究Swift記憶體
- 窺探Swift之別樣的列舉型別Swift型別
- 深入淺出 Java 中列舉的實現原理Java
- 基於註解的 PHP 列舉類實現PHP
- Java一個列舉類的2種實現。Java
- Java與JavaScript 完美實現字串拆分(利用陣列儲存)與合併的互逆操作JavaScript字串陣列
- Swift列舉,結構體,類,擴充套件,協議Swift結構體套件協議
- 一網打盡列舉操作 .net core
- 兩個棧實現佇列操作佇列
- (幾乎)完美實現 el-table 列寬自適應
- 用 Rust 實現佇列Rust佇列
- 用佇列實現棧佇列
- 用棧實現佇列佇列
- 125 列舉實現PHP擷取中文不亂碼的實現方法PHP
- 用 Swift 來寫命令列程式Swift命令列
- Java 列舉、JPA 和 PostgreSQL 列舉JavaSQL
- 快捷操作