Swift編程的15個技巧

jopen 8年前發布 | 26K 次閱讀 Swift

相對于Objective-C,Swift是一種編譯代碼時速度更快、安全性與可靠性更高、同時具有可預測性的語言。下面我們列出了在實踐中使用這種新語言時,所獲取一些Swift使用技巧。這些技巧有助于讓開發者編寫出更干凈的代碼,并能幫助更熟悉Objective-C的程序員適應Swift編程,同時適用于在Swift上具有各種背景經歷的人,請繼續往下看。

章節的順序是按照使用者對Swift的熟悉程度來排列的。第一部分是針對不太了解Swift的人,第二部分是針對初級入門者,而最后一部分是對于已在使用Swift的人。

你應當了解,但有可能不知道的Swift技巧

提高常數的可讀性

在Swift中使用struct的簡潔辦法,就是在應用中制作一個適用所有常數的文件。由于Swift允許我們嵌用下面的結構,這種辦法非常有用:

import Foundationstruct Constants {
    struct FoursquareApi {
        static let BaseUrl = "https://api.foursquare.com/v2/"
    }    
    struct 推terApi {
        static let BaseUrl = "https://api.推ter.com/1.1/"
    }
    struct Configuration {
        static let UseWorkaround = true
    }    
}

嵌套讓我們可以為常數生成一個命名空間(namespace)。例如:我們可以使用Constants.FoursquareApi.BaseUrl來訪問Foursquare的BaseUrl常數,這樣會使得數據可讀性更高,并為相關的常數提供一系列封裝。

為了提高性能,要避免NSObject與@objc

Swift允許我們將分類進行擴展,從NSObject到獲取對象的Objective-Cruntime系統功能。還允許我們用@objc來注釋Swift方法,以便在Objective-C runtime中使用。

支持Objective-C runtime,代表著系統不再通過通過靜態或vtable分配,而是動態分配來調用方法。結果就是:在調用支持Objective-C運行的方法時,性能損失會高達四倍。在實際應用中,這種情況對性能的影響也許微不足道,不過這樣一來,我們就知道通過Swift執行方法調用要比使用Objective-C快四倍。

在Swift中使用方法調配(Method Swizzling)

方法調配是替換一個已存在的方法實現。如果對此不熟悉,可以 閱讀這篇文章 。Swift優化后,不再像Objective-C中那樣,在runtime尋找方法的位置,而是直接調用內存地址。因此默認情況下,在Swift類中調配無法起效,除非:

  • 用動態關鍵字禁用這種優化。這是最佳選擇,如果數據庫完全以Swift構建的話,這種選擇也是最合理的方式。
  • 擴展NSObject。如果單純為了方法調配的話,不要用這種方式(而要采用動態的)。需要了解:在將NSObject作為基礎類的已存在類中,方法調配是有效的,不過最好使用動態選擇的方法。
  • 在要調配的方法中使用@objc注釋。如果我們想要調配的方法同時也需要使用Objective-C的代碼,那么這種方法是最合適的。
  • </ul>

    更新:根據要求,我們增加了一個完全使用Swift的調用樣例。在這個樣例中仍需要Objective-C runtime,不過類并非繼承自NSObject,方法也未標記成@objc。

    </div>

    import UIKitclass AwesomeClass {
        dynamic func originalFunction() -> String {
            return "originalFunction"
        }  
        dynamic func swizzledFunction() -> String {
            return "swizzledFunction"
        }
    }let awesomeObject = AwesomeClass()print(awesomeObject.originalFunction()) // prints: "originalFunction"let aClass = AwesomeClass.selflet originalMethod = class_getInstanceMethod(aClass, "originalFunction")let swizzledMethod = class_getInstanceMethod(aClass, "swizzledFunction")
    method_exchangeImplementations(originalMethod, swizzledMethod)print(awesomeObject.originalFunction())  // prints: "swizzledFunction"

    入門者所需的Swift技巧

    清理異步代碼

    Swift在編寫補齊函數(completion function)上語法非常簡潔。在Objective-C中有completion block,不過出現的很晚, 語法也有些粗糙 ,如下:

    [self loginViaHttpWithRequest:request completionBlockWithSuccess:^(LoginOperation *operation, id responseObject) {
      [self showMainScreen];
    } failure:^(LoginOperation *operation, NSError *error) {
      [self showFailedLogin];
    }];

    在Swift中有一種更簡單的新型閉包語法。任何將閉包作為末尾參數的方法都可以使用Swift的新語法,讓回調更簡潔,如下:

    loginViaHttp(request) { response in
      if response.success {
        showMainScreen()
      } else {
        showFailedLogin()
      }
    }

    控制對代碼的訪問

    應該堅持用合適的訪問控制修飾符(access control modifier)來 封裝 代碼。如果封裝的好,無需記下思維過程,也無需詢問代碼編寫者,就能理解這段代碼是如何交互的。

    Swift常見的訪問控制機制有三種:私人訪問、內部訪問和公共訪問。不過Swift中并沒有常見于其它面向對象語言中的protected訪問控制修飾符。為什么會這樣呢?那是因為在子類中通過新的公共方法或屬性,就可以顯示protected方法或屬性,因此實際上保護是無效的。而且由于從任何地方都能重寫,因此protected并未給Swift編譯器開啟優化的機會。最后,由于protected阻止子類helper訪問子類能夠訪問的信息,會讓封裝變差。想要了解Swift團隊關于protected更多的想法,請點擊 這里 查看。

    實地實驗與驗證

    Playground是蘋果在2014年隨Swift一起推出的一款交互式編程工具,可以用來測試及驗證想法、學習Swift、與其他人分享概念。無需創建新項目,只需在運行Xcode的時候將playground選中就可以了。

    也可以在Xcode中創建新的playground:

    Swift編程的15個技巧

    一旦有了playground,在編程時便能實時看到結果:

    Swift編程的15個技巧

    通過Playground可以將想法原型化,并以代碼形式展示,同時還不會造成開啟新項目的額外開銷。

    Swift編程的15個技巧

    安全地使用可選值

    可選值(optional)屬性指的是這個屬性或有效值或無值(為空)。通過可選值的名稱+感嘆號,格式為optionalProperty!,便可隱式解開一個可選值。 一般這是需要避免的,因為感嘆號暗示著“ 危險 ”。

    不過有些情況下,隱式解開可選值是可以接受的。比如IBOutlets就是默認將可選值隱式解開的(在Interface Builder中點擊拖拽時),因為UIKit假定我們是將對象接口(outlet)與IB連接起來的。IBOutlets在初始化之后已經設置好了,因此接口是可選值的,同時根據 Swift規則 ,在初始化之后所有非可選值的屬性必須有值。另一個通過名稱獲得UIImage的案例是存在于我們的asset catalog之中的:

    let imageViewSavvyNewYearsParty = UIImageView(image: UIImage(named: "Savvy2016.png")!)

    將默認值設置為常量屬性,在不隱式打開可選值的情況下是無法做到的。也就是說,!仍舊代表“危險!”但在這種情況下,是告知我們需要當心錯誤,并在運行前驗證名稱是否相符。一般來講,假如我們必須使用空值,app就會有崩潰的風險。用!來隱式打開值會讓編譯器知道,我們已經知道在運行時可選值不會為空。在幾乎所有場景之中,這都是帶有賭博性質的,因此最好使用if let模式來確定可選值是有有效值還是為空:

    if let name = user.name {
        print(name)
    } else {
        print("404 Name Not Found")
    }

    拋棄數字對象(NSNumber)

    Objective-C使用C primitives來代表數字,用Foundation Objective-C API來提供數字對象類型,將primitives裝箱拆箱。需要在primitives與對象類型之間切換時,代碼會像 [array addObject:@(intPrimitive)]和[array[0] intValue]這樣。Swift就不會有這種不當的機制。相對的,我們實際上可以向Swift字典和數組中添加Int / Float / AnyObject值。

    下面是代替數字對象的一些Swift最常用的類型:

    </div>

    • Swift: Objective-C
    • Int: [NSNumber integerValue]
    • UInt: [NSNumber unsignedIntegerValue]
    • Float: [NSNumber floatValue]
    • Bool: [NSNumber boolValue]
    • Double: [NSNumber doubleValue]
    • </ul>

      在用Objective-C編寫的不同類型中,我們仍可以用數字對象來進行轉換,不過在Swift中,轉化值的 常用方式 是使用目標類型的構造函數。舉個例子,如果我們從API中獲得一個數字userID,將其在數字對象中打開并顯示為字符串,在Objective-C中需要輸入[userId stringValue]。而在Swift中數字對象不再使用(除非要向后兼容Objective-C),因為在Swift中,數字結構與在Objective-C中限制不同。

      注意:在使用Objective-C或依賴沒有Swift封裝的舊式代碼庫中,可能仍得使用數字對象。在這種情況下,數字對象API基本沒什么變化。

      </div>

      相反,在Swift中我們通過構造函數進行等效轉換。舉個例子,如果userID是一個Int,而我們想要字符串的話,只需通過String(userId)進行轉換。這比一直將數字對象裝箱拆箱容易多了,不過數字對象所提供的各種各樣的轉換,確實讓API簡單易用。

      通過默認參數減少樣板文件代碼

      在Swift中,函數自變量現在可以有默認值了。這些默認的參數減少了雜亂程度。如果某函數的被調用者選擇使用默認值,由于默認參數可以省略,這個函數調用就能更短一些了。例如:

      unc printAlertWithMessage(message: String, title: String = "title") {
         print("Alert: \(title) | \(message)")
      }printAlertWithMessage("message") // prints: Alert: title | messageprintAlertWithMessage("message", title: "non-default title") // prints: Alert: non-default title | messagex

      為更熟練的使用者提供的一些Swift技巧

      通過Guard來驗證方法

      Swift的guard語句讓代碼更簡潔、更安全。guard語句會檢查一到多個情況,找出不符合else部分的調用。而else部分需要return,break,continue或throw語句來終止方法的執行,也就是說終止程序控制的執行。

      我們使用guard語句來減少代碼混亂,并避免在if/else語句中的嵌入。由于在guard語句的else部分中,代碼必須轉移程序控制的范圍,如果出現無效的情況,簡單地采用if語句來調用return語句更為安全。在編譯時這些bug仍有可能出現。如果guard語句的情況通過的話,在我們的范圍中,解包后的可選值仍舊可用。

      class ProjectManager {

      func increaseProductivityOfDeveloper(<span style="box-sizing:border-box;">developer: Developer) {
      
          guard <span style="box-sizing:border-box;">let developerName = developer.name <span style="box-sizing:border-box;">else {
              <span style="box-sizing:border-box;">print(<span style="box-sizing:border-box;">"Papers, please!")
              <span style="box-sizing:border-box;">return
          }
          <span style="box-sizing:border-box;">let slackMessage = SlackMessage(<span style="box-sizing:border-box;">message: <span style="box-sizing:border-box;">"\(developerName) is a great iOS Developer!")
          slackMessage.send()
      }
      

      }</code></pre>

      用Defer管理程序控制流

      defer語句會推遲包含這個命令的代碼執行,直到當前范圍終止。也就是說,在defer語句中清理邏輯是可以替換的,而且只要離開相應的調用范圍,這段命令就肯定就會被調用。這樣可以減少冗余步驟,更重要的是增加安全性。

      func deferExample() {
          defer {
              print("Leaving scope, time to cleanup!")
          }
          print("Performing some operation...")
      }// Prints:// Performing some operation...// Leaving scope, time to cleanup!

      簡化單例模式(Singleton)

      在任何語言中對 單例模式 的使用都屬于 熱議話題 ,不過它仍是大多數開發人員非常熟悉的 模式 。在Objective-C中,實現單例模式包括多個步驟,以便確保不會多次創建單例模式類。在Swift中這種使用有了大幅簡化。下面我們會看到在Objective-C中實現單例模式的代碼行數,是在Swift中實現單例模式代碼的兩倍。除此之外,由于使用了dispatch token,不僅可讀性較差,也很難記住。

      Objective-C:

      @implementation MySingletonClass

      +(<span style="box-sizing:border-box;">id)sharedInstance {
          <span style="box-sizing:border-box;">static MySingletonClass *sharedInstance = <span style="box-sizing:border-box;">nil;
          <span style="box-sizing:border-box;">static <span style="box-sizing:border-box;">dispatch_once_t onceToken;
          <span style="box-sizing:border-box;">dispatch_once(&onceToken, ^{
              sharedInstance = [[<span style="box-sizing:border-box;">self alloc] init];
          });
          <span style="box-sizing:border-box;">return sharedInstance;
      }</code></pre> <p>Swift:</p>
      

      class MySingletonClass {
          static let sharedInstance = MySingletonClass()
          private init() {
          }
      }

      通過協議擴展減少重復的代碼

      在Objective-C中,我們通過分類來擴展已有的類型,不過這種做法對協議無效。Swift允許向協議中添加功能,使用Swift可以擴展單協議(甚至在標準數據庫中的那些!),并將其應用在實現協議的類中。協議擴展足夠將我們的整個編程范式從面向對象式改為面向協議式。這個概念的關鍵在于,我們默認通過協議來添加功能,而不是通過類,以便增加代碼的可復用性。想要了解更多面向協議編程的知識,請查看這個 視頻 ,摘自WWDC 2015。

      創建全局Helper函數

      全局變量和函數經常被合稱為“壞東西”,不過事實是兩者都能讓代碼更干凈,真正的壞東西是全局狀態。全局函數經常需要 全局狀態 來完成相關工作,因此很容易理解它們為什么會有這樣的壞名聲。下面是一些 Grand Central Dispatch 的helper函數樣例,不是建立在全局狀態之上,而且多少有些語法糖(指計算機語言中添加的某種語法,這種語法對語言的功能并沒有影響,但是更方便程序員使用)的性質。下面我們會采用dispatch_after函數,用Swift的方式來解包:

      import Foundation

      /** Executes the closure on the main queue after a set amount of seconds.

      - parameter <span style="box-sizing:border-box;">delay:   Delay <span style="box-sizing:border-box;">in seconds
      - parameter closure: Code <span style="box-sizing:border-box;">to execute <span style="box-sizing:border-box;">after <span style="box-sizing:border-box;">delay
      

      / func delayOnMainQueue(delay: Double, closure: ()->()) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay Double(NSEC_PER_SEC))), dispatch_get_main_queue(), closure) }

      /** Executes the closure on a background queue after a set amount of seconds.

      - parameter <span style="box-sizing:border-box;">delay:   Delay <span style="box-sizing:border-box;">in seconds
      - parameter closure: Code <span style="box-sizing:border-box;">to execute <span style="box-sizing:border-box;">after <span style="box-sizing:border-box;">delay
      

      / func delayOnBackgroundQueue(delay: Double, closure: ()->()) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay Double(NSEC_PER_SEC))), dispatch_get_global_queue(QOS_CLASS_UTILITY, ), closure) }</code></pre>

      下面是新解包的函數樣例:

      delayOnBackgroundQueue(5) {
          showView()
      }

      下面是未解包的函數樣例:

      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_global_queue(QOS_CLASS_UTILITY, )) {
          showView()
      }

      用Swift語法來解包C函數,讓我們的代碼更易于一眼理解。找到你最喜歡的函數,試一下吧!只要在正確方法命名上盡責,將來程序的維護者肯定感激我們。如果我們將上面的方法簽名修改為delay(delay: Double, closure: ()->()),這就成了不負責任的方法命名反例,因為dispatch_after需要 GCD 隊列,而從名稱中看不出來使用的哪個隊列。然而,如果我們使用的代碼庫在主線程所有方法的執行上有既定規范,除非在名稱或評論上另有指示,delay(delay: Double, closure: ()->())就可以是一個正確的方法名稱。無論我們如何命名helper函數,它們都是為了通過包裝樣本代碼節省時間,讓代碼更易讀。

      擴展集合性能

      Swift增加了一些方法,幫助我們對集合進行簡潔的查詢和修改。這些集合方法受到了函數式語言的啟發。我們使用集合將多個值保存到一個單獨的數據結構中,通常我們也會查詢和修改集合。這些函數是基于Swift的標準數據庫構建,協助簡化常見的任務。為了協助詮釋下面這些函數,我們使用了這些樣例:let ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]。

      • 對集合中的每個值執行閉包映射(map),之后返回填充有映射值的映射結果類型數組。下面我們將Int數組轉化為字符串數據:
      • </ul>

        let strings = ints.map { return String($0) }print("strings: \(strings)") // prints: strings: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]

        • 對數組中的每個值執行函數篩選(filter),返回Bool值。在結果數組中,只會返回true值,而不會返回false值。下面我們從ints數組中篩選奇數:
        • </ul>

          let evenInts = ints.filter { return ($0 % 2 == ) }print("evenInts: \(evenInts)") // prints: evenInts: [0, 2, 4, 6, 8]

          • reduce比map和filter更復雜,不過因為非常有用,花時間學習也是有價值的。第一個參數就是第一個reduce值(在下面的案例中為0)。第二個參數是訪問之前reduce值和數組現值的函數。在本例中,我們的函數是將之前的函數值簡單加到數組的現值中。
          • </ul>

            let reducedInts = ints.reduce(, combine: +)print("reducedInts: (reducedInts)") // prints: reducedInts: 45

            // defined another way: let reducedIntsAlt = ints.reduce() { (previousValue: Int, currentValue: Int) -> Int in return previousValue + currentValue }print("reducedIntsAlt: (reducedIntsAlt)") // prints: reducedIntsAlt: 45</code></pre>

            通過map,filter,reduce方面的技巧,就能減少篩選時和處理集合時的工作量,并增加可讀性,方便以后的人維護。

            結論

            這份列表來自于我們團隊的建議,收集了一些最常用的技巧,其中很多在整個代碼庫中都很常見。隨著Swift這門編程語言的發展,像這樣的技巧也在繼續增加。我們希望能繼續看到Swift的變化,并期待在應用中更多地使用這種語言。

            英文來源: 15 Tips to Become a Better Swift Developer  

            作者:NATHAN HILLYER,全棧開發者

            翻譯:孫薇

            </div> </div> </div>

            來自: http://www.lupaworld.com/article-257317-1.html

            </span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>

             本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
             轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
             本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!