Objective-C開發者眼中的Swift: 那些激動人心的新功能

本文翻譯自 http://www.raywenderlich.com/73997/swift-language-highlightsjavascript

若是你和我同樣,週一早上坐下來準備好好看看蘋果的Keynote,興奮地準備開始嘗試一些新的API,結果你聽到最多的是一門新的語言:Swift!你忽然被告知,這不是Objective-C的擴展,而是一門完徹底全新的語言。你是會激動呢,仍是高興,抑或頭腦一片空白?java

Swift將會徹底改變咱們寫iOS和Mac應用的方式,在這片文章裏,我歸納了這門語言的一些要點,並和Objective-C裏面相應部分作了對比。python

注意:這不是Swift的入門讀物,蘋果已經發布了一本很全面的 Swift Programming Language,我強烈建議你先讀它。這篇文章只會討論一些特別cool、值得玩味的知識點。web

類型

第一個重大的事情是Swift提供了類型推斷(type inference)。若是語言自己能夠推斷類型,那麼開發者就不再必聲明每個變量的類型了。編譯器能夠搞定這件事,以下面的例子,編譯器能夠自動把變量類型設爲String:swift

// automatically inferred
var name1 = "Matt" // explicit typing (optional in this case) var name2: String = "Matt" 

與類型推斷相伴而來的,Swift提供了類型安全的保證(type safety)。在Swift中,編譯器(大部分狀況如此,偶有例外)知道一個對象的完整類型,這讓它能夠作決定來怎麼編譯代碼。數組

這和Objective-C是截然不同的,Objective-C是一門很是動態的語言,在編譯期對象的類型並不徹底清楚,部分緣由是由於,你能夠在運行時給既存的類型增長新的方法、增長一個完整的新類、甚至改變一個實例的類型。安全

讓咱們看一個具體的例子,下面的Objective-C代碼:數據結構

Person *matt = [[Person alloc] initWithName: @"Matt Galloway"]; [matt sayHello]; 

當編譯器看到sayHello被調用的時候,它可以檢查頭文件裏面是否有這個方法的聲明,若是沒有則編譯會出錯,可是它能作的僅此而已。這一般狀況下是足夠了,可以用來發現問題最早出現的地方,以及出現的打字錯誤。可是由於動態性,編譯器不知道sayHello是否會在運行時被修改或者一直存在。譬如,它多是某個協議的一個可選方法(記住respondsToSelector: 檢查)。app

由於不是強類型的,因此Objective-C編譯器能作的就比較有限,沒法在方法調用的時候進行一些優化。衆所周知,方法調用都是經過objc_msgSend來動態dispatch的,你應該在backtrace裏看到過。對這個函數而言,選擇器的實現會通過查找和跳轉兩步,咱們不能否認這會增長額外開銷和複雜度。函數

咱們再來看看Swift怎樣完成一樣的事情:

var matt = Person(name: "Matt Galloway"); matt.sayHello() 

在Swift中,編譯器知道更多類型信息,它能確切地知道sayHello在哪裏被定義。所以,它能作一些優化而直接跳轉到方法的實現入口,而沒必要經由動態dispatch。在其餘語言中,可能會經過vtable來進行dispatch,這會比Objective-C採用的動態dispatch代價稍小一些,C++的虛函數就是這樣作的。

在Swift中,編譯器能提供更多幫助。他能阻止模糊類型帶來的bug,也能經過編譯優化讓你的代碼跑得更快。

泛型

另外一個重大的功能就是泛型。若是你對C++比較熟悉,那你可能以爲泛型(generic)和模板(templates)比較像。由於Swift有嚴格的類型限制,你聲明一個函數的時候,對於它的參數必須指明肯定的類型。但有時候你對於不一樣的數據類型,可能都須要類似的功能。

一個例子是一般很是有用的數據結構Pair,你須要將一對值存放在一塊兒。對於整數,你能夠這麼幹:

struct IntPair {
    let a: Int! let b: Int! init(a: Int, b: Int) { self.a = a self.b = b } func equal() -> Bool { return a == b } } let intPair = IntPair(a: 5, b: 10) intPair.a // 5 intPair.b // 10 intPair.equal() // false 

看起來還比較有用。可是如今你想讓它也能用在浮點數上,你可能就須要再定義一個FloatPair的類,這看起來就有點醜陋了。這時候泛型就能派上用場了,與再定義一個新的類相比,你也能夠:

struct Pair {
    let a: T! let b: T! init(a: T, b: T) { self.a = a self.b = b } func equal() -> Bool { return a == b } } let pair = Pair(a: 5, b: 10) pair.a // 5 pair.b // 10 pair.equal() // false let floatPair = Pair(a: 3.14159, b: 2.0) floatPair.a // 3.14159 floatPair.b // 2.0 floatPair.equal() // false 

這就至關有用了!可能這還不夠清楚說明你如今爲何要使用這一功能,可是相信我:機會無限!你很快就會在你的代碼中用到它了。

容器

你應該已經明白而且喜歡使用NSArray,NSDictionary以及他們的可修改版本了,如今你會看到他們在Swift中的等價物。幸運的是,他們很是相像,你能夠這樣來聲明一個數組和字典:

let array = [1, 2, 3, 4] let dictinary = ["dog": 1, "elephant": 2] 

這對你來講應該再熟悉不過,只是有些許差別:在Objective-C中,只要你願意,數組和字典中能夠放入任何類型的對象,可是在Swift裏面,數字和字典必須指定數據類型,這都是經過前面所講的泛型實現的。

上面的兩個變量也能夠這樣定義,帶上類型信息(記住:儘管你沒必要這麼作):

let array: Array = [1, 2, 3, 4] let dictinary: Dictionary = ["dog": 1, "elephant": 2] 

注意看在定義哪些類型能夠放入容器時,泛型的使用。對於數組還有一個簡略的形式,看起來可讀性更好,但本質上是作一樣的事情:

let array: Int[] = [1, 2, 3, 4] 

注意:如今會禁止任何Int以外的實例加入這個數組。這聽起來不那麼方便,可是毋庸置疑地有用:你的API不再用長篇累牘地解釋它返回的數組中會保存什麼內容,或者一個屬性裏面可以保存什麼內容,你能夠把這些問題交給編譯器,由它來進行前期的錯誤檢查和優化,是更明智的作法。

可變性

Swift中的容器的一個有意思的地方就是它的可變性。與Objective-C不同,ArrayDictionary並不存在一個可變版本,你只能經過letvar來區分它們。對於那些尚未讀過原書,或者還未深刻Swift的讀者(我建議大家讀一下,儘快!),只須要知道let用來聲明常量,var用來聲明變量。let有點相似於C/C++/Objective-C中的const
let聲明的容器沒法改變它的大小,也就是說不能調用追加和刪除方法,若是你這樣作,就會獲得相似錯誤:

let array = [1, 2, 3] array.append(4) // error: immutable value of type 'Array' only has mutating members named 'append' 

這一樣適用於字典類。這致使編譯器能夠推導集合的性質並作適當優化。好比,若是大小不能改變,那麼已經保存的值就永遠不用考慮從新分配以接納新值。因此,對於不會發生變化的集合對象,老是使用let來聲明是一種很好地作法。

字符串

Objective-C中的字符串是衆所周知的難用,就算只是鏈接字符串,代碼也是很是冗長,例如:

Person *person = ...;
 
NSMutableString *description = [[NSMutableString alloc] init]; [description appendFormat:@"%@ is %i years old.", person.name, person.age]; if (person.employer) { [description appendFormat:@" They work for %@.", person.employer]; } else { [description appendString:@" They are unemployed."]; } 

這種代碼太囉嗦了,裏面包含了太多與目的無關的字符。一樣的事情Swift裏面作起來就是這樣:

var description = "" description += "\(person.name) is \(person.age) years old." if person.employer { description += " They work for \(person.employer)." } else { description += " They are unemployed." } 

清晰許多!注意格式化一個字符串的方法,你如今也能夠經過+=操做符來鏈接簡單鏈接兩個字符串,這都簡潔太多,而且,也沒有可變、不可變兩種字符串存在。

Swift另外一個很是好的加強是字符串比較。你應該知道,Objective-C中使用==操做符並不能比較兩個字符串是否相等,你必須使用isEqualToString:這個方法,這是由於==只是比較兩個指針是否相等。Swift拋棄了這些概念,讓你直接經過==操做符來直接比較內容是否相等,這也意味着字符串也能夠用到switch語句中,下一節會介紹更多相關內容。

最後一則好消息就是Swift原生支持全部Unicode字符,你能夠在字符串中使用任何Unicode代碼點,甚至在函數名和變量名中,若是你願意,你能夠給一個函數命名爲(pile of poo!)。

對Unicode支持的另外一個很是方便的地方,就是提供了內建方法,來計算一個字符串的真實長度。一旦支持全範圍的Unicode字符,字符串長度的計算就再也不是一件簡單的事情。在UTF8字符串裏,你不能把字節數當成字符串長度,由於有些字符會佔用超過一個字節的空間。在Objective-C中,NSString老是按照UTF16來計算長度,把每兩個字節當成一個字符,但技術上說這並不老是正確的,由於有些Unicode字符會佔用2個以上的字節。

幸運的是,Swift提供了一個很是方便的函數來計算代碼點的真實個數,這就是名叫countElements的頂級函數。你能夠像這樣來使用它:

var poos = "a9" countElements(poos) // 2 

但這也並非對全部狀況都成立,它僅僅只統計了一下Unicode代碼點的個數,並無考慮引發字符變化的特殊情形。例如,你能夠加一個元音符號到一個前綴字符上,這時候countElements會認爲是兩個字符,而實際上看起來只有一個字符:

var eUmlaut = "e\u0308" // ë countElements(eUmlaut) // 2 

說一千道一萬,我想你確定會同意,相比Objective-C,Swift中的字符串好用太多!

Switch語句

我想講的最後一件事情就是switch語句,與Objective-C相比,Swift對它作了完全的改進。這是一件頗有意思的事情,由於涉及到一些東西,若是不從根本上打破Objective-C是C的嚴格超集這一事實,就沒法加入到Objective-C中去。

第一個讓人興奮的點是switch能夠用到字符串上了,這多是你之前想作但作不到的。在Objective-C中,你只能經過大量的if語句和isEqualToString組合來達到相似效果,例如:

if ([person.name isEqualToString:@"Matt Galloway"]) { NSLog(@"Author of an interesting Swift article"); } else if ([person.name isEqualToString:@"Ray Wenderlich"]) { NSLog(@"Has a great website"); } else if ([person.name isEqualToString:@"Tim Cook"]) { NSLog(@"CEO of Apple Inc."); } else { NSLog(@"Someone else); } 

這樣的代碼可讀性太差,而且有太多的字符要敲入。而一樣的事情,在Swift中則:

switch person.name {
  case "Matt Galloway": println("Author of an interesting Swift article") case "Ray Wenderlich": println("Has a great website") case "Tim Cook": println("CEO of Apple Inc.") default: println("Someone else") } 

除了字符串上的switch以外,還有一些有意思的地方須要注意。這裏沒有任何break,這是由於case並不會一直執行下去,碰到下一個case的時候自動就退出了,這樣能夠減小好多不當心引起的Bug!

下面的switch語句可能會讓你頭腦發矇,請作好準備:

switch i {
case 0, 1, 2: println("Small") case 3...7: println("Medium") case 8..10: println("Large") case _ where i % 2 == 0: println("Even") case _ where i % 2 == 1: println("Odd") default: break } 

首先,這裏出現了一個break語句。這是由於switch須要徹底窮舉,好比須要處理全部分支。在這裏,咱們默認是什麼都不作,因此使用break來明確聲明這一意圖。

其次,這裏有.....,這兩個用來定義範圍的操做符。前一個表示閉區間(包含最右邊的邊界值),然後一個則表示開區間(不包含最右邊的邊界值),這將會帶來難以置信的便利。

最後,能夠直接把case的分支變量做爲計算的輸入。以上爲例,若是一個數字不在0-10範圍內,那麼對於偶數會打印「Even」,而對於奇數則會打印出「Odd」。神奇到爆,有沒有!

接下來

但願這篇文章能讓你初嘗Swift的滋味,也能讓你明白後面蘊藏了多少寶藏,可是還有更多事情要作,我強烈同意你去讀一下蘋果公司的書,看看其餘文檔,以幫助你來深刻學習這門語言——你早晚須要這麼作!

相關文章
相關標籤/搜索