這篇文章是自己學習Swift的筆記與深化。希望這篇文章能夠幫助已經有Objective-C經驗的開發者更快地學習Swift。同時也品味到Swift的精妙之處。
結論放在開頭:
我認為Swift比Objective-C更優雅,更安全同時也更現代,更性感。
文章組織脈絡:
從Objective-C到Swift的語法差異。我們熟悉的Objective-C特性在Swift中如何展現。
從Objective-C到Swift的進步改進。研究對比Swift在安全性,易用性上的提升,給我們帶來的新程式設計正規化。
目錄:
1.屬性(property)和例項變數(instance variable)
2.控制流
3.函式
4.類與初始化(Initializers)
5.列舉與結構體
6.協議(Protocols)
7.Swift與Cocoa
8.總結
1.屬性(property)和例項變數(instance variable)
Objective-C property in Swift world
在Cocoa世界開發的過程中,我們最常打交道的是property.
典型的宣告為:
1 |
@property (strong,nonatomic) NSString *string; |
而在Swift當中,擺脫了C的包袱後,變得更為精煉,我們只需直接在類中宣告即可
1 2 3 |
class Shape { var name = "shape" } |
注意到這裡,我們不再需要@property指令,而在Objective-C中,我們可以指定property的attribute,例如strong,weak,readonly等。
而在Swift的世界中,我們通過其他方式來宣告這些property的性質。
需要注意的幾點:
strong: 在Swift中是預設的
weak: 通過weak關鍵詞申明
weak var delegate: UITextFieldDelegate?
readonly,readwrie 直接通過宣告變數var,宣告常量let的方式來指明
copy 通過@NSCopying指令宣告。
值得注意的是String,Array和Dictionary在Swift是以值型別(value type)而不是引用型別(reference type)出現,因此它們在賦值,初始化,引數傳遞中都是以拷貝的方式進行(簡單來說,String,Array,Dictionary在Swift中是通過struct實現的)
延伸閱讀:Value and Reference Types
nonatomic,atomic 所有的Swift properties 都是nonatomic。但是我們線上程安全上已經有許多機制,例如NSLock,GCD相關API等。個人推測原因是蘋果想把這一個本來就用的很少的特性去掉,執行緒安全方面交給平時我們用的更多的機制去處理。
然後值得注意的是,在Objective-C中,我們可以跨過property直接與instance variable打交道,而在Swift是不可以的。
例如:我們可以不需要將someString宣告為property,直接使用即可。即使我們將otherString宣告為property,我們也可以直接用_otherString來使用property背後的例項變數。
1 2 3 4 |
@interface SomeClass : NSObject { NSString *someString; } @property(nonatomic, copy) NSString* otherString; |
而在Swift中,我們不能直接與instance variable打交道。也就是我們宣告的方式簡化為簡單的一種,簡單來說在Swift中,我們只與property打交道。
A Swift property does not have a corresponding instance variable, and the backing store for a property is not accessed directly
小結
- 因此之前使用OC導致的像巧哥指出的開發爭議就不再需要爭執了,在Swift的世界裡,我們只與property打交道。
- 並且我們在OC中init和dealloc不能使用屬性self.property = XXX來進行設定的情況得以解決和統一。
(不知道這一條規定,在init直接用self.property = value 的同學請自覺閱讀iOS夯實:記憶體管理)
:)
個人覺得這看似小小一點變動使Swift開發變得更加安全以及在程式碼的風格更為統一與穩定。
Swift property延伸:
- Stored Properties和Computed properties
在Swift中,property被分為兩類:Stored Properties和Computed properties 簡單來說,就是stored properties 能夠儲存值,而conmuted properties只提供getter與setter,利用stored properties來生成自己的值。個人感覺Computed properties更像方法,而不是傳統意義的屬性。但是這樣一個特性存在,使得我們更容易組織我們的程式碼。
延伸閱讀:computed property vs function
- Type Properties
Swift提供了語言級別定義類變數的方法。
In C and Objective-C, you define static constants and variables associated with a type as global static variables.In Swift, however, type properties are written as part of the type’s definition, within the type’s outer curly braces, and each type property is explicitly scoped to the type it supports.
在Objective-C中,我們只能通過單例,或者static變數加類方法來自己構造類變數:
1 2 3 4 5 6 7 8 9 10 11 12 |
@interface Model + (int) value; + (void) setValue:(int)val; @end @implementation Model static int value; + (int) value { @synchronized(self) { return value; } } + (void) setValue:(int)val { @synchronized(self) { value = val; } } @end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// Foo.h @interface Foo { } +(NSDictionary*) dictionary; // Foo.m +(NSDictionary*) dictionary { static NSDictionary* fooDict = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ // create dict }); return fooDict; } |
而在Swift中我們通過清晰的語法便能定義類變數:
通過static定義的類變數無法在子類重寫,通過class定義的類變數則可在子類重寫。
1 2 3 4 5 6 7 8 9 |
struct SomeStructure { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { return 1 } class var overrideableComputedTypeProperty: Int { return 107 } } |
同時利用類變數我們也有了更優雅的單例模式實現:
1 2 3 4 |
class singletonClass { static let sharedInstance = singletonClass() private init() {} // 這就阻止其他物件使用這個類的預設的'()'初始化方法 } |
Swift單例模式探索:The Right Way to Write a Singleton
延伸:
目前Swift支援的type propertis中的Stored Properties型別不是傳統意義上的類變數(class variable),暫時不能通過class 關鍵詞定義,通過static定義的類變數類似java中的類變數,是無法被繼承的,父類與子類的類變數指向的都是同一個靜態變數。
延伸閱讀: Class variables not yet supported
1 2 3 4 5 6 |
class SomeStructure { class var storedTypeProperty = "Some value." } //Swift 2.0 Error: Class stored properties not yet supported in classes |
通過編譯器丟擲的錯誤資訊,相信在未來的版本中會完善Type properties。
2.控制流
Swift與Objective-C在控制流的語法上關鍵詞基本是一致的,但是擴充套件性和安全性得到了很大的提升。
主要有三種型別的語句
- if,switch和新增的guard
- for,while
- break,continue
主要差異有:
關於if
語句裡的條件不再需要使用()包裹了。
1 2 3 4 |
let number = 23 if number < 10 { print("The number is small") } |
但是後面判斷執行的的程式碼必須使用{}包裹住。
為什麼呢,在C,C++等語言中,如果後面執行的語句只有語句,我們可以寫成:
1 2 3 |
int number = 23 if (number < 10) NSLog("The number is small") |
但是如果有時要在後面新增新的語句,忘記新增{},災難就很可能傳送。
:) 像蘋果公司自己就犯過這樣的錯誤。下面這段程式碼就是著名的goto fail錯誤,導致了嚴重的安全性問題。
1 2 3 4 5 6 7 |
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) goto fail; goto fail; // :)注意 這不是Python的縮減 ... other checks ... fail: ... buffer frees (cleanups) ... return err; |
:)
最終在Swift,蘋果終於在根源上消除了可能導致這種錯誤的可能性。
if 後面的條件必須為Boolean表示式
也就是不會隱式地與0進行比較,下面這種寫法是錯誤的,因為number並不是一個boolean表示式,number != 0才是。
1 2 3 |
int number = 0 if number{ } |
關於for
for迴圈在Swift中變得更方便,更強大。
得益於Swift新新增的範圍操作符…與…<
我們能夠將之前繁瑣的for迴圈:
1 2 3 4 |
for (int i = 1; i <= 5; i++) { NSLog(@"%d", i); } |
改寫為:
1 2 3 |
for index in 1...5 { print(index) } |
當然,熟悉Python的親們知道Python的range函式很方便,我們還能自由選擇步長。 像這樣:
1 2 3 4 |
>>> range(1,5) #代表從1到5(不包含5) [1, 2, 3, 4] >>> range(1,5,2) #代表從1到5,間隔2(不包含5) [1, 3] |
雖然在《The Swift Programming Language》裡面沒有提到類似的用法,但是在Swift中我們也有優雅的方法辦到。
1 2 3 |
for index in stride(from: 1, through: 5, by: 2) { print(index) }// through是包括5 |
然後對字典的遍歷也增強了.在Objective-c的快速列舉中我們只能對字典的鍵進行列舉。
1 2 3 4 |
NSString *key; for (key in someDictionary){ NSLog(@"Key: %@, Value %@", key, [someDictionary objectForKey: key]); } |
而在Swift中,通過tuple我們可以同時列舉key與value:
1 2 3 4 |
let dictionary = ["firstName":"Mango","lastName":"Fang"] for (key,value) in dictionary{ print(key+" "+value) } |
關於Switch
Swich在Swift中也得到了功能的增強與安全性的提高。
不需要Break來終止往下一個Case執行
也就是下面這兩種寫法是等價的。
1 2 3 4 5 6 7 8 9 10 |
let character = "a" switch character{ case "a": print("A") break case "b": print("B") break default: print("character") |
1 2 3 4 5 6 7 8 |
let character = "a" switch character{ case "a": print("A") case "b": print("B") default: print("character") |
這種改進避免了忘記寫break造成的錯誤,自己深有體會,曾經就是因為漏寫了break而花了一段時間去debug。
如果我們想不同值統一處理,使用逗號將值隔開即可。
1 2 3 4 |
switch some value to consider { case value 1,value 2: statements } |
Switch支援的型別
在OC中,Swtich只支援int型別,char型別作為匹配。
而在Swift中,Switch支援的型別大大的拓寬了。實際上,蘋果是這麼說的。
A switch statement supports any kind of data
這意味在開發中我們能夠能夠對字串,浮點數等進行匹配了。
之前在OC繁瑣的寫法就可以進行改進了:
1 2 3 4 5 6 7 8 9 |
if ([cardName isEqualToString:@"Six"]) { [self setValue:6]; } else if ([cardName isEqualToString:@"Seven"]) { [self setValue:7]; } else if ([cardName isEqualToString:@"Eight"]) { [self setValue:8]; } else if ([cardName isEqualToString:@"Nine"]) { [self setValue:9]; } |
1 2 3 4 5 6 7 8 9 10 |
switch carName{ case "Six": self.vaule = 6 case "Seven": self.vaule = 7 case "Eight": self.vaule = 8 case "Night": self.vaule = 9 } |
3.函式
對於在OC中,方法有兩種型別,類方法與例項方法。方法的組成由方法名,引數,返回值組成。
在Swift中函式的定義基本與OC一樣。
主要區別為:
- 通過func關鍵詞定義函式
- 返回值在->關鍵詞後標註
各舉一個類方法與例項方法例子。
1 2 |
+ (UIColor*)blackColor - (void)addSubview:(UIView *)view |
對應的swift版本
1 2 |
class func blackColor() -> UIColor //類方法, 通過 class func 關鍵詞宣告 func addSubview(view: UIView) //例項方法 |
改進:
在Swift中,函式的最重要的改進就是函式作為一等公民(first-class),和物件一樣可以作為引數進行傳遞,可以作為返回值,函數語言程式設計也成為了Swift支援的程式設計正規化。
In computer science, a programming language is said to have first-class functions if it treats functions as first-class citizens. Specifically, this means the language supports passing functions as arguments to other functions, returning them as the values from other functions, and assigning them to variables or storing them in data structures
讓我們初略感受一下函數語言程式設計的魅力:
舉一個例子,我們要篩選出一個陣列裡大於4的數字。
在OC中我們可能會用快速列舉來進行篩選。
1 2 3 4 5 6 7 |
NSArray *oldArray = @[@1,@2,@3,@4,@5,@6,@7,@8,@9,@10]; NSMutableArray *newArray; for (NSNumber* number in oldArray) { if ([number compare:@4] == NSOrderedDescending ) { [newArray addObject:number]; } } |
而在Swift中,我們用兩行程式碼解決這個問題:
1 2 |
let oldArray = [1,2,3,4,5,6,7,8,9,10] let newArray = oldArray.filter({$0 > 4}) |
進一步瞭解Swift的函數語言程式設計可以通過這篇優秀的部落格Functional Reactive Programming in Swift
- 個人覺得另外一個很棒的改進是:Default parameter values
在我們的專案中,經常會不斷進行功能的增添。為了新增特性,許多方法在開發的過程中不斷變動。舉一個例子:我們開始有一個tableViewCell,它的設定方法一開始簡單地需要一個Model引數:
1 |
func configureCellWithModel(Model: model) |
不久之後,我們想對部分Cell增添一個設定背景顏色的功能。方法需要再接收多一個引數:
1 |
func configureCellWithModel(Model: model,color:UIColor) |
這個時候方法改變,所以涉及到這些方法的地方都需要修改。給我們造成的困擾
一是:需要做許多重複修改的工作。
二是:無法做得很好的擴充套件和定製,有些地方的cell需要設定顏色,有些不需要。但是在OC裡,我們只能對所有的cell都賦值。你可能覺得我們可以寫兩個方法,一個接收顏色引數,一個不接受。但是我們知道這不是一個很好的解決方法,會造成冗餘的程式碼,維護起來也不方便。
而在Swift中,default parameter values的引入讓我們能夠這樣修改我們的程式碼:
1 |
func configureCellWithModel(Model: model,color:UIColor = UIColor.whiteColor()) |
這樣的改進能讓我們寫出的程式碼更具向後相容性,減少了我們的重複工作量,減少了犯錯誤的可能性。
4.類與初始化(Initializers)
- 檔案結構與訪問控制
在swift中,一個類不再分為interface(.h)與implementation(.m)兩個檔案實現,直接在一個.swift檔案裡進行處理。好處就是我們只需管理一份檔案,以往兩頭奔波修改的情況就得到解放了,也減少了標頭檔案與實現檔案不同步導致的錯誤。
這時我們會想到,那麼我們如何來定義私有方法與屬性呢,在OC中我們通過在class extension中定義私有屬性,在.m檔案定義私有方法。
而在Swift中,我們通過Access Control來進行控制。
properties, types, functions等能夠進行版本控制的統稱為實體。
- Public:可以訪問自己模組或應用中原始檔裡的任何實體,別人也可以訪問引入該模組中原始檔裡的所有實體。通常情況下,某個介面或Framework是可以被任何人使用時,你可以將其設定為public級別。
- Internal:可以訪問自己模組或應用中原始檔裡的任何實體,但是別人不能訪問該模組中原始檔裡的實體。通常情況下,某個介面或Framework作為內部結構使用時,你可以將其設定為internal級別。
- Private:只能在當前原始檔中使用的實體,稱為私有實體。使用private級別,可以用作隱藏某些功能的實現細節
一個小技巧,如果我們有一系列的私有方法,我們可以把它們組織起來,放進一個extension裡,這樣就不需要每個方法都標記private,同時也便於管理組織程式碼:
1 2 3 4 5 |
// MARK: Private private extension ViewController { func privateFunction() { } } |
- 建立物件與alloc和init
關於初始化,在Swift中建立一個物件的語法很簡潔:只需在類名後加一對圓括號即可。
1 |
var shape = Shape() |
而在Swift中,initializer也與OC有所區別,Swift的初始化方法不返回資料。而在OC中我們通常返回一個self指標。
Unlike Objective-C initializers, Swift initializers do not return a value. Their primary role is to ensure that new instances of a type are correctly initialized before they are used for the first time.
Swift的初始化方法讓我們只關注物件的初始化。之前在OC世界中為什麼要self = [super init]?。。這種問題得以避免。Swift幫助我們處理了alloc的過程。也讓我們的程式碼更簡潔明確。
而在Swift中,init也有了更嚴格的規則。
- 對於所有Stored Properties,都必須在物件被建立出來前設定好。也就是我們必須在init方法中賦好值,或是直接給屬性提供一個預設值。
如果有property可以被允許在初始出來時沒有值,也就是需要在建立出來後再賦值,或是在程式執行過程都可能不會被賦值。那麼這個property必須被宣告為optional型別。該型別的屬性會在init的時候初始化為nil.
- initializer嚴格分為Designated Initializer和Convenience Initializer 並且有語法定義。
而在Objective-C中沒有明確語法標記哪個初始化方式是convenience方法。關於Designated Initializer可參閱之前的:Objective-C 拾遺:designated initializer
1 2 3 4 5 6 7 |
init(parameters) { statements } convenience init(parameters) { statements } |
5.列舉與結構體
- 列舉
在Swift中,列舉是一等公民。(first-class)。能夠擁有方法,computed properties等以往只有類支援的特性。
在C中,列舉為每個成員指定一個整型值。而在Swift中,列舉更強大和靈活。我們不必給列舉成員提供一個值。如果我們想要為列舉成員提供一個值(raw value),我們可以用字串,字元,整型或浮點數型別。
1 2 3 4 5 6 7 8 |
enum CompassPoint { case North case South case East case West } var directionToHead = CompassPoint.West |
- 結構體
Struct在Swift中和類有許多相同的地方,可以定義屬性,方法,初始化方法,可通過extension擴充套件等。
不同的地方在於struct是值型別.在傳遞的過程中都是通過拷貝進行。
在這裡要提到在前面第一節處提到了String,Array和Dictionary在Swift是以值型別出現的。這背後的原因就是String,Array,Dictionary在Swift中是通過Struct實現的。而之前在Objective-C它們都是通過class實現的。
Swift中強大的Struct使得我們能夠更多與值型別打交道。Swift的值型別增強了不可變性(Immutabiliity)。而不可變性提升了我們程式碼的穩定性,多執行緒併發的安全性。
在WWDC2014《Advanced iOS Application Architecture and Patterns》中就有一節的標題是Simplify with immutability。
延伸閱讀:WWDC心得:Advanced iOS Application Architecture and Patterns
6.協議(Protocols)
語法:
在Objective-C中我們這麼宣告Protocol:
1 2 3 |
@protocol SampleProtocol <NSObject> - (void)someMethod; @end |
而在Swift中:
1 2 3 4 |
protocol SampleProtocol { func someMethod() } |
在Swift遵循協議:
1 2 3 4 |
class AnotherClass: SomeSuperClass, SampleProtocol { func someMethod() {} } |
那麼之前Objective-C的protocol中,我們可以標誌optional。那在Swift中呢?
遺憾的是,目前純Swift的protocol還不支援optional。但根據蘋果官方論壇的一位員工的回答,未來Swift是會支援的。
Optional methods in protocols are limited to @objc protocols only because we haven’t implemented them in native protocols yet. This is something we plan to support. We’ve gotten a number of requests for abstract/pure virtual classes and methods too.
— Joe Groff
Source: https://devforums.apple.com/message/1051431#1051431
protocol和delegate是緊密聯絡的。那麼我們在Swift中如何定義Delegate呢?
1 2 3 4 5 6 |
protocol MyDelegate : class { } class MyClass { weak var delegate : MyDelegate? } |
注意到上面的protocol定義後面跟著的class。這意味著該protocol只能被class型別所遵守。
並且只有遵守了class protocol的delegate才能定義為weak。這是因為在Swift中,除了class能夠遵守協議,列舉和結構同樣能夠遵守協議。而列舉和結構是值型別,不存在記憶體管理的問題。因此只需要class型別的變數宣告為weak即可。
利用Swift的optional chaining,我們能夠很方便的檢查delegate是否為Nil,是否有實現某個方法:
以前我們要在Objective-C這樣檢查:
1 2 3 |
if (self.dataSource && [self.dataSource respondsToSelector:@selector(titleForSegmentAtIndex:)]) { thisSegmentTitle = [self.dataSource titleForSegmentAtIndex:index]; } |
在Swift中,非常的優雅簡潔。
1 2 |
if let thisSementTitle = dataSource?.titleFroSegmentAtIndex?(index){ } |
新特性:
在Swift中,protocol變得更加強大,靈活:
- class,enum,structure都可以遵守協議。
- Extension也能遵守協議。利用它,我們不需要繼承,也能夠讓系統的類也遵循我們的協議。
例如:
1 2 3 4 5 6 7 8 9 |
protocol myProtocol { func hello() -> String } extension String:myProtocol{ func hello() -> String { return "hello world!" } } |
我們還能夠用這個特性來組織我們的程式碼結構,如下面的程式碼所示,將UITableViewDataSource的實現移到了Extension。使程式碼更清晰。
1 2 3 4 |
// MARK: - UITableViewDataSource extension MyViewcontroller: UITableViewDataSource { // table view data source methods } |
3. Protocol Oriented Programming
隨著Swift2.0的釋出,面向協議程式設計正式也加入到了Swift的程式設計正規化。Cool.
這種程式設計方式通過怎樣的語法特性支撐的呢?
那就是我們能夠對協議進行擴充套件,也就是我們能夠提供協議的預設實現,能夠為協議新增新的方法與實現。
用前面的myProtocol為例子,我們在Swift裡這樣為它提供預設實現。
1 2 3 4 5 |
extension myProtocol{ func hello() -> String { return "hello world!" } } |
我們還能對系統原有的protocol進行擴充套件,大大增強了我們的想象空間。Swift2.0的實現也有很多地方用extension protocol的形式進行了重構。
面向協議程式設計能夠展開說很多,在這裡這簡單地介紹了語法。有興趣的朋友可以參考下面的資料:
Session 408: Protocol-Oriented Programming in Swift
IF YOU’RE SUBCLASSING, YOU’RE DOING IT WRONG.
7.Swift與Cocoa
一門語言的的強大與否,除了自身優秀的特性外,很大一點還得依靠背後的框架。Swift直接採用蘋果公司經營了很久的Cocoa框架。現在我們來看看使用Swift和Cocoa互動一些需要注意的地方。
1. id與AnyObject
在Swift中,沒有id型別,Swift用一個名字叫AnyObject的protocol來代表任意型別的物件。
1 2 |
id myObject = [[UITableViewCell alloc]init]; var myObject: AnyObject = UITableViewCell() |
我們知道id的型別直到執行時才能被確定,如果我們向一個物件傳送一條不能響應的訊息,就會導致crash。
我們可以利用Swift的語法特性來防止這樣的錯誤:
1 |
myObject.method?() |
如果myObject沒有這個方法,就不會執行,類似檢查delegate是否有實現代理方法。
在Swift中,在AnyObject上獲取的property都是optional的。
2. 閉包
OC中的block在Swift中無縫地轉換為閉包。函式實際上也是一種特殊的閉包。
3. 錯誤處理
之前OC典型的錯誤處理步驟:
1 2 3 4 5 6 7 |
NSFileManager *fileManager = [NSFileManager defaultManager]; NSURL *URL = [NSURL fileURLWithPath:@"/path/to/file"]; NSError *error = nil; BOOL success = [fileManager removeItemAtURL:URL error:&error]; if (!success) { NSLog(@"Error: %@", error.domain); } |
在Swift中:
1 2 3 4 5 6 7 |
let fileManager = NSFileManager.defaultManager() let URL = NSURL.fileURLWithPath("/path/to/file") do { try fileManager.removeItemAtURL(URL) } catch let error as NSError { print("Error: \(error.domain)") } |
4. KVO。
Swift支援KVO。但是KVO在Swift,個人覺得是不夠優雅的,KVO在Swift只限支援繼承NSObject的類,有其侷限性,在這裡就不介紹如何使用了。
網上也出現了一些開源庫來解決這樣的問題。有興趣可以參考一下:
KVO 在OS X中有Binding的能力,也就是我們能夠將兩個屬性繫結在一起,一個屬性變化,另外一個屬性也會變化。對與UI和資料的同步更新很有幫助,也是MVVM架構的需求之一。之前已經眼饞這個特性很久了,雖然Swift沒有原生帶來支援,Swift支援的泛型程式設計給開源界帶來許多新的想法。下面這個庫就是實現binding的效果。
8.總結
到這裡就基本介紹完Swift當中最基本的語法和與Objective-C的對比和改進。
事實上Swift的世界相比OC的世界還有很多新鮮的東西等待我們去發現和總結,Swift帶來的多正規化程式設計也將給我們程式設計的架構和程式碼的組織帶來更來的思考。而Swift也是一個不斷變化,不斷革新的語言。相信未來的發展和穩定性會更讓我們驚喜。這篇文章也將隨著Swift的更新而不斷更新,同時限制篇幅,突出重點。
希望這篇文章能夠給各位同行的小夥伴們快速瞭解和學習Swift提供一點幫助。有疏漏錯誤的地方歡迎直接提出。感謝。
參考: