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

swiftfeature

咱們會在 AVOS Cloud Blog 上持續地發佈 Swift 相關的文章。AVOS Cloud 的 Swift SDK 也會很快推出,請你們關注。在轉載本文時請務必保持完整性並在開頭提供出處連接。web

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

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

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

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

類型

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

// 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: 檢查)。this

由於不是強類型的,因此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的滋味,也能讓你明白後面蘊藏了多少寶藏,可是還有更多事情要作,我強烈同意你去讀一下蘋果公司的書,看看其餘文檔,以幫助你來深刻學習這門語言——你早晚須要這麼作!

相關文章
相關標籤/搜索