iOS 開發者指南:從 Objective-C 到學習 Swift

已發表: 2022-03-11

2008 年,Apple 宣布並發布了 iPhone SDK 2.0。 這一事件開啟了軟件開發的另一場革命,新一代的開發者誕生了。 他們現在被公認為 iOS 開發者。

其中許多開發人員以前從未使用過 Objective-C,這是 Apple 向他們提出的第一個挑戰。 儘管語法和手動內存管理不熟悉,但它還是取得了巨大的成功,幫助 App Store 填充了數以萬計的應用程序。 Apple 在每個版本中不斷改進 Objective-C,添加塊和文字,通過自動引用計數簡化內存管理,以及許多其他表明現代編程語言的特性。

經過 6 年的改進並致力於 Objective-C,Apple 決定向開發人員提出另一個挑戰。 再一次,iOS 開發者需要學習一門新的編程語言: Swift 。 Swift 移除了不安全的指針管理並引入了強大的新功能,同時保持與 Objective-C 和 C 的交互。

Swift 1.0 已經是一個穩定而強大的開發平台,它肯定會在未來幾年以有趣的方式發展。 現在是開始探索這種新語言的絕佳時機,因為它顯然是 iOS 開發的未來。

本教程的目的是讓 Objective-C 開發人員快速了解 Swift 語言的新特性,幫助您邁出下一步並開始在日常工作中採用 Swift。 我不會花太多時間解釋 Objective-C,我會假設你熟悉 iOS 開發。

Swift 改變了 Objective-C iOS 開發者的遊戲規則。

嘗試 Swift 與 Objective-C

為了開始探索 Swift,您只需從 App Store 下載 XCode 並創建一個 Playground 進行實驗。 本文中提到的所有示例都是以這種方式完成的。

Apple 的 Swift 主頁是學習 Swift 編程的最佳參考。 你會發現它非常寶貴,在你完全跟上 Swift 開發的速度之前,我相信你會經常回到這裡。

變量和常量

在 Swift 中聲明一個變量是使用var關鍵字完成的。

 var x = 1 var s = "Hello"

您會注意到兩個變量xs的類型不同。 x是一個整數,而s是一個字符串。 Swift 是一種類型安全的語言,它會從賦值中推導出變量類型。 如果您希望使您的代碼更具可讀性,您可以選擇註釋變量的類型:

 var y: Int y = 2

常量是相似的,但是你使用let而不是var來聲明它們。 常量的值不需要在編譯時就知道,但你必須給它賦值一次。

 let c1 = 1 // Constant known at compile time var v = arc4random() let c2 = v // Constant known only at run time

顧名思義,它們是不可變的,所以下面的代碼會導致編譯時錯誤。

 let c = 1 c = 3 // error

其他類型也可以聲明為常量。 例如,下面的代碼將一個數組聲明為一個常量,如果你試圖修改其中的任何一個元素,Swift 編譯器就會報錯:

 var arr2 = [4, 5, 6] arr2[0] = 8 print (arr2) // [8, 5, 6] let arr = [1, 2, 3] a[0] = 5 // error

可選的

常量在聲明時需要初始化,變量在使用前需要初始化。 那麼Objective-C nil等價物在哪裡呢? Swift 引入了可選值。 可選值可以有一個值或為nil 。 如果您查看以下代碼,您會注意到x被分配了一個Optional2014 。 這意味著 Swift 編譯器知道x也可能是nil

 var s = "2014" var x = s.toInt() print(x) // Optional(2014)

如果您對此代碼進行更改並將值"abc"分配給無法轉換為整數s ,您會注意到x現在是nil

 var s = "abc" var x = s.toInt() print(x) // nil

toInt()函數的返回類型是Int? ,這是一個可選的 Int 。 讓我們嘗試在x上調用標準函數:

 var x = "2014".toInt() print(x.successor()) // error

編譯器發出錯誤信號,因為x可選的並且可能是 nil 。 我們必須先測試x ,並確保在實數上調用successor函數,而不是nil值:

 var x = "2014".toInt() if x != nil { print(x!.successor()) // 2015 }

請注意,我們必須通過附加感嘆號 (!)來解開x 。 當我們確定x包含一個值時,我們可以訪問它。 否則我們會得到一個運行時錯誤。 我們也可以做 Swift 所稱的可選綁定,將可選變量轉換為非可選變量

let x = "123".toInt() if let y = x { print(y) }

if語句中的代碼僅在x有值時才會執行,並將其分配給y 。 請注意,我們不需要解包y ,它的類型不是可選的,因為我們知道x不是nil

查看 Apple 的 Swift 教程,了解更多關於可選項和不錯的特性(如可選鏈)的詳細信息

字符串插值

在 Objective-C 中格式化字符串通常使用stringWithFormat:方法:

 NSString *user = @"Gabriel"; int days = 3; NSString *s = [NSString stringWithFormat:@"posted by %@ (%d days ago)", user, days];

Swift 有一個稱為字符串插值的功能可以做同樣的事情,但它更緊湊且更易於閱讀:

 let user = "Gabriel" let days = 3 let s = "posted by \(user) \(days) ago"

您還可以使用表達式:

 let width = 2 let height = 3 let s = "Area for square with sides \(width) and \(height) is \(width*height)"

要了解有關 Swift 的字符串插值和其他新功能的更多信息,請訪問此處。

職能

Swift 中的函數定義與 C 不同。示例函數定義如下:

 func someFunction(s:String, i: Int) -> Bool { ... // code }

Swift 函數是一流的類型。 這意味著您可以將函數分配給變量,將它們作為參數傳遞給其他函數,或者使它們返回類型:

 func stringLength(s:String) -> Int { return countElements(s) } func stringValue(s:String) -> Int { if let x = s.toInt() { return x } return 0 } func doSomething(f:String -> Int, s:String) -> Int { return f(s).successor() } let f1 = stringLength let f2 = stringValue doSomething(f1, "123") // 4 doSomething(f2, "123") // 124

同樣,Swift 推斷出f1f2的類型( String -> Int ),儘管我們可以明確定義它們:

 let f1:String -> Int = stringLength

函數還可以返回其他函數:

 func compareGreaterThan(a: Int, b: Int) -> Bool { return a > b } func compareLessThan(a: Int, b: Int) -> Bool { return a < b } func comparator(greaterThan:Bool) -> (Int, Int) -> Bool { if greaterThan { return compareGreaterThan } else { return compareLessThan } } let f = comparator(true) println(f(5, 9))

可以在此處找到 Swift 中的函數指南。

枚舉

Swift 中的枚舉比 Objective-C 中的強大得多。 作為 Swift 結構,它們可以有方法,並按值傳遞:

 enum MobileDevice : String { case iPhone = "iPhone", Andro, WP8 = "Windows Phone8", BB = "BlackBerry" func name() -> String { return self.toRaw() } } let m = MobileDevice.Android print(m.name()) // "Android"

與 Objective-C 不同,Swift 枚舉除了整數之外,還可以將字符串、字符或浮點數作為每個成員的值。 方便的toRaw()方法返回分配給每個成員的值。

枚舉也可以參數化:

 enum Location { case Address(street:String, city:String) case LatLon(lat:Float, lon:Float) func description() -> String { switch self { case let .Address(street, city): return street + ", " + city case let .LatLon(lat, lon): return "(\(lat), \(lon))" } } } let loc1 = Location.Address(street: "2070 Fell St", city: "San Francisco") let loc2 = Location.LatLon(lat: 23.117, lon: 45.899) print(loc1.description()) // "2070 Fell St, San Francisco" print(loc2.description()) // "(23.117, 45.988)"

有關枚舉的更多信息,請參見此處。

元組

元組將多個值組合成一個複合值。 元組中的值可以是任何類型,並且不必彼此具有相同的類型。

 let person = ("Gabriel", "Kirkpatrick") print(person.0) // Gabriel

您還可以命名單個元組元素:

 let person = (first: "Gabriel", last: "Kirkpatrick") print(person.first)

對於需要返回多個值的函數,元組作為返回類型非常方便:

 func intDivision(a: Int, b: Int) -> (quotient: Int, remainder: Int) { return (a/b, a%b) } print(intDivision(11, 3)) // (3, 2) let result = intDivision(15, 4) print(result.remainder) // 3

與 Objective-C 不同,Swift 支持在 switch 語句中進行模式匹配:

 let complex = (2.0, 1.1) // real and imaginary parts switch complex { case (0, 0): println("Number is zero") case (_, 0): println("Number is real") default: println("Number is imaginary") }

在第二種情況下,我們不關心數字的實部,所以我們使用_來匹配任何內容。 您還可以檢查每種情況下的附加條件。 為此,我們需要將模式值綁定到常量:

 let complex = (2.0, 1.1) switch complex { case (0, 0): println("Number is zero") case (let a, 0) where a > 0: println("Number is real and positive") case (let a, 0) where a < 0: println("Number is real and negative") case (0, let b) where b != 0: println("Number has only imaginary part") case let (a, b): println("Number is imaginary with distance \(a*a + b*b)") }

請注意我們如何只需要綁定我們將在比較或 case 語句中使用的值。

要閱讀有關元組的更多信息,請轉到此處。

類和結構

與 Objective-C 不同,Swift 不需要您為自定義類和結構創建單獨的接口和實現文件。 在學習 Swift 時,您將學習在單個文件中定義一個類或結構,並且該類或結構的外部接口會自動提供給其他代碼使用。

定義類

類定義非常簡單:

 class Bottle { var volume: Int = 1000 func description() -> String { return "This bottle has \(volume) ml" } } let b = Bottle() print(b.description())

如您所見,聲明和實現在同一個文件中。 Swift 不再使用頭文件和實現文件。 讓我們為示例添加一個標籤:

 class Bottle { var volume: Int = 1000 var label:String func description() -> String { return "This bottle of \(label) has \(volume) ml" } }

編譯器會抱怨,因為 label 是一個非可選變量,並且在實例化 Bottle 時不會保存值。 我們需要添加一個初始化器:

 class Bottle { var volume: Int = 1000 var label:String init(label:String) { self.label = label } func description() -> String { return "This bottle of \(label) has \(volume) ml" } }

或者,我們可以對不需要初始化的屬性使用Optional類型。 在以下示例中,我們將volume Optional Integer

 class Bottle { var volume: Int? var label:String init(label:String) { self.label = label } func description() -> String { if self.volume != nil { return "This bottle of \(label) has \(volume!) ml" } else { return "A bootle of \(label)" } } }

結構

Swift 語言也有structs ,但它們比 Objective-C 靈活得多。 以下代碼教程定義了一個struct

 struct Seat { var row: Int var letter:String init (row: Int, letter:String) { self.row = row self.letter = letter } func description() -> String { return "\(row)-\(letter)" } }

與 Swift 中的類一樣,結構可以具有方法、屬性、初始化器並符合協議。 類和結構的主要區別在於類是通過引用傳遞的,而結構是通過值傳遞的

這個例子演示了通過引用傳遞類:

 let b = Bottle() print(b.description()) // "b" bottle has 1000 ml var b2 = b b.volume = 750 print(b2.description()) // "b" and "b2" bottles have 750 ml

如果我們用struct嘗試類似的情況,你會注意到變量是按值傳遞的:

 var s1 = Seat(row: 14, letter:"A") var s2 = s1 s1.letter = "B" print(s1.description()) // 14-B print(s2.description()) // 14-A

我們什麼時候應該使用struct什麼時候應該使用class ? 與在 Objective-C 和 C 中一樣,當您需要對幾個值進行分組並期望它們被複製而不是引用時,請使用結構。 例如,複數、2D 或 3D 點或 RGB 顏色。

類的實例傳統上稱為對象。 然而,Swift 的類和結構在功能上比其他語言更接近,許多功能可以應用於類或結構類型的實例。 正因為如此,Swift 引用中使用的更通用的術語是instance ,它適用於這兩個中的任何一個。

在這裡學習 Swift 類和結構的基礎知識。

特性

正如我們之前看到的,Swift 中的屬性是在類或結構定義中使用var關鍵字聲明的。 我們也可以使用let語句來聲明常量。

 struct FixedPointNumber { var digits: Int let decimals: Int } var n = FixedPointNumber(digits: 12345, decimals: 2) n.digits = 4567 // ok n.decimals = 3 // error, decimals is a constant

還要記住,類屬性是強引用的,除非您在它們前面加上weak關鍵字。 然而,弱非可選屬性有一些微妙之處,因此請閱讀 Apple 的 Swift 指南中的自動引用計數章節。

計算屬性

計算屬性實際上並不存儲值。 相反,它們提供了一個 getter 和一個可選的 setter 來間接檢索和設置其他屬性和值。

以下代碼提供了計算值sign的示例:

 enum Sign { case Positive case Negative } struct SomeNumber { var number:Int var sign:Sign { get { if number < 0 { return Sign.Negative } else { return Sign.Positive } } set (newSign) { if (newSign == Sign.Negative) { self.number = -abs(self.number) } else { self.number = abs(self.number) } } } }

我們也可以通過實現一個 getter 來定義只讀屬性:

 struct SomeNumber { var number:Int var isEven:Bool { get { return number % 2 == 0 } } }

在 Objective-C 中,屬性通常由實例變量支持,由編譯器顯式聲明或自動創建。 另一方面,在 Swift 中,屬性沒有對應的實例變量。 也就是說,不能直接訪問屬性的後備存儲。 假設我們在 Objective-C 中有這個

// .h @interface OnlyInitialString : NSObject @property(strong) NSString *string; @end // .m @implementation OnlyInitialString - (void)setString:(NSString *newString) { if (newString.length > 0) { _string = [newString substringToIndex:1]; } else { _string = @""; } } @end

因為,在 Swift 中,計算屬性沒有後備存儲,我們需要做這樣的事情:

 class OnlyInitialString { var initial:String = "" var string:String { set (newString) { if countElements(newString) > 0 { self.initial = newString.substringToIndex(advance(newString.startIndex, 1)) } else { self.initial = "" } } get { return self.initial } } }

屬性在這裡有更詳細的解釋

待續

在 Swift 中還有許多更重要的新東西需要學習,例如泛型、與 Objective-C 庫的交互、閉包、可選鍊和運算符重載。 單一的教程無法完整地描述一門新語言,但我毫不懷疑關於學習 Swift 編程的更多內容。 但是,我相信這篇快速閱讀將幫助許多沒有時間和學習 Swift 語言細節的 Objective-C 開發人員走上正軌,讓 Swift 鳥把他們帶到一個新的高度。