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















 
 
 


 
 
 
 