本文翻譯自Mike Ash的博客:Interesting Swift Featureshtml
今天,我想要開始一些事情,探索幾個Swift的特性,其從Objective-C的角度看是使人感興趣的而且不尋常的,而且我將會思考他們會有什麼好處。git
顯式可變值程序員
Swift 使一些可變值做爲一個優秀語言的構成,而且使任何可變類型像這樣被標識。例如:github
var a: Int var b: Int?
在這裏,a是一個顯式的Int,而且它老是包含一些整型值。b是一個可變的Int,而且它也包含一個整型值,或者它什麼也不包含。編程
那個例子並沒有特別之處,可是藉助於其餘的類型它會變得更有意思。swift
var c: String var d: String?
像上面這樣,c是一個簡單的String,而且它老是包含一些字符串值。d 是一個可變的String,而且它也可能包含一個字符串值或者可能什麼也不包括。api
讓咱們看看Objective-C 中的等價的聲明:數組
int e; NSString *f;
這裏,e是一個明顯的整型,而且它老是包含一些整型值。f 是一個NSString *,這就意味着它也能夠包括一個指向NSString的指針,或者它可能包括nil,這就一般意味着"nothing"。閉包
注意:e是一個非可變類型,而f是有效的可變類型。NULL在c中的存在(和Objective-C中的nil同樣)意味着語言中的所有的指針類型是隱式的可變類型。若是你有一個 NSString *,就意味着」指向 NSString 的指針,或者是nil」。若是你有一個char *,就意味着「指向char 的指針,或者NULL」.app
C中存在的NULL在成爲指針時隔開整個靜態系統。每個指針類型都是half truth類型的。不管什麼時候你看到一個類型」指向X的指針」,那老是一個含蓄的表述」指向X的指針或者爲NULL」。
NULL 行爲徹底不一樣。你能夠解引用一個指針,可是你不能解引用NULL。Objective-C中你將給它發送消息,可是返回值老是0,甚至是沒有意義的。
[nil isEqual: nil] // returns NO!
記錄一個nil字符串,你會獲得null。[nil arrayByAppendingArray: obj]不會產生一個包括單元素obj而不是nil的數組。全部都有兩個不一樣的行爲:常規的一個,以及nil或者NULL值。
這是一個主要的問題由於咱們的代碼不是那種方式運行。有時候,咱們寫咱們的代碼「指向X或者NULL的指針」,可是有時咱們僅僅寫「指向X的指針」。可是沒法在語言中表達這個字母類型。
爲了說明這個問題,考慮以下兩個問題:
1.nil 是一個傳遞給isEqual:的合法參數嗎?
2.nil 是一個傳遞給isEqualToString:的合法參數嗎?
我將會在你檢查文檔的時候給你一點時間。
第一個問題是「yes」。文檔寫道:「在這個方法返回NO的時候或許是nil。」
第二個問題是...no嗎?可能嗎?文檔沒說明。最好的假設若是nil不是明確被容許,那麼它就不合法。
方法參數有時候能夠爲nil,有時候不行。返回值也是一樣。一些方法從不返回nil,然而一些返回。若是一個方法返回nil而且你傳遞返回值給一個不接受nil的方法,問題就來了。你必須增長校驗,而且編譯器不會幫助你,就像編譯器知道的那樣,一切接受nil。甚至若是你能夠掌控一切,文檔常常掩蓋nil處理。
這有另外一個要說明的問題:這個代碼合法嗎?
free(NULL);
在個人經驗中,大概99% 的C 和Objective-C程序員將會說」no」。一般這樣校驗:
if(ptr) free(ptr);
可是若是你檢查代碼,它是好的。 free()函數釋放ptr指向的內存。若是ptr是一個NULL指針,沒有操做會被完成。
若是人們常常獲得一些和這個一樣簡單的錯誤,咱們還有什麼但願在更復雜的狀況下。
Swift 在一個一致性模式下解決這個問題,方法是藉助於產生全部的非可變類型而且容許任何類型變成可變的。
對於Objective-C裏指針類型,這樣解決了由nil和NULL致使的不一致。若是一個參數能夠接受nil,那麼它將會被聲明成一個可變類型。反之不能夠。編譯器能夠輕鬆檢查你的代碼以確保它作正確的事情。一樣的,若是一個返回值能夠爲nil,很明顯它將會是一個可變類型。一般狀況下,你的類型將會是不可變的,讓它清空它曾經持有的東西。
由於任何類型可能成爲可變類型,這也將解決一些別的問題。框架中的許多方法返回指定的標記值用來標識一個返回值。例如indexOfObject: 返回NSNotFound,僅僅是NSIntegerMax的別名。這個很容易忘記:
NSUInteger index = [array indexOfObject: obj]; [array removeObjectAtIndex: index]; // oops
Swift 中等價的代碼將會返回一個可變類型:
func indexOfObject(obj: T) -> Int?
沒有找到對象將會返回一個非整數值,而且事實上可變類型是一個不一樣的類型,意味這編譯器能夠幫你檢查一切。
多個返回值
Swift 容許一個函數返回多個值。或者若是你以一個不一樣的方式看到它,Swift 函數老是返回一個值,可是那個值多是一個容易獲得的數組。
C 和Objective-C用兩種方式支持多值,都不太棒。函數/方法能夠返回一個包含多個值的結構體或者類,或者他們可使用外部參數。返回一個結構體是如此笨重,因此基本上歷來不會被使用,除非結構體理論上是一個單獨的單位,像frame返回一個NSRect 或者CGRect。外部的參數在Cocoa中用的不少,雖然,特別爲了錯誤句柄而設計。
NSError 類和對應的NSError ** 模式在10.2時代被展示而且快速普及。一些語言拋出異常,Cocoa 把一個NSError * 經過參數傳遞給調用者來指出錯誤。像這樣的代碼是很常見的:
NSError *error; NSData *data = [NSData dataWithContentsOfFile: file options: 0 error: &error]; if(data == nil) { // handle error }
這個將會變得麻煩,而且錯誤老是可變的,許多代碼將會像這樣替代:
NSData *data = [NSData dataWithContentsOfFile: file options: 0 error: NULL];
這有點糟糕,可是有吸引力。
多個返回值顯示除了異常和外部指針外的另外一個選項。在Python 中,例如,代碼將會看起來這樣:
data, error = NSData.dataWithContentsOfFile_options_error_(file, 0, None) if not data: # handle error
由於橋接,這變的有點奇怪,你將會必需要傳遞NONE做爲外部參數,甚至它正被轉換成第二個返回值。一個本地的Python調用可能看起來像這樣:
data, error = Data.dataWithContentsOfFile(file, 0)
Swift 版本看起來幾乎同樣的:
let (data, error) = Data.dataWithContentsOfFile(file, 0)
這是一件小事,可是NSError返回在Cocoa 中是很常見的,使一切變得更友好。問題已經足夠困擾我,我已經提交preprocessor crimes against humanity,嘗試在 Objective-C中構建多返回值,而且我不須要作其餘的任何事情。(我認爲一個好的狀況可能發生,用一個可選的類型返回錯誤。在任何狀況下,我指望研究哪些選項。)
泛型
這是重要的一點。Swift 支持泛型類和函數。
這打開了許多可能性。一個是容器類如今可能有一個靜態類型,在編譯階段,聲明他們包括的類型。在Objective-C中,一旦對象進入一個容器,你便失去了靜態類型信息。
NSArray *array = @[ @"a", @"b", @"c" ];
咱們可以處理這種狀況,但它不是最好的。因爲一件事情,它徹底終止了點語法:
NSLog(@"%@", array[0].uppercaseString);
這句編譯失敗,由於 array[0] 的類型是id而且沒有使用點語法。
另外一點,它努力保持對東西內部的追蹤。這並非一個大的對於你設置並馬上使用本地變量的處理。對於一個小的實例變量它可能有點小痛苦,而且可能真的讓你由於參數和返回值而煩惱。當你失敗而且獲得錯誤類型,結果是一個運行時錯誤。
對於字典來講,它變得更糟糕,大概有兩種類型錯誤。我有不少名字像_idStringToHamSandwichMap的實例變量,用來幫助我記住鍵值是NSString 實例以及HamSandwich實例的值。
在Swift中,類型不是數組和字典,而是Array<String> 和Dictionary<String, HamSandwich>。當你從數組中得到一個元素的時候,結果不是id (或者Swift中Any/AnyObject對象)。它也鏈接的很好,因此若是你爲了一個全部它的值的列表請求Dictionary<String, HamSandwich> ,結果是一個集合類型,其包括Dictionary<String, HamSandwich> 實例。
函數可能也是泛型的。這讓你寫操做任何類型的函數,而不須要在你傳遞他們的時候丟失類型信息。例如,你能夠用 Objective-C寫一個小的幫助函數用來提供一個默認值,在值爲nil的時候用。
id DefaultNil(id first, id fallback) { if(first) return first; return fallback; }
讓咱們忽略了一會不規範?:操做符提供這個精確的行爲。
這個工做是不錯的,可是丟失了類型信息,以至於不能運行。
NSLog(@"%@", DefaultNil(myName, genericName).uppercaseString);
經過使用macros和非標準的__typeof__ 操做符有可能變得更好,可是它會變的驚人的快速。
Swift中等價的代碼看起來將是像這樣的:
func DefaultNil(first: AnyObject?, fallback: AnyObject) -> AnyObject { if first { return first! } return fallback }
Swift 的嚴格的靜態類型會使得使用這個變得痛苦,比起Objective-C更是如此,其丟失了類型信息,可是基本上仍是信任咱們所說的類型。然而,藉助於泛型,咱們能夠輕易解決這個問題。
func DefaultNil<T>(first: T?, fallback: T) -> T { if first { return first! } return fallback }
這樣保存類型以便於返回值有一樣的參數類型。類型推斷意味着你甚至不須要告訴它要使用的類型,它僅僅知道參數類型便可。
泛型是另外一個個人preprocessor crimes against humanity 的例子,嘗試將他們嵌入到Objective-C中,而且我爲可以真正有這些特性而高興。
類型推斷
靜態輸入是不錯的,可是它可能在代碼中引發多餘的困惱:
NSString *string = @"hello"; UIAlertView *alert = [[UIAlertView alloc] initWithTitle...];
編譯器知道這些表述內容的類型,因此爲何咱們必須每次都重複那些信息?有了Swift,咱們能夠不用重複:
let string = "hello" let alert = UIAlertView(...)
儘管對於像JavaScript這樣的語言從表面看,有基本的類型變量,僅僅是類型不做爲聲明的一部分被寫出來。若是有一種狀況,你真正想要確保那個變量是一個肯定的類型(若是初始化不匹配,會產生一個編譯器錯誤),你仍然能夠聲明它:
let string: String = ThisMustReturnAString()
編程冗長從某種程度來講多是好事,有助於提升可讀性。可是Objective-C 和Cocoa 可能將其帶入一個荒謬的極端,若是有選項用來減小卻是不錯的。
Class-like結構體
像Objective-C同樣,Swift有類和結構體。和Objective-C不一樣的是,Swift 結構體能夠包括方法。代替一個大規模的用來操做結構體的函數集,一個Swift 結構體可能僅僅包括做爲方法的代碼。CGRectGetMaxX(rect) 能夠變成 rect.maxX.
該語言也提供一個默認的初始化方法(若是你不提供一個),這能夠省去你爲了初始化而編寫代碼的麻煩,而且擺脫像CGRectMake這樣難看的函數。
此外,結構體被更好地整合進該語言。在 Objective-C中,在結構體和語言之間有一個難看的分隔。聲明語法是不一樣的,你不能在沒有裝箱的狀況下藉助於Cocoa 集合使用他們,而且在用ARC的時候,將Objective-C對象做爲結構體的成員會變得極其不方便。
在Swift中,聲明語法是同樣的。由於相應的集合使用泛型,他們沒有包含結構體的問題。對象成員執行起來也不困難。他們基本變成按值傳遞而不是一個徹底不一樣的對象。
結果是小的模型類在Swift中變得更好。你曾經多少次藉助於幾個固定的鍵值來表明一個相關的本應該是一個類的數據集?若是你喜歡我,那麼答案就是「太多了」。那是由於生成一個簡單的Objective-C類有點小小的痛苦。在ARC以前這是特別真實的,可是甚至使用ARC,它也是很煩人的。你僅僅能夠把屬性扔給一個類而且調用它一天,固然:
@interface Person : NSObject @property (copy) NSString *name; @property NSUInteger age; @property (copy) NSString *phoneNumber; @end @implementation Person @end
可是,如今它是可變的,因此你必須但願沒人決定開始改變他們。而且沒有初始化,因此你必須經過幾行代碼設置實例變量,但願你別忘記他們。若是你想要一個好的不可變的模型類,你必須手動寫出一個初始化的函數而且保持它和相關屬性的同步。
有了Swift,這個簡單了:
struct Person { let name: String let age: Int let phoneNumber: String }
這個自動生成的initializer和實例全是不可變的。做爲獎勵,一旦結構體能夠潛在地被內聯分配內存,而不是須要在堆上分配,它也將會更有效。
尾部閉包
這是一個一流的特色,雖然不多被指出可是真的很好。當一個函數或者方法的最後一個參數是閉包的時候(即一個block),Swift 容許你在調用以後寫block,在括號外,兩種調用是等價的:
dispatch_async(queue, { println("dispatch!") }) dispatch_async(queue) { println("dispatch!") }
若是閉包僅僅是一個參數,那麼你甚至不須要括號:
func MainThread(block: Void -> Void) { dispatch_async(dispatch_get_main_queue(), block) } MainThread { println("hello from the main thread") }
這是有意思的,由於它拆除了一個在語言編寫和庫調用之間的障礙。當我在2008年討論的時候,對於Objective-C來講blocks是一個很棒的補充,由於他們容許你寫你本身的控制結構。它藉助於GCD有一個大的用途,其可以提供許多有用的像語言構建那樣的異步調用,可是做爲庫調用來實現。然而,使用Objective-C,在如何調用你寫的block和如何使用內建的語言結構上仍舊有一個語法上的區別。
例如,Swift (至少當前)缺乏像Objective-C的@synchronized語法。尾部閉包意味着你可以實現你本身的,而且使其看起來很像內建的實現方式。
synchronized(object) { ... }
若是你正在用Objective-C來作這個事情,括號不能到達合適的地方,而且你略尷尬地終止一些事情。
synchronized(object, ^{ ... });
這是一個小事情,由於它仍然運行良好,而且易於閱讀。但同可以以更天然的方式組織代碼是不錯的事情。
運算符重載
我想象這將會是有爭議的。不幸的是,有不少程序員已經被C++中嚴重的操做符重載文化所影響。當某種語言設計者思考爲了IO重載<< 和>>位轉換操做符,那麼你知道你已經在困境中了。我不懼怕去責怪人。
然而,我認爲若是你超越C++,狀況會好得多。操做符重載存在於許多語言中,沒有像C++這樣瘋狂濫用。
當人們僅僅使用任意標誌來代替那些操做符,操做符重載就是有危害的。當人們使用它以致於傳統的操做符可以以新的形式被用做他們傳統的目的,就是一件不錯的事情。
舉一個簡單的Objective-C 例子:
NSNumber *count = ...; count = @([count intValue] + 1);
你能夠像這樣寫代碼,即若是數量被保存在一個字典或者一個數組中。處理裝箱或者拆箱僅僅增長數目將會是一件痛苦的事情。一旦NSNumber 的值可以超過最大整型值,就會變得有點危險。你能夠寫一個方法完成那個事情,可是它仍然不太棒。
count = [count add: @(1)];
藉助於操做符重載,你可以實現 讓+, +=, 和++來 作和操做一個標準整型值一樣的事情。代碼將會變成:
NSNumber *count = ...; count++;
固然,Swift中的泛型本意是NSNumber自身一般更少有這種須要,可是有許多其餘的潛在的運算符重載的需求。Swift沒有內建的任意大小的整型,可是你可以本身實現一個用來完成全部的標準運算的操做符。
一個真正有意思的例子是創建一個自動佈局約束,做爲SwiftAutoLayout的示例。這個使用操做符重載將表達式view1.al_left() == view2.al_right() * 2.0 + 10.0 轉換成NSLayoutConstraint 的實例。這是一個好的方式,比蘋果官方的更酷,可是成了瘋狂的視覺格式的語言。
除了重載已有的操做符以外,Swift 容許你利用限定字符集聲明全新的操做符。若是你真的想作,你能夠調用<~^~>創建一個操做符而且使其作你想作的事情。看到人們這樣作將會是有意思的,咱們將會必須很是當心以便於讓這種能力不會上癮。
結論
Swift 是一種咱們期待的那種用來實際作事的很是有意思的新的語言。它有許多不一樣於Objective-C的特色,可是我認爲這些最終將會很好用。
這就是今天要講的。下次帶來更多的有意思的東西。我計劃繼續探討Swift 一段時間。若是你有一些你想要明白的關於Swift 的話題,那固然很棒了!若是關於一些事情,我能夠將它放在文件中,方便閱讀。另外一種方式,若是你有一個想了解的話題,請發給我!