從Objective-C到Swift:幾點想法和意見

edithfang發表於2014-10-15
注意:本文討論的開發環境為Xcode 6 beta 2版本。

單一檔案結構 VS 介面-實現

最值得一提的一大改動便是在Objective-C中“[介面].h/[實現].m”這種檔案結構被取締了。

其實我本人是很支援這種檔案結構的,因為通過介面檔案來獲取及共享類特性的方式相當安全而且簡單,不過現在不得不面對它不復存在的現實了。

在Swift中並不存在介面與實現分割成兩個檔案的現象,我們僅需要依靠實現來構建一個類就行了(並且在寫的時候甚至不可能新增關於可訪問性的修改)。

如果對於這一改動感到無法忍受的話應注意以下事項:

最為明顯的:靠直覺。

我們可以藉助漂亮的文件來提高類的可讀性。舉個例子,我們可以把所有想作為public的要素全部挪到檔案開頭去,也可以採用擴充套件來區分public和private。

另一個很實用的辦法就是在private的方法和變數命名前加一個下劃線'_'作為字首。

下面是混合了以上兩種方案的示例:
// Public 
extension DataReader { 
       
    var data { } 
    func readData(){ 
        var data = _webserviceInteraction() 
    } 
} 
   
// Private implementation 
class DataReader: NSObject { 
      
    let _wsURL = NSURL(string: "http://theurl.com") 
       
    func _webserviceInteraction()->String{ 
        // ... 
    } 
} 
雖然我們沒辦法修改類中各元素的可見性,不過我們可以試著讓某些訪問變得“困難一些”。

一個特殊的方法就是使用巢狀類來把private部分隱藏起來(至少是自動的隱藏),下面是例子:
import UIKit 
   
class DataReader: NSObject { 
       
    // Public *********************** 
    var data:String?{ 
        get{return private.internalData} 
    } 
       
    init(){ 
        private = DataReaderPrivate() 
    } 
   
    func publicFunction(){ 
        private.privateFunc() 
    } 
   
       
    // Private ********************** 
    var private:DataReaderPrivate 
       
    class DataReaderPrivate { 
        var internalData:String? 
           
        init(){ 
            internalData = "Private data!" 
        } 
           
        func privateFunc (){} 
    } 
} 
我們將private的實現放入一個private常量的例項中,然後用“正常”的類的實現來充當介面。不過private部分並非會真正的隱藏起來,只不過在訪問的時候需要加上一個private關鍵字了:
let reader = DataReader() 
reader.private.privateFunc() 
問題來了:僅是為了最後要將這些private的部分隱藏起來要把程式碼寫得這樣怪異值得嗎?

我的建議是在可見性的修改出來之前(蘋果正在忙這個事),我們還是採用詳細的文件或者多少用一點擴充套件來完成這個事。

常量和變數

在 寫Objective-C的時候我會很少的使用到const關鍵字,甚至於我知道有的資料時不會變的(好吧不要吐槽我)。然而在Swift中蘋果建議開發 者們多花點心思在使用常量(let)而不是變數(var)上。所以請注意要弄明白你的變數的具體要做什麼。你會使用常量的頻繁度將是你從未想象過的。

更加簡化的寫法

來看一下下面的兩行程式碼並比較有何不同:
let wsURL:NSURL = NSURL(string:"http://wsurl.com"); 
vs 
let wsURL = NSURL(string:"http://wsurl.com") 
在我最開始接觸Swift的前兩個星期我強迫自己不要在每一行程式碼最後都新增分號,現在我感到人生圓滿(不過現在寫Objective-C的時候我不會加分號了)。

型別推斷可以直接根據變數的定義為其指派型別,相比較Objective-C這類冗雜的語言來說,在這裡倒是可圈可點。

我們應該使用一致的命名方式,否則其他的開發者(包括你自己)就很難通過極其糟糕的命名來推測其型別:
    
let a = something() 
更加合理的命名是這樣的:
let a = anInt() 
還有一個改動就是關於括弧號,他們不再需要配對了:
if (a > b){} 
     vs    
if a > b {} 
不過請記住,我們在括號中間寫入的部分會被認為是一個表示式,在這裡不總是代表這樣寫是對的。在變數繫結時我們不能像下面這樣使用括號:
if (let x = data){} // Error!  
if let x = data {} // OK! 
使用型別判斷和刪除分號及括號並不完全必要,不過我們可以考慮一下用以上建議的方式來寫Swift程式碼,這樣的話會提高程式碼的可讀性並且減少一些輸入量。

可選值

有多少次我們困惑與函式的返回值該如何設定?我曾經使用過NSNotFound, -1, 0,自定義的返回值來表示返回為空。

現在有了可選值的出現很好的解決了返回值為空的問題,我們僅需要在資料型別的後面新增一個問號就可以了。

我們可以這樣寫:
class Person{ 
    let name:String 
    let car:Car? // Optional value 
       
    init(name:String){ 
        self.name = name 
    } 
} 
   
// ACCESSING THE OPTIONAL VALUE *********** 
   
var Mark = Person(name:"mark") 
   
// use optional binding  
if let car = Mark.car { 
    car.accelerate() 
} 
   
// unwrap the value 
Mark.car?.accelerate()
這是個用了可選值來描述“某人有一輛車”的例子,它表示car這一特徵可以是沒有的,因為這表示某人沒有車。

然後我們可以用optional binding或者unwrap來取得它的值。

如果對於一個屬性沒有設定為可選值,我們又沒有為其賦值的話,編譯器會立馬不爽快的。

一旦初始化了之後便沒有設定非可選屬性的機會了。

所以我們應該事先考慮一下類的屬性與其它部分的關係以及在類進行例項化的時候它們會發生什麼變化。

這些改進徹底的改變了構思一個類的方式。

可選值的拆包

你會發現可選值這個東西難以理喻,因為你不會理解為什麼編譯器會提示你在使用之前對其進行拆包。

Mark.car?

我建議你把可選值當做一個結構體(當做結構體的話會好理解一些),其中包括了一個你所設定的值。不過外面包裹了其他東西(wrap)。如果裡面的值有定義,你就可以進行拆包(unwrap)然後得到你所想得到的值。否則你就得到一個空值(nil)。

使用感嘆號"!"來進行強制拆包而不管其中的值是否有定義,這樣做是有風險的,因為如果裡面的值沒有定義的話應用會崩掉。

委託模式

經過多年的Objective-C和Cocoa程式碼編寫我想大部分人都對使用委託模式養成了一種嗜好。注意了!我們還是可以繼續保留這種嗜好的,下面是一個非常簡單的例子:
@objc protocol DataReaderDelegate{ 
    @optional func DataWillRead() 
    func DataDidRead() 
} 
   
class DataReader: NSObject { 
      
    var delegate:DataReaderDelegate? 
    var data:NSData? 
   
    func buildData(){ 
           
        delegate?.DataWillRead?() // Optional method check 
        data = _createData() 
        delegate?.DataDidRead()       // Required method check 
    } 
} 
這裡我們使用了一個簡單的@optional來替換了使用respondToSelector檢測委託方法是否存在。
delegate?.DataWillRead?()
請注意我們在協議之前必須加@obj字首,因為後面使用了@optional。同時編譯器也會在這裡報一個警告的訊息以防你沒有加上@obj。

要實現協議的話,我們需要構建一個類來實現它然後用曾經在OC上用過的方式來指派。
class ViewController: UIViewController, DataReaderDelegate { 
                               
    override func viewDidLoad() { 
        super.viewDidLoad() 
           
        let reader = DataReader() 
        reader.delegate = self 
    } 
   
    func DataWillRead() {...} 
       
    func DataDidRead() {...} 
} 
目標-動作模式

另一常用的設計模式:目標-動作模式。我們仍然同樣可以像在OC中使用它那樣在Swift中實現它。
class ViewController: UIViewController { 
       
    @IBOutlet var button:UIButton 
       
    override func viewDidLoad() { 
        super.viewDidLoad() 
           
        button.addTarget(self, action: "buttonPressed:", forControlEvents: UIControlEvents.TouchUpInside) 
    } 
   
    func buttonPressed(sender:UIButton){...} 
這裡唯一不同的地方就是如何定義一個selector選擇器。我們可以變形使用像下面這樣的字串來寫方法原型:
Selector("buttonPressed:") 
單件模式

簡直又愛又恨。單件模式依舊是設計模式中最為常用的模式之一。

我們可以用GCD和dispatch_once來實現它,當然還可以用let關鍵字來實現執行緒安全。
class DataReader: NSObject { 
       
    class var sharedReader:DataReader { 
           
        struct Static{ 
            static let _instance = DataReader() 
        } 
           
        return Static._instance 
    } 
... 
}
我們來快速瀏覽一下這段程式碼:

  • 1.sharedReader是一個靜態的複合屬性(我們也可以替換為方法)。
  • 2.靜態屬性不允許在類被實現的時候重構,所以由於內部型別是被允許的,我們可以再這裡加入一個結構體。
  • 3._instance是一個常量,它不會被重寫而且保證執行緒安全。


可以參考下面DataReader單例的用法:
DataReader.sharedReader
結構和列舉

Swift中的結構和列舉簡直神乎其神,你根本不會在其他的語言裡面找到像它們這樣的。

它們支援方法:
struct User{ 
    // Struct properties 
    let name:String 
    let ID:Int 
       
    // Method!!! 
    func sayHello(){ 
        println("I'm " + self.name + " my ID is: \(self.ID)") 
    } 
} 
   
let pamela = User(name: "Pamela", ID: 123456) 
pamela.sayHello() 
如你所見在這裡的結構體使用了初始化,而且這個是Swift自動建立的(我們可以新增一些自定的實現)。

列舉型別的語法比起我們用過的會稍難。

它的定義需要用到關鍵字case:
enum Fruit {  
  case orange 
  case apple 
} 
而且列舉並不侷限於int型:
enum Fruit:String {  
  case .orange = "Orange" 
  case .apple = "Apple" 
} 
而且還可以用的更復雜一些:
enum Fruit{ 
       
    // Available Fruits 
    case orange 
    case apple 
       
    // Nested type 
    struct Vitamin{ 
        var name:String 
    } 
       
    // Compound property 
    var mainVitamin:Vitamin { 
       
    switch self{ 
    case .orange: 
        return Vitamin(name: "C") 
           
    case .apple: 
        return Vitamin(name: "B") 
        } 
    } 
}  
   
let Apple = Fruit.apple 
var Vitamin = Apple.mainVitamin 
在上面我們為Fruit列舉類新增了一個內部型別Vitamin和一個複合的mainVitamin,並且這樣的結構還可以根據列舉的值來進行初始化裡面的元素……是不是已經感到眼花繚亂了?

可變與不可變類

在OC中我們總會用到可變以及不可變類,舉個例子?NSArray和NSDictionary。在Swift裡面我們不在像這樣來區分資料了,只需要用常量和變數的定義來替代。

資料變數是可以變的而陣列常量的值不可更改。所以請記下這個公式:
“let = immutable. var = mutable”.

塊和閉包

我非常喜歡塊的語法,因為它相當簡單而且好記。
<p>
順帶提一下,因為有多年Cocoa的變成習慣所以有時候我會偏愛於用塊來替代簡單的委託作業。這是很靈活快捷的方式,而且非常實用。

Swift中與塊相對的是閉包。閉包的作用極為強大而且蘋果在將其簡單化上做得很棒,很容易就可以實現。

官方文件裡的示例只能說讓我無言以對。

它是這樣定義的:
reversed = sort(names, { (s1: String, s2: String) -> Bool in 
    return s1 > s2 
})
然後是這樣重構的:
reversed = sort(names, >) 
所以,由於型別判斷的存在我們能以不同的方式來實現一個閉包、速寫引數($0, $1)和直接操作函式(>)。

在這篇文章裡我打算遍歷一下閉包的用法不過此前我想對如何獲取閉包中的值說幾句。

在OC裡面,我們定義一個變數像_block這樣,以方便我們想預備將它壓入塊。不過在閉包裡面這些都沒有必要。

我們可以使用和修改周圍的值。事實上閉包被設計得非常聰明,足夠它獲取外部的元素來給內部使用。每個被獲取的元素會作為拷貝或者是引用。如果閉包會修改它的值則建立一個引用,否則就生成一份拷貝。

如果閉包引用了一個包含或呼叫了閉包本身的例項,我們就會進入一個迴圈強引用。

來看一下例子:
class Person{ 
       
    var age:Int = 0 
       
    @lazy var agePotion: (Int) -> Void = { 
        (agex:Int)->Void in 
            self.age += agex 
    } 
       
    func modifyAge(agex:Int, modifier:(Int)->Void){ 
        modifier(agex) 
    }    
} 
   
var Mark:Person? = Person() 
Mark!.modifyAge(50, Mark!.agePotion) 
Mark = nil // Memory Leak 
這個agePotion閉包引用了它本身,而對當前的例項保證了強引用。同時例項保持了一個隊閉包的引用。BOOM~~~我們進入了一個迴圈強引用。

為了避免這種情況我們需要使用獲取列表Capture List.這個列表維護了我們想使用的例項的無主弱引用。語法十分簡單,只需要在閉包定義前新增 [unowned/strong self] 就行,然後你會得到一個無主的弱引用來替代以前的強引用。
@lazy var agePotion: (Int) -> Void = { 
     [unowned self](agex:Int)->Void in 
         self.age += agex 
} 
無主弱引用

在OC裡面我們知道弱引用是怎麼運作的。在Swift裡面也一樣,基本沒什麼變化。

所以什麼是無主引用呢。我仔細的看了看這個關鍵詞的介紹,因為它很好的說明了類間的關係的定義。

讓我們來簡單的描述一下人Person與銀行賬戶BankAccount間的關係:
  • 1.一個人可以擁有一個銀行賬戶(可選)。
  • 2.一個銀行賬戶屬於一個人(必須)。
We can describe this relation with code:  
class Person{ 
    let name:String 
    let account:BankAccount! 
       
    init(name:String){ 
        self.name = name 
        self.account = BankAccount(owner: self) 
    } 
} 
   
class BankAccount{ 
    let owner:Person 
       
    init(owner:Person){ 
        self.owner = owner 
    } 
} 
這寫關係會建立一個引用迴圈。第一種解決方案新增了一個弱引用給“BankAccount.owner”屬性。不過還用了一個無主引用作為約束:這個屬性必須有一個值,不能為空(之前的列表裡的第二點令人滿意)。

好了,關於無主引用沒有更多要說的了。其實它恰好就像一個沒有為所指引用增加作用的弱引用,不過為其保證了一個不為空的值。

總結

我不得不承認我偶爾會在編譯器報錯的時候無能為力的看著它心想:WTF。

我在Swift耗費的實驗和測試的次數越多我就會越清晰的明白其價值所在。在我們能舒坦的使用它之前脫離OC去接觸Swift開發和訓練需要相當大的興趣的。
相關閱讀
評論(1)

相關文章