摘要:Swift發展已有一年多的時間,越來越多的開發者開始學習和使用這門語言,但在實際項目中的應用卻還是比較少。本文作者從Swift的語義邏輯、易用性、語言特性、與Cocoa互動等全方位分享如何在實際工程中使用Swift開發。
CSDN移動将持續為您優選移動開發的精華内容,共同探讨移動開發的技術熱點話題,涵蓋移動應用、開發工具、移動遊戲及引擎、智能硬體、物聯網等方方面面。如果您想投稿、參與内容翻譯工作,或尋求近匠報道,請發送郵件至tangxy#csdn.net(請把#改成@)。
簡介
Swift語言從WWDC2014釋出開始,到現在已經發展了一年多時間,越來越多的開發者也開始學習和使用這門語言。但就我所了解的情況來看,在實際項目中Swift的應用還是比較少。開發者給它的評價也是褒貶不一,有的說它的安全性高,有的說它的特性多,有的說它的學習成本高,還有的說它是一個玩具語言不适合工程。其實這都很正常,因為一千個人眼中有一千個哈姆雷特,語言的喜好本身就是一件很主觀的事情。具體這個語言怎麼樣,适不适合工程,需要每個人實踐之後才能得出自己結論。
Swift的特點
支援Unicode
代碼原生支援Unicode字元。不僅在字元串中,甚至變量名、函數名等都能直接使用Unicode字元。雖然看上去很強大,但似乎并沒有什麼用,應該沒人喜歡在程式設計時不停的切換輸入法吧?
安全的類型
采用嚴格的類型,并去掉了隐式類型轉換:
隐式類型轉換一直是一把雙刃劍,雖然使用便利,但是可能引入一些很難調試的BUG,不容忽視。把隐式類型轉換摘除,利大于弊。
從類型層面将空值nil隔離,使用時要求對空值進行處理:
嚴格的語義邏輯
Swift對C語系一些常見的語義邏輯漏洞進行了修改,比如if等條件限定為Bool類型,指派“=”操作不再有傳回值等(其實有,是Void,即空元組“()”)。雖然使用上沒有之前那麼友善和靈活,但這種改變能杜絕很大一部分的手誤BUG,比如“==”寫成“=”,還能避免一些偷懶所引入的很隐秘的坑,對程式的穩定性和程式員好習慣的培養有很大幫助。
易用性
沿用并完善了Objective-C的函數中綴調用方式,參數有了真正的名字,調用時帶上參數名能讓函數接口更容易了解,可讀性更好:
優化了可變參數定義和使用方式:
在C語系中定義可變參數還需要va_list、va_start等,一段時間不用根本想不起來怎麼寫,還得上網查,而在Swift中隻需要周遊一個數組就能取到所有參數,非常友善。
優化了控制流的使用:
Switch的case可以連寫,而且加入了很多實用的比對模式,比如比對範圍、元組、條件等,還可以自定義比對模式,十分強大。另外分支預設是break方式,不像在C語言中,明知道90%的case都是要break的,還要強制寫上。還有一個很好的優化就是加入了跳轉标簽,在多重循環間控制轉移的時候更靈活了。
加入了很多實用文法糖,僅僅一個閉包就有這麼多簡寫方式:
這些文法糖能節省大量的開發時間和代碼量,使用得當也能讓代碼更清晰,可讀性更好。當然如果濫用的話可能起反作用。
豐富的語言特性
Swift支援類、協定、繼承、多态等面向對象的語言特性:
也有進階函數、閉包等函數式程式設計特性:
還有泛類型、泛函數、泛協定等泛型程式設計特性以及操作符自定義等新特性:
總之Swift加入了大量的流行語言特性,功能靈活、強大,但是文法點也增加了很多,導緻文法學習難度增大,各位可以按需要進行有針對性的學習。
Swift的一些重要概念
值與引用
值與引用類型在某些情況下與我們的程式設計習慣可能會有些沖突,是Swift初學常遇到的一個坑。先看一個例子,定義兩個數組arr1和arr2,arr2用arr1指派:
修改arr2[0]後,發現arr1[0]并沒有修改。由此可知在Swift中,Array類型是值類型。再來看看Array的實作方式。按住Command鍵點選Array類型,進入到Swift庫,可以看到如下定義:
Array和Dictionary,包括Int、Set、Double等基本内建資料類型都是由struct及其實作的一組協定構成。由于struct類型是值類型,是以Swift中的基本内建類型都是值類型。由于是值類型,是以每次指派或者傳參的時候都會有個拷貝的過程。我們先來做個實驗:
運作以上代碼可以發現,在修改了arr[0]的情況下,指派的時間是1783ms(模拟器下),而僅是讀取的情況,指派時間是0ms。這個結果說明值傳遞使用了寫時拷貝(copy on write)技術,也就是說隻要不修改存儲的資料,副本和原值共享記憶體區域。是以我們在使用這些值類型的時候一般有一些原則:
- 盡量限制資料規模;
- 如果資料規模較大,盡量不用作指派和傳參;
- 如果需要指派和傳參,盡量定義為常量或後續不修改資料;
- 如果以上都不能避免,可以使用引用類型代替值類型,如 NSMutableArray等;
Optional
Optional可選類型是Swift的一個重要概念,也是初學者比較難了解的地方。我們經常能在閱讀代碼時看到下面這樣的代碼:
有問号有感歎号,放的位置不一樣作用也不一樣,用的時候搞不清什麼時候該用哪個,隻好跟着xcode提示走,掉進坑裡也不知道。要搞清楚這個問題先要明白Optional是什麼。在Swift庫中可以找到Optional的定義:
Optional其實是個泛型枚舉,定義時的類型比如 Int? 等價于 Optional<Int>。它實作了NilLiteralConvertible協定,是以能被nil字面量指派。當可選類型用nil指派時,nil被轉化成None,當用非nil指派時,轉化為Some(T)。Some(T)這種形式叫做相關值(Associated Values),可以這麼了解,這是一種名叫Some,能存放T類型的盒子,我給它賦的值就放在這個盒子裡儲存。
可選類型相當于對nil或實際的值做了封裝,是以使用Optional時需要将實際的值從Some(T)中取出,這個過程就叫解可選(unwrap)。
先來看看問号定義的可選類型:
問号定義的可選類型就是普通的Optional,常用的解可選方式有三種,如上圖所示。
- 先對opt進行判空,若不為空則用 opt! 進行強制解可選。“!”在這裡的意思就是,我确定該值不為空,請直接取出實際值;
- 用if let unwrap = opt 來解可選。這是Swift為解可選做的文法糖,叫做可選綁定。如果opt為nil則進入else分支,若不為nil則将實際值提取到unwrap,然後進入if分支;
- 利用opt ?? 0解可選。“??”叫空合運算符(Nil Coalescing Operator),作用就是,如果opt為nil,表達式值取後面的預設值,否則表達式傳回opt的實際值;
再來看看感歎号定義的可選類型:
感歎号定義的可選類型叫做隐式解可選類型。在概念上與問号定義的可選類型并沒有什麼差別,隻是在使用形式上有些不同。
隐式解可選類型在用作右值時并不需要人為進行解可選,它預設由編譯器進行強制解可選,相當于編譯器自動在後面添加一個感歎号。這樣在使用的時候就不用強制對空值進行處理,更友善一點,但是卻十分危險,因為必須由人工保證opt_f在用到的地方必須不為nil,否則程式會crash。而人腦總不如機器保險,是以一般情況不建議使用隐式可選類型。
問号還有一種使用場景叫做可選鍊。先來看看這麼一種情況:
我定義了一個Person類,類有father屬性表示他的父親,而孤兒可能不知道父親是誰,是以father是個可選類型。現在來了一個需求,需要擷取某個人曾祖父的名字,我們可能會寫出如下代碼:
看上去很不美觀,寫起來更費勁。針對這種情景,Swift做了優化,加入了可選鍊這種文法糖,上面的代碼就能改寫為:
用問号把方法調用或屬性擷取等連接配接起來,組成一條調用鍊。意思就是我不關心中間過程,隻關心最後結果,如果中途任何環節傳回了nil,就直接傳回nil,否則傳回最後結果。這樣就能節省很多工作量,讓代碼看上去更美觀,結構更清晰。需要注意的一點是,雖然name是确定的String類型,但是由于可選鍊上任一環節都可能傳回nil,最後得到的是一個String?可選類型,是以還需要做一次解可選操作。
Swift的應用
與Cocoa互動
要用Swift寫App首先需要了解的就是UI怎麼寫、系統功能怎麼調用。由于Swift并沒有重寫系統功能庫,隻是對Cocoa進行了橋接,是以要調用系統功能就要與Cocoa的互動。Swift與Cocoa互動的細節非常多,但是并沒有太大難度,因為Cocoa的使用與用Objective-C開發時沒有太大不同,一般來說跟着Xcode的提示走基本都沒有問題。需要注意的幾點:
- Swift沒有發消息這種方式,所有方法調用和屬性通路都是用點操作, alloc、init橋接成了swift中的構造器,使用時直接用class執行個體化的方式即可。
- 一些常用類型在swift中都有對應,可以互相轉換,通常情況建議使用swift的類型。
- Object-C中有些機制比如 KVO等在swift中不支援,如果要使用,需要帶上@objc、dynamic等标記。
多線程
Swift語言本身并沒有多線程支援,是以要使用多線程還是要調用NSThread、GCD等:
此外,Object-C中一個很友善的代碼互斥方式@synchronized在swift中不支援,是以要實作代碼互斥需要自己使用鎖。當然也可以模仿@synchronized自定義一個synchronized方法,利用尾部閉包造出原來的感覺:
單例
單例模式是移動開發中常見的一種模式,其一般要求是:
- 隻初始化一次;
- 線程安全;
在Object-C中單例通常是用dispatch_once來實作的,在Swift中同樣可以這麼做。但是由于Swift中支援全局變量、常量的lazy初始化,我們可以簡化單例的實作:
通過let定義單例執行個體常量來保證線程安全,通過全局常量的lazy初始化來保證單例隻初始化一次。
ARC
Swift中的ARC原理與Object-C中一樣,隻不過在使用形式上有所不同。Swift中所有引用預設都是強引用,另外加入了兩個ARC标記:
- weak:類似Object-C的weak,弱引用,引用失效後引用自動指派為nil;
- unowned:類似Object-C中的unsafe_unretain,弱引用,引用失效後保留引用本身;
由于weak引用在使用過程中可能變為nil,是以weak引用必須是可選類型(Optional)。另外Swift對閉包捕獲的變量的标記做了優化:
在Object-C中這種情況需要在閉包之外定義一個weak self,然後閉包捕獲weak self。Swift中隻需要把weak self寫在閉包參數表之前的中括号中即可完成同樣功能,這種方式代碼結構更清晰,可讀性更好。需要注意的是在閉包内self是weak的,是個可選類型,是以使用self時需要解可選。
Delegate
代理模式是開發中很常見的一種模式,它的實作原理與Object-C類似,也是通過協定來确定代理接口,用實作協定的執行個體來充當代理:
不同的地方在于,Object-C中delegate是id類型的,而swift中協定是一種基本類型,delegate可以直接定義為協定類型,這樣能讓代理的職責更明确。要注意的地方是delegate是weak的,是以必須使用可選類型,而且weak标記的必須是引用,是以protocol必須打上class标記,表示該協定隻能被class實作,這樣協定類型才能當做引用類型使用。
自定義操作符
Swift中可以自定義操作符,自定義一個操作符需要兩個要素:
- 操作符特性描述,包括:操作符位置 prefix、infix、postfix等,操作符的結合性left、right以及操作符的優先級;
- 操作符功能函數,參數和傳回值需要與操作符特性一緻,比如 prefix的操作符接受一個參數,傳回一個值;
比如我們可以定義一個笑臉操作符,他在字元串後面添加一個笑臉(^_^):
函數式程式設計
在Swift中,函數是first-class,也就是說函數可以作為參數傳入也可以作為傳回值傳回,也可以給變量指派。函數式程式設計是一種很強大、很靈活的思想,在Object-C中也有block等函數式程式設計思想,但并不明顯,在Swift中這一點得到了強化。
比如說可以實作一個函數工廠:
泛型程式設計
Swift中增加了泛型這個概念,泛型是一種很好的代碼複用機制,用于将類型不同但實作相同的代碼統一起來。泛型在Swift中應用非常廣泛,你隻要跳轉到Swift的庫檔案就會發現類型、操作符、全局函數的定義中大量應用了泛型,是以不再做詳細介紹。這裡有一個小例子,利用操作符自定義、函數式和泛型在代碼中實作管道機制:
總結
Swift有着更嚴格的類型和更規範的語義,針對容易造成BUG的點進行了優化,又加入了很多不錯的文法糖,代碼更簡潔、更安全。它吸收了大量其他語言的優秀特性,可以實作一些強大靈活的設計模式,但也造成了其文法繁雜的缺點,增加了學習的難度。Swift本身的設計目标是高效、靈巧,但由于需要相容Cocoa,導緻其設計受到牽制,引入了一些有隐患的設計。由于這些特點,編寫Swift代碼其實很依賴IDE的輔助,但目前為止xcode的表現還不穩定,經常提示錯誤,再加上幾乎每次xcode更新都伴随着Swift文法修改,Swift想要廣泛應用于實際工程還有一段路要走,而Swift2.0的釋出和Swift開源化,無疑将加快這個程序。