從Objective-C到Swift:幾點想法和意見
在這篇文章里我想跟大家談?wù)動嘘P(guān)我近來從Objective-C過渡到Swift的一些感受。我會盡可能的給大家一些意見,提示一些誤區(qū)并比較一下在兩種語言之間的差異。話不多說,讓我們開門見山。
注意:本文討論的開發(fā)環(huán)境為Xcode 6 beta 2版本。
單一文件結(jié)構(gòu) VS 接口-實現(xiàn)
最值得一提的一大改動便是在Objective-C中“[接口].h/[實現(xiàn)].m”這種文件結(jié)構(gòu)被取締了。
其實我本人是很支持這種文件結(jié)構(gòu)的,因為通過接口文件來獲取及共享類特性的方式相當(dāng)安全而且簡單,不過現(xiàn)在不得不面對它不復(fù)存在的現(xiàn)實了。
在Swift中并不存在接口與實現(xiàn)分割成兩個文件的現(xiàn)象,我們僅需要依靠實現(xiàn)來構(gòu)建一個類就行了(并且在寫的時候甚至不可能添加關(guān)于可訪問性的修改)。
如果對于這一改動感到無法忍受的話應(yīng)注意以下事項:
最為明顯的:靠直覺。
我們可以借助漂亮的文檔來提高類的可讀性。舉個例子,我們可以把所有想作為public的要素全部挪到文件開頭去,也可以采用擴展來區(qū)分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的實現(xiàn)放入一個private常量的實例中,然后用“正常”的類的實現(xiàn)來充當(dāng)接口。不過private部分并非會真正的隱藏起來,只不過在訪問的時候需要加上一個private關(guān)鍵字了:
- let reader = DataReader()
- reader.private.privateFunc()
問題來了:僅是為了***要將這些private的部分隱藏起來要把代碼寫得這樣怪異值得嗎?
我的建議是在可見性的修改出來之前(蘋果正在忙這個事),我們還是采用詳細(xì)的文檔或者多少用一點擴展來完成這個事。
常量和變量
在 寫Objective-C的時候我會很少的使用到const關(guān)鍵字,甚至于我知道有的數(shù)據(jù)時不會變的(好吧不要吐槽我)。然而在Swift中蘋果建議開發(fā) 者們多花點心思在使用常量(let)而不是變量(var)上。所以請注意要弄明白你的變量的具體要做什么。你會使用常量的頻繁度將是你從未想象過的。
更加簡化的寫法
來看一下下面的兩行代碼并比較有何不同:
- let wsURL:NSURL = NSURL(string:"http://wsurl.com");
- vs
- let wsURL = NSURL(string:"http://wsurl.com")
在我最開始接觸Swift的前兩個星期我強迫自己不要在每一行代碼***都添加分號,現(xiàn)在我感到人生圓滿(不過現(xiàn)在寫Objective-C的時候我不會加分號了)。
類型推斷可以直接根據(jù)變量的定義為其指派類型,相比較Objective-C這類冗雜的語言來說,在這里倒是可圈可點。
我們應(yīng)該使用一致的命名方式,否則其他的開發(fā)者(包括你自己)就很難通過極其糟糕的命名來推測其類型:
- let a = something()
更加合理的命名是這樣的:
- let a = anInt()
還有一個改動就是關(guān)于括弧號,他們不再需要配對了:
- if (a > b){}
- vs
- if a > b {}
不過請記住,我們在括號中間寫入的部分會被認(rèn)為是一個表達式,在這里不總是代表這樣寫是對的。在變量綁定時我們不能像下面這樣使用括號:
- if (let x = data){} // Error!
- if let x = data {} // OK!
使用類型判斷和刪除分號及括號并不完全必要,不過我們可以考慮一下用以上建議的方式來寫Swift代碼,這樣的話會提高代碼的可讀性并且減少一些輸入量。
可選值
有多少次我們困惑與函數(shù)的返回值該如何設(shè)置?我曾經(jīng)使用過NSNotFound, -1, 0,自定義的返回值來表示返回為空。
現(xiàn)在有了可選值的出現(xiàn)很好的解決了返回值為空的問題,我們僅需要在數(shù)據(jù)類型的后面添加一個問號就可以了。
我們可以這樣寫:
- 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來取得它的值。
如果對于一個屬性沒有設(shè)定為可選值,我們又沒有為其賦值的話,編譯器會立馬不爽快的。
一旦初始化了之后便沒有設(shè)定非可選屬性的機會了。
所以我們應(yīng)該事先考慮一下類的屬性與其它部分的關(guān)系以及在類進行實例化的時候它們會發(fā)生什么變化。
這些改進徹底的改變了構(gòu)思一個類的方式。
可選值的拆包
你會發(fā)現(xiàn)可選值這個東西難以理喻,因為你不會理解為什么編譯器會提示你在使用之前對其進行拆包。
Mark.car?
我建議你把可選值當(dāng)做一個結(jié)構(gòu)體(當(dāng)做結(jié)構(gòu)體的話會好理解一些),其中包括了一個你所設(shè)定的值。不過外面包裹了其他東西(wrap)。如果里面的值有定義,你就可以進行拆包(unwrap)然后得到你所想得到的值。否則你就得到一個空值(nil)。
使用感嘆號"!"來進行強制拆包而不管其中的值是否有定義,這樣做是有風(fēng)險的,因為如果里面的值沒有定義的話應(yīng)用會崩掉。
委托模式
經(jīng)過多年的Objective-C和Cocoa代碼編寫我想大部分人都對使用委托模式養(yǎng)成了一種嗜好。注意了!我們還是可以繼續(xù)保留這種嗜好的,下面是一個非常簡單的例子:
- @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?()
請注意我們在協(xié)議之前必須加@obj前綴,因為后面使用了@optional。同時編譯器也會在這里報一個警告的消息以防你沒有加上@obj。
要實現(xiàn)協(xié)議的話,我們需要構(gòu)建一個類來實現(xiàn)它然后用曾經(jīng)在OC上用過的方式來指派。
- class ViewController: UIViewController, DataReaderDelegate {
- override func viewDidLoad() {
- super.viewDidLoad()
- let reader = DataReader()
- reader.delegate = self
- }
- func DataWillRead() {...}
- func DataDidRead() {...}
- }
目標(biāo)-動作模式
另一常用的設(shè)計模式:目標(biāo)-動作模式。我們?nèi)匀煌瑯涌梢韵裨贠C中使用它那樣在Swift中實現(xiàn)它。
- 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:")
單件模式
簡直又愛又恨。單件模式依舊是設(shè)計模式中最為常用的模式之一。
我們可以用GCD和dispatch_once來實現(xiàn)它,當(dāng)然還可以用let關(guān)鍵字來實現(xiàn)線程安全。
- class DataReader: NSObject {
- class var sharedReader:DataReader {
- struct Static{
- static let _instance = DataReader()
- }
- return Static._instance
- }
- ...
- }
我們來快速瀏覽一下這段代碼:
1.sharedReader是一個靜態(tài)的復(fù)合屬性(我們也可以替換為方法)。
2.靜態(tài)屬性不允許在類被實現(xiàn)的時候重構(gòu),所以由于內(nèi)部類型是被允許的,我們可以再這里加入一個結(jié)構(gòu)體。
3._instance是一個常量,它不會被重寫而且保證線程安全。
可以參考下面DataReader單例的用法:
- DataReader.sharedReader
結(jié)構(gòu)和枚舉
Swift中的結(jié)構(gòu)和枚舉簡直神乎其神,你根本不會在其他的語言里面找到像它們這樣的。
它們支持方法:
- 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()
如你所見在這里的結(jié)構(gòu)體使用了初始化,而且這個是Swift自動創(chuàng)建的(我們可以添加一些自定的實現(xiàn))。
枚舉類型的語法比起我們用過的會稍難。
它的定義需要用到關(guān)鍵字case:
- enum Fruit {
- case orange
- case apple
- }
而且枚舉并不局限于int型:
- enum Fruit:String {
- case .orange = "Orange"
- case .apple = "Apple"
- }
而且還可以用的更復(fù)雜一些:
- 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
在上面我們?yōu)镕ruit枚舉類添加了一個內(nèi)部類型Vitamin和一個復(fù)合的mainVitamin,并且這樣的結(jié)構(gòu)還可以根據(jù)枚舉的值來進行初始化里面的元素……是不是已經(jīng)感到眼花繚亂了?
可變與不可變類
在OC中我們總會用到可變以及不可變類,舉個例子?NSArray和NSDictionary。在Swift里面我們不在像這樣來區(qū)分?jǐn)?shù)據(jù)了,只需要用常量和變量的定義來替代。
數(shù)據(jù)變量是可以變的而數(shù)組常量的值不可更改。所以請記下這個公式:
“let = immutable. var = mutable”.
塊和閉包
我非常喜歡塊的語法,因為它相當(dāng)簡單而且好記。
- <p>
順帶提一下,因為有多年Cocoa的變成習(xí)慣所以有時候我會偏愛于用塊來替代簡單的委托作業(yè)。這是很靈活快捷的方式,而且非常實用。
Swift中與塊相對的是閉包。閉包的作用極為強大而且蘋果在將其簡單化上做得很棒,很容易就可以實現(xiàn)。
官方文檔里的示例只能說讓我無言以對。
它是這樣定義的:
- reversed = sort(names, { (s1: String, s2: String) -> Bool in
- return s1 > s2
- })
然后是這樣重構(gòu)的:
- reversed = sort(names, >)
所以,由于類型判斷的存在我們能以不同的方式來實現(xiàn)一個閉包、速寫參數(shù)($0, $1)和直接操作函數(shù)(>)。
在這篇文章里我打算遍歷一下閉包的用法不過此前我想對如何獲取閉包中的值說幾句。
在OC里面,我們定義一個變量像_block這樣,以方便我們想預(yù)備將它壓入塊。不過在閉包里面這些都沒有必要。
我們可以使用和修改周圍的值。事實上閉包被設(shè)計得非常聰明,足夠它獲取外部的元素來給內(nèi)部使用。每個被獲取的元素會作為拷貝或者是引用。如果閉包會修改它的值則創(chuàng)建一個引用,否則就生成一份拷貝。
如果閉包引用了一個包含或調(diào)用了閉包本身的實例,我們就會進入一個循環(huán)強引用。
來看一下例子:
- 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閉包引用了它本身,而對當(dāng)前的實例保證了強引用。同時實例保持了一個隊閉包的引用。BOOM~~~我們進入了一個循環(huán)強引用。
為了避免這種情況我們需要使用獲取列表Capture List.這個列表維護了我們想使用的實例的無主弱引用。語法十分簡單,只需要在閉包定義前添加 [unowned/strong self] 就行,然后你會得到一個無主的弱引用來替代以前的強引用。
- @lazy var agePotion: (Int) -> Void = {
- [unowned self](agex:Int)->Void in
- self.age += agex
- }
無主弱引用
在OC里面我們知道弱引用是怎么運作的。在Swift里面也一樣,基本沒什么變化。
所以什么是無主引用呢。我仔細(xì)的看了看這個關(guān)鍵詞的介紹,因為它很好的說明了類間的關(guān)系的定義。
讓我們來簡單的描述一下人Person與銀行賬戶BankAccount間的關(guān)系:
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
- }
- }
這寫關(guān)系會創(chuàng)建一個引用循環(huán)。***種解決方案添加了一個弱引用給“BankAccount.owner”屬性。不過還用了一個無主引用作為約束:這個屬性必須有一個值,不能為空(之前的列表里的第二點令人滿意)。
好了,關(guān)于無主引用沒有更多要說的了。其實它恰好就像一個沒有為所指引用增加作用的弱引用,不過為其保證了一個不為空的值。
總結(jié)
我不得不承認(rèn)我偶爾會在編譯器報錯的時候無能為力的看著它心想:WTF。
我在Swift耗費的實驗和測試的次數(shù)越多我就會越清晰的明白其價值所在。在我們能舒坦的使用它之前脫離OC去接觸Swift開發(fā)和訓(xùn)練需要相當(dāng)大的興趣的。
本文鏈接:http://www.cocoachina.com/ios/20141011/9884.html