天天看點

Swift 與Cocoa的互動

Swift 與 Objective-C 之間存在互通性,你可以在同一個檔案中通路并使用另一種語言的代碼。當你開始在開發app中使用Swift語言的時候,了解如何平衡這種互通性在重新定義,改善,或者是增強代碼的時候是非常有益處的。

另外一個非常重要的方面是互通性能夠使你在寫Swift的時候使用Objective-C 的API,在導入Objective-C的架構之後,你可以使用Swift的文法執行個體化一個類并與之互動。

初始化

  要使用Swift初始化一個Objecive-C的類,隻需要使用Swift的文法調用這個類的其中一個構造函數。當一個Objective-C的init方法過渡到Swift時,它不需要本地的Swift初始化語句。init這個字首是一個關鍵字,它能決定一個方法是否為初始化方法。例如一個初始化方法以”initWith”開頭,”With”将會被切掉,含有”init”或者”initWith”的每一個selector将會變成小寫的,其後的每一個selector依次變成參數名,并在圓括号内調用。

例如,當使用Objective-C時你會這樣做:

[pre]UITableView *myTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];[/pre]而使用Swift時:

[pre]let myTableView: UITableView = UITableView(frame: CGRectZero, style: .Grouped)[/pre]你不再需要調用alloc,Swift能替你實作。注意當使用Swift風格的初始化函數的時候,”init”并沒有出現。

你可以在初始化時顯式的聲明對象的類型,也可以省略它,Swift能夠正确判斷對象的類型。

[pre]let myTextField = UITextField(frame: CGRect(0.0, 0.0, 200.0, 40.0))[/pre]  上述的UITableView與UITextField與你在Objective-C時的功能完全一緻,你可以用一樣的方式使用他們,包括通路屬性或者調用各自的類中的方法。

  為了連貫與統一,Objective-C中的工廠方法也在Swift中映射為簡單友善的初始化方法,這種映射能夠讓他們使用同樣簡單,清晰的初始化方法,例如,在Objective-C中你可能這樣調用一個類方法:

[pre]UIColor *color = [UIColor colorWithRed:0.5 green:0.0 blue:0.5 alpha:1.0];[/pre]在Swift中,你可以這樣做:

[pre]let color = UIColor(red: 0.5, green: 0.0, blue: 0.5, alpha: 1.0)[/pre] 通路屬性

兩種語言都使用點操作符來通路與設定屬性

[pre]myTextField.textColor = UIColor.darkGrayColor()myTextField.text = "Hello world"if myTextField.editing {    myTextField.editing = false}[/pre]  當通路或設定屬性之時,直接使用屬性名稱,不需要增加圓括号。你發現darkGrayColor後面跟了一對圓括号,這是因為darkGrayColor是UIColor的類方法,而不是一個屬性。

  在Objective-C時代,一個有傳回值的無參方法可以被作為一個隐式的通路函數,并且可以與通路器使用同樣的方法調用。但在Swift中不再能夠這樣做了,在Swift時代,隻有使用@property關鍵字聲明的屬性才會被作為屬性引入。

方法

  當在Swift調用Objective-C方法時,使用點操作符。

  Objective-C中的方法轉換到Swift時,Objective-C的選擇器的第一部分将會成為方法名并出現在圓括号的前面,而第一個參數将直接在括号中出現,并且沒有名字,而剩下的參數名與參數則一一對應的填入圓括号中。

例如在調用Objective-C方法時:

[pre][myTableView insertSubview:mySubview atIndex:2];[/pre]而在Swift中

[pre]myTableView.insertSubview(mySubview, atIndex: 2)[/pre]如果你調用一個無參函數,你仍需要在名稱後面加上一對圓括号

[pre]myTableView.layoutIfNeeded()[/pre] id 相容性

  Swift包含一個叫做 AnyObject 的協定類型,可以表示所有的對象。就好像Objective-C中的id.AnyObject 協定可以讓你寫出類型安全的代碼的同時維持無類型對象的通用性。因為AnyObject協定保證了這種安全,Swift将id對象導入為AnyObject.

  例如,與id一樣,你可以為AnyObject類型的對象配置設定任何其他類型的對象,你也同樣可以為它重新配置設定其他類型的對象。

[pre]var myObject: AnyObject = UITableViewCell()myObject = NSDate()[/pre]  你也可以同樣在調用Objective-C方法或者通路屬性時不将它轉換為具體類的類型。包括Objcive-C中标記為@objc的方法。

[pre]let futureDate = myObject.dateByAddingTimeInterval(10)let timeSinceNow = myObject.timeIntervalSinceNow[/pre]  然而,因為AnyObject對象的類型隻有在運作時才能知道,是以我們不經意間,就寫出了不安全的代碼。另外,與Objective-C不一樣的是,如果你調用方法或者通路的屬性AnyObject 對象沒有聲明,将會報運作時錯誤。例如,下面的代碼将會在運作時抛出unrecognized selector error 。

[pre]myObject.characterAtIndex(5)// crash, myObject does't respond to that method[/pre]  盡管如此,你也可以借助Swift的optionals特性來排除這個Objective-C中常見的錯誤,當你用AnyObject對象調用一個Objective-C的方法,這次調用将會變成一次隐式展開optional(implicitly unwrapped optional)的行為。你可以通過optional特性來決定AnyObject類型的對象是否調用該方法,同樣的,你可以把這種特性應用在屬性上。

  例如,在下面的代碼中,第一行與第二行并不會執行,因為NSDate 對象并沒有聲明length屬性與characterAtIndex: 方法。myLength将被推測為一個可選的Int類型,并且被設定成為nil。你可以使用if-let聲明來有條件的展開這個方法的傳回值,進而判斷對象是否能執行這個方法。就像第三行做的一樣。

[pre]let myLength = myObject.length?let myChar = myObject.characterAtIndex?(5)if let fifthCharacter = myObject.characterAtIndex(5) {    println("Found \(fifthCharacter) at index 5")}[/pre]而對于Swift中的強制類型轉換,從AnyObject類型的對象轉換成更明确的類型并不會保證完全成功,是以它會傳回一個可選的值。而你需通過檢查該值的類型來确認轉換是否成功。

[pre]let userDefaults = NSUserDefaults.standardUserDefaults()let lastRefreshDate: AnyObject? = userDefaults.objectForKey("LastRefreshDate")if let date = lastRefreshDate as? NSDate {    println("\(date.timeIntervalSinceReferenceDate)")}[/pre]當然,如果你能夠确定這個對象的類型(并且它不是nil),你可以加上一個as操作符強制調用。

[pre]let myDate = lastRefreshDate as NSDatelet timeInterval = myDate.timeIntervalSinceReferenceDate[/pre] 使用nil

  在Objective-C時代,對象的引用可以使值為NULL的原始指針(同樣也是Objective-C中的nil)。而在Swift中,所有的值–包括結構體與對象的引用–都被保證為非空。作為替代,你将這個可以為空的值包裝為optional type。當你需要訓示該值為空,你需要使用nil,你可以閱讀更多 關于Optionals 的内容

  因為Objective-C不會保證一個對象的值是否非空,Swift在引入Objective-C的API的時候,確定了所有函數的傳回類型與參數類型都是optional,在使用Objective-C API之前,你應該檢查并保證該值非空。

  在某些情況下,你可能絕對确認某些Objective-C方法或者屬性永遠不應該傳回一個nil的對象引用。為了讓對象在這種情況下更加易用,Swift使用 implicitly unwrapped optionals 方法引入對象, implicitly unwrapped optionals 包含optional 類型的所有安全特性。此外,你可以直接通路對象的值而無需檢查nil。當你通路這種類型的變量時, implicitly unwrapped optional 首先檢查這個對象的值是否不存在,如果不存在,将會抛出運作時錯誤。

擴充 Extensions

  一個Swift的擴充與Objective-C的類别很相似,擴充可以為原來的類增加方法,結構體,枚舉,包括在Objective-C中定義過的。你可以為系統的架構或者你自己的類型增加擴充,隻需要簡單的引入合适的元件。

  比如,你可以通過擴充UIBezierPath類來為它增加一個等邊三角形,這個方法隻需提供三角形的邊長與起點。

[pre]extension UIBezierPath {    convenience init(triangleSideLength: Float, origin: CGPoint) {            self.init()        let squareRoot = Float(sqrt(3))        let altitude = (squareRoot * triangleSideLength) / 2        moveToPoint(origin)        addLineToPoint(CGPoint(triangleSideLength, origin.x))        addLineToPoint(CGPoint(triangleSideLength / 2, altitude))        closePath()           }}[/pre]  你也可以使用擴充來增加屬性(包括類的屬性與靜态屬性)。然而,這些屬性必須是通過計算才能擷取的,擴充不會為類,結構體,枚舉存儲屬性。下面這個例子為CGRect類增加了一個叫area的屬性

[pre]extension CGRect {    var area: CGFloat {        return width * height    }}    let rect = CGRect(x: 0.0, y: 0.0, width: 10.0, height: 50.0)    let area = rect.area    // area: CGFloat = 500.0[/pre]你可以使用擴充來為類添加協定而無需增加它的子類。你不能使用擴充來覆寫Objective-C類型中存在的方法與屬性。

閉包Closures

  Objective-C 中的blocks會被自動引入為Swift 中的閉包。例如,下面是一個Objective-C 中的block 變量

[pre]void (^completionBlock)(NSData *, NSError *) = ^(NSData *data, NSError *error) {}[/pre]而它在Swift中的形式為

[pre]let completionBlock: (NSData, NSError) -> Void = {data, error in }[/pre]  Swift的閉包與Objective-C中的blocks能夠和睦相處,是以你可以把一個Swift閉包傳遞給一個把block作為參數的Objective-C函數。Swift閉包與函數具有互通的類型,是以你甚至可以傳遞Swift函數的名字。

  閉包與blocks語義上想通但是在一個地方不同:變量是可以直接改變的,而不是像block那樣會拷貝變量。換句話說,Swift中變量的預設行為與Objective-C中__block變量一緻。

比較對象Object Comparison

  當比較兩個Swift中的對象時,可以使用兩種方式。第一種,使用(==),判斷兩個對象内容是否相同。第二種,使用(===),判斷常量或者變量是否為同一個對象的執行個體。

  Swift與Objective-C一般使用 == 與 === 操作符來做比較。Swift的==操作符為源自NSObject的對象提供了預設的實作。在實作 == 操作符時,Swift調用 NSObject定義的 isEqual: 方法。NSObject類僅僅做了身份的比較,是以你需要在你自己的類中重新實作isEqual:方法。因為你可以直接傳遞Swift對象給Objective-C的API,你也應該為這些對象實作自定義的isEqual:方法,如果你希望比較兩個對象的内容是否相同而不是僅僅比較他們是不是由相同的對象派生。

  作為實作比較函數的一部分,確定根據 Object comparison 實作對象的hash屬性。更進一步的說,如果你希望你的類能夠作為字典中的鍵,也需要遵從Hashable協定以及實作hashValues屬性。

Swift Type Compatibility

  當你定義了一個繼承自NSObject 或者其他Objective-C 類的Swift類,這些類都能與Objective-C無縫連接配接。所有的步驟都有Swift編譯器自動完成,如果你從未在Objective-C代碼中導入Swift 類,你也不需要擔心類型适配問題。另外一種情況,如果你的Swift類并不來源自Objectve-C類而且你希望能在Objecive-C的代碼中使用它,你可以使用下面描述的@objc屬性。

  @objc 可以讓你的Swift API在Objective-C 與 runtime 中使用。換句話說,你可以通過在任何Swift方法、類、屬性前添加@objc,來使得他們可以在Objective-C 代碼中使用。如果你的類繼承自Objective-C,編譯器會自動幫助你完成這一步。編譯器還會在所有的變量、方法、屬性前加@objc,如果這個類自己前面加上了@objc關鍵字。當你使用@IBOutlet,@IBAction,或者是@NSManaged ,@objc也會自動加在前面。這個關鍵字也可以用在Objetive-C中的 target-action 設計模式中,例如,NSTimer或者UIButton。

  當你在Objective-C中使用Swift API,編譯器基本對語句做直接的翻譯。例如,Swift API func playSong(name: String) 會被解釋為 - (void)playSong:(NSString *)name 。然而,有一個例外:當在Objective-C中使用Swift的初始化函數,編譯器會在方法前添加”initWith”并且将原初始化函數的第一個參數首字母大寫。例如,這個Swift初始化函數

[pre]init (songName: String, artist: String [/pre]将被翻譯為

[pre] - (instancetype)initWithSongName:(NSString *)songName artist:(NSString *)artist[/pre]  Swift 同時也提供了一個@objc關鍵字的變體,通過它你可以自定義在Objectiv-C中轉換的函數名。例如,如果你的Swift 類的名字包含Objecytive-C中不支援的字元,你就可以為Objective-C提供一個可供替代的名字。如果你給Swift函數提供一個Objcetiv-C 名字,要記得為帶參數的函數添加(:)

[pre]@objc(Squirrel)class Белка {    @objc(initWithName:)    init (имя: String) { }    @objc(hideNuts:inTree:)           func прячьОрехи(Int, вДереве: Дерево) { }}[/pre]  當你在Swift類中使用@objc(<#name#>)關鍵字,這個類可以不需要命名空間即可在Objective-C中使用。這個關鍵字在你遷徙Objecive-C代碼到Swift時同樣也非常有用。由于歸檔過的對象存貯了類的名字,你應該使用 @objc(<#name#>) 來聲明與舊的歸檔過的類相同的名字,這樣舊的類才能被新的Swift類解檔。

Objective-C Selectors

  一個Objective-C 選擇器類型指向一個Objective-C的方法名。在Swift時代, Objective-C 的選擇器被Selector結構體替代。你可以通過字元串建立一個選擇器,比如 

[pre]let mySelector: Selector = "tappedButton:"[/pre]由于字元串能夠自動轉換為選擇器,是以你可以把字元串直接傳遞給接受選擇器的方法。

[pre]import UIKitclass MyViewController: UIViewController {    let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))    init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {        super.init(nibName: nibName, bundle: nibBundle)        myButton.targetForAction("tappedButton:", withSender: self)    }    func tappedButton(sender: UIButton!) {        println("tapped button")    }}[/pre]如果你的Swift類繼承自Objective-C,你的所有方法都可以用作Objective-C的選擇器。另外,如果你的Swift類并未繼承自Objective-C,你需要在方法前面加上@objc 關鍵字。 更多參考

從上面看出在項目中使用Swift是不難的,蘋果已經為開發者做好了準備,而且對于熟悉Cocoa架構的人也是非常容易上手。是以我認為自己應該積極接受這個變化,并且視為一種機遇。

http://www.nsguy.com/blog/2014/06/03/swift-yu-cocoade-jiao-hu/