從Objective-C到Swift——Swift糖果
Swift帶來(lái)很多確實(shí)很棒的特性,使得很難再回到Objective-C。主要的特性是安全性,不過(guò)這也被看成是一種額外副作用。
帶類型接口的強(qiáng)型別
Swift有強(qiáng)型別,這意味著除非你要求,不然Swift不會(huì)為你做類型之間的轉(zhuǎn)換。所以,例如你無(wú)法把Int型賦給Double型。你不得不首先轉(zhuǎn)換類型:
- let i: Int = 42
- let d: Double = Double(i)
或者你必須給Double類擴(kuò)展一個(gè)方法用來(lái)轉(zhuǎn)換Int型:
- extension Double {
- func __convert(i: Int) -> Double {
- return Double(i)
- }
- }
- let another_d: Double = i
強(qiáng)型別對(duì)于安全性是非常非常有利的。但是,如果它沒(méi)有為你給類型接口添加許多類型信息的話,它也可能變成有一點(diǎn)令人生畏的事情,有點(diǎn)像是在寫腳本語(yǔ)言。
- let ary = ["Hello", "world"] // NOTE: 'ary' is of type String[] or Array<String>
- for s in ary { // NOTE: 's' is of type String
- print(s + " ")
- }
如果你想要?jiǎng)?chuàng)建一個(gè)包含很多類型(無(wú)共同祖先)的數(shù)組,你應(yīng)該用枚舉(它可以包含值,見如下)。如果你想要它能夠包含所有值,你可以用Any型。如果想讓它包含任何Objective-C的類型,那就用AnyObject型。
請(qǐng)注意類型接口不會(huì)在申明函數(shù)的時(shí)候?yàn)槟闾砑宇愋?。你必須明確地說(shuō)明你所申明函數(shù)的類型。
Blocks
Swift 中的Blocks很像Objective-C中的Blocks, 不過(guò)有兩點(diǎn)不同: 類型推斷和避免weakify dance.
對(duì)于類型推斷,你不必每次寫block時(shí)都包含完整類型信息:
- sort([2,1,3], {
- (a: Int, b: Int) -> Bool in return a < b
- })
- // Using Type Inference
- // Using the Trailing Closures feature
- sort([2,1,3]) {
- a, b in return a < b
- }
- // Implicit 'return' for single-expression blocks
- sort([2,1,3]) { a,b in a<b }
- // Shorthand Argument Names
- sort([2,1,3]) { $0 < $1 }
- // Operators are functions, and functions are blocks too!
- let sorted: Int[] = sort([2,1,3], <)
訪問(wèn) Closures 了解更多blocks信息。
除此之外,Objectvie-C 的weakify dance有點(diǎn)容易,只需在block的開始處加上 [unowned self] 或 [weak self] 即可。
- class CallbackTest {
- var i = 5
- var callback: (Int -> ())? // NOTE: The optional callback takes an Int
- deinit { // NOTE: This is like -dealloc in Objective-C
- println("Deinit")
- }
- }
- var obj = CallbackTest()
- obj.callback = {
- [unowned obj] // NOTE: Without this, deinit() would never be invoked!
- a in
- obj.i = a
- }
請(qǐng)注意Introduction post文章中介紹了Optional(像上面的callback)。
請(qǐng)參考 ARC 章節(jié)來(lái)了解更多關(guān)于Swift中ARC的信息。
強(qiáng)勁的Enumerations
Swift中的枚舉比Objective-C中的有很大提高。
修復(fù) Enums
Apple一直提倡顯示的提供枚舉類型的大小,不過(guò)被Objective-C搞亂了:
- // Apple recommended enum definition in Objective-C
- typedef NS_ENUM(NSInteger, UIViewAnimationCurve) {
- UIViewAnimationCurveEaseInOut,
- UIViewAnimationCurveEaseIn,
- UIViewAnimationCurveEaseOut,
- UIViewAnimationCurveLinear
- };
- // Previous Apple recommended enum definition in Objective-C. No link between
- // enum values and theUIViewAnimationCurve type.
- typedef enum {
- UIViewAnimationCurveEaseInOut,
- UIViewAnimationCurveEaseIn,
- UIViewAnimationCurveEaseOut,
- UIViewAnimationCurveLinear
- };
- typedef NSInteger UIViewAnimationCurve;
- // Traditional enum definition in Objective-C. No explicit fixed size.
- typedef enum {
- UIViewAnimationCurveEaseInOut,
- UIViewAnimationCurveEaseIn,
- UIViewAnimationCurveEaseOut,
- UIViewAnimationCurveLinear
- } UIViewAnimationCurve;
Swift中的修復(fù):
- enum UIViewAnimationCurve : Int {
- case EaseInOut
- case EaseIn
- case EaseOut
- case Linear
- }
拓展Enums
Enums 在Swift中更進(jìn)一步,只作為一個(gè)獨(dú)立的選項(xiàng)列表。你可以添加方法(以及計(jì)算屬性):
- enum UIViewAnimationCurve : Int {
- case EaseInOut
- case EaseIn
- case EaseOut
- case Linear
- func typeName() -> String {
- return "UIViewAnimationCurve"
- }
- }
使用類型拓展,你可以向枚舉中添加任何你想要的方法:
- extension UIViewAnimationCurve {
- func description() -> String {
- switch self {
- case EaseInOut:
- return "EaseInOut"
- case EaseIn:
- return "EaseIn"
- case EaseOut:
- return "EaseOut"
- case Linear:
- return "Linear"
- }
- }
- }
#p#
向Enums中添加值
Swift中的枚舉跟進(jìn)一步,允許每一個(gè)獨(dú)立的選項(xiàng)都有一個(gè)對(duì)應(yīng)的值:
- enum Shape {
- case Dot
- case Circle(radius: Double) // Require argument name!
- case Square(Double)
- case Rectangle(width: Double, height: Double) // Require argument names!
- func area() -> Double {
- switch self {
- case Dot:
- return 0
- case Circle(let r): // Assign the associated value to the constant 'r'
- return π*r*r
- case Square(let l):
- return l*l
- case Rectangle(let w, let h):
- return w*h
- }
- }
- }
- var shape = Shape.Dot
- shape = .Square(2)
- shape = .Rectangle(width: 3, height: 4) // Argument names required
- shape.area()
如果你喜歡,你可以把它當(dāng)做一個(gè)安全的union類型?;蛘咧挥妹杜e應(yīng)該做的事情。
Enumerations 文章介紹了更多關(guān)于Apple對(duì)此的看法。
Swift Switch語(yǔ)句
就像你看到的,Swift中switch語(yǔ)句有很多優(yōu)化。
隱式的fall-through行為已經(jīng)改為了顯示的:
- var (i, j) = (4, -1) // Assign (and create) two variables simultaneously
- switch i {
- case 1:
- j = 1
- case 2, 3: // The case for both 2 and 3
- j = 2
- case 4:
- j = 4
- fallthrough
- case 5:
- j++
- default:
- j = Int.max // The Swift version of INT_MAX
- }
就像前面看到的,Switch 語(yǔ)句可以訪問(wèn)枚舉的關(guān)聯(lián)值,不過(guò)它還可以做更多:
- var tuple: (Int, Int) // Did I mention that Swift has tuples? :-)
- var result: String
- tuple = (1,3)
- switch tuple {
- case (let x, let y) where x > y:
- result = "Larger"
- case (let x, let y) where x < y:
- result = "Smaller"
- default:
- result = "Same"
- }
甚至可以使用String:
- var s: String = "Cocoa"
- switch s {
- case "Java": s = "High caffeine"
- case "Cocoa": s = "High sugar"
- case "Carbon": s = "Lots of bubbles"
- default: ()
- }
另外,如果你覺(jué)得他可以使你的代碼更可讀,你可以重載~=操作符來(lái)改變switch語(yǔ)句的行為。
- func ~=(pattern: String, str: String) -> Bool {
- return str.hasPrefix(pattern)
- }
- var s = "Carbon"
- switch s {
- case "J": s = "High caffeine"
- case "C": s = "No caffeine"
- default: ()
- }
你可以從 Conditional Statements 這篇文章中了解更多關(guān)于switch語(yǔ)句的知識(shí)。
類與結(jié)構(gòu)體
類似于C++,Swift的類與結(jié)構(gòu)體初看是一樣的:
- class Apple {
- var color = "green" // Property declaration
- init() {} // Default initializer
- init(_ color: String) { // '_' means no argument name
- self.color = color
- }
- func description() -> String {
- return "apple of color \(color)"
- }
- func enripen() {
- color = "red"
- }
- }
- struct Orange {
- var color = "green"
- init() {}
- init(_ color: String) {
- self.color = color
- }
- func description() -> String {
- return "orange of color \(color)"
- }
- mutating func enripen() { // NOTE: 'mutating' is required
- color = "orange"
- }
- }
- var apple1 = Apple()
- var apple2 = apple1 // NOTE: This references the same object!
- apple1.enripen()
- apple2.description() // Result: "apple of color red"
- var orange1 = Orange()
- var orange2 = orange1 // NOTE: This makes a copy!
- orange1.enripen()
- orange2.description() // Result: "orange of color green"
主要的不同點(diǎn)在于類是(和塊相似的)引用類型,而結(jié)構(gòu)體是(和枚舉相似的)數(shù)值類型。所以兩個(gè)變量能夠指向同一個(gè)(類的)對(duì)象,而把一個(gè)結(jié)構(gòu)體賦給 另外一個(gè)變量則必須做一個(gè)此結(jié)構(gòu)體的(緩慢的)拷貝。關(guān)鍵詞'mutating'告訴調(diào)用者enripen()方法不能被常結(jié)構(gòu)體調(diào)用。把一個(gè)常引用 mutating給一個(gè)類對(duì)象則沒(méi)有問(wèn)題。
Swift中大多數(shù)內(nèi)建類型實(shí)際上都是結(jié)構(gòu)體。甚至Int型也是。通過(guò)點(diǎn)擊Cmd你能夠看到內(nèi)建類型的申明,比如Int型的Swift(或者 Playground)源碼。數(shù)組和詞典類型也是結(jié)構(gòu)體,但是數(shù)組在某些方面表現(xiàn)得像是引用類型:賦值數(shù)組并不拷貝每一個(gè)元素,實(shí)際上你可以更新常數(shù)組只 要元素的個(gè)數(shù)保持不變。
- let array1 = [1, 2, 3]
- let array2 = array1 // A copy, but references the same elements!
- array1[1] = 5 // Legal, as it doesn't modify the struct but a referenced element
- array2 // Result: [1, 5, 3]
在蘋果的文檔中,你可以讀到更多關(guān)于Collection Types的內(nèi)容。
#p#
對(duì)象的生命周期
另一個(gè)類與結(jié)構(gòu)體的不同是類可以被子類化。 雷和結(jié)構(gòu)體都可以被拓展,并且實(shí)現(xiàn)protocol,但是只用類可以繼承其他類。
- class Pineapple : Apple {
- init(color: String) {
- super.init(color)
- }
- convenience init() {
- self.init("green")
- }
- convenience init(ripe: Bool) {
- self.init()
- if ripe {
- color = "yellow"
- } else {
- color = "green"
- }
- }
- deinit {
- println("Pineapple down")
- }
- override func description() -> String {
- return "pine" + super.description()
- }
- override func enripen() {
- color = "yellow"
- }
- }
就像你看到的,Swift為繼承添加了一點(diǎn)更有趣的需要學(xué)習(xí)的東西。對(duì)于初學(xué)者來(lái)說(shuō),你需要清除你覆蓋父類中某個(gè)方法的意圖。如果你想阻止子類覆蓋一些東西,你可以在一個(gè)單獨(dú)聲明或整個(gè)類的前面加上@final屬性。閱讀 Apple’s documentation了解更多。
初始化
Swift的對(duì)象分兩步進(jìn)行初始化: 首先對(duì)象必須是有效的,然后它能被替換。
- class ChildShoe {
- var size: Double // Uninitialized properties are not allowed unless taken care of in init()
- init(foot_size: Double) {
- size = foot_size // First make the object valid
- addGrowthCompensation()
- }
- func addGrowthCompensation() {
- size++
- }
- }
使對(duì)象有效必須要調(diào)用一個(gè)超級(jí)類指定的init()方法。類可以同時(shí)擁有指定的以及便捷的(用關(guān)鍵詞'convenience'標(biāo)記)初始化方法。 便捷初始化方法調(diào)用同一個(gè)類中的其他初始化方法(最終還是一個(gè)指定的初始化方法),而指定的初始化方法調(diào)用超級(jí)類的初始化方法。
如果你給所有的超級(jí)類指定初始化方法添加初始化方法,你的類也會(huì)自動(dòng)繼承所有便捷初始化方法。如果沒(méi)有添加任何指定的初始化方法,你的類則繼承超級(jí)類的所有(指定的和便捷的)初始化方法。
深入閱讀請(qǐng)參見Initialization。
類型轉(zhuǎn)換
類之間的轉(zhuǎn)換,特別是向下轉(zhuǎn)換,你可以使用"is","as?"和"as"關(guān)鍵詞:
- let apple: Apple = Pineapple()
- let exotic: Bool = apple is Pineapple
- let pineappleOption: Pineapple? = apple as? Pineapple
- let pineapple: Pineapple = apple as Pineapple // NOTE: Throws if not!
- if let obj = apple as? Pineapple { // If executed, 'obj' is a Pineapple
- "sweet"
- }
想了解更多這方面內(nèi)容請(qǐng)參見Type Casting一章.
泛型
泛型是Swift的一個(gè)加分的特點(diǎn)。他們看起來(lái)有一點(diǎn)像C++里面的模板,但是有更強(qiáng)的型別,也更簡(jiǎn)單(更簡(jiǎn)單使用,功能稍遜)。
- 01 // Mark both Int and Double as being convertible to a Double using the '+' prefix
- 02 protocol DoubleConvertible {
- 03 @prefix func +(v: Self) -> Double
- 04 }
- 05 @prefix func +(v: Int) -> Double { return Double(v) }
- 06 extension Double: DoubleConvertible {}
- 07 extension Int: DoubleConvertible {}
- 08 // NOTE: Repeat this for all Int*, UInt*, and the Float type
- 09
- 10 // The traits of a generalized point
- 11 protocol PointTraits {
- 12 typealias T
- 13 class var dimensions: Int { get }
- 14 func getCoordinate(dimension: Int) -> T
- 15 }
- 16
- 17 // Generalized Pythagoras
- 18 struct Pythagoras<P1: PointTraits, P2: PointTraits where P1.T: DoubleConvertible, P2.T: DoubleConvertible> {
- 19 static func apply(a: P1, b: P2, dimensions: Int) -> Double {
- 20 if dimensions == 0 {
- 21 return 0
- 22 }
- 23 let d: Double = +a.getCoordinate(dimensions-1) - +b.getCoordinate(dimensions-1) // NOTE: '+' to convert to Double
- 24 return d * d + apply(a, b: b, dimensions: dimensions-1)
- 25 }
- 26 static func apply(a: P1, b: P2) -> Double {
- 27 let dimensions = P1.dimensions
- 28 assert(P2.dimensions == dimensions)
- 29 return apply(a, b: b, dimensions: dimensions)
- 30 }
- 31 };
- 32
- 33 import func Foundation.sqrt // NOTE: You can import a typealias­, struct­, class­, enum­, protocol­, var­, or func only
- 34
- 35 func distance<P1: PointTraits, P2: PointTraits where P1.T: DoubleConvertible, P2.T: DoubleConvertible>(a: P1, b: P2) -> Double {
- 36 assert(P1.dimensions == P2.dimensions)
- 37 return sqrt(Pythagoras.apply(a, b: b));
- 38 }
- 39
- 40 // A generalized 2D point
- 41 struct Point2D<Number> : PointTraits {
- 42 static var dimensions: Int { return 2 }
- 43 var x: Number, y: Number
- 44 func getCoordinate(dimension: Int) -> Number { return dimension == 0 ? x : y } // NOTE: The typealias T is inferred
- 45 }
- 46 let a = Point2D(x: 1.0, y: 2.0)
- 47 let b = Point2D(x: 5, y: 5)
- 48 Pythagoras.apply(a, b: b) // NOTE: Methods require all argument names, except the first
- 49 distance(a, b) // NOTE: Functions do not require argument names
- 50
- 51 // UIColor
- 52 extension UIColor : PointTraits {
- 53 class var dimensions: Int { return 4 }
- 54 func getCoordinate(dimension: Int) -> Double {
- 55 var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
- 56 getRed(&red, green: &green, blue: &blue, alpha: &alpha)
- 57 switch dimension {
- 58 case 0: return red
- 59 case 1: return green
- 60 case 2: return blue
- 61 default: return alpha
- 62 }
- 63 }
- 64 }
- 65 distance(UIColor.redColor(), UIColor.orangeColor())
以上代碼是受Boost中的Design Rationale的啟發(fā).幾何的C++庫(kù)。Swift中的泛型功能不是那么強(qiáng),但是卻能使源碼比C++中泛型更具閱讀性。
Swift的泛型是通過(guò)類型參數(shù)化的。每個(gè)參數(shù)類型要求實(shí)現(xiàn)一個(gè)特定的協(xié)議或者繼承自一個(gè)特定的基類。在申明參數(shù)類型后,有一個(gè)可選 的"where"項(xiàng)目能用于給這個(gè)類型(或者是他們的內(nèi)部類型,就如上面那個(gè)PointTraits協(xié)議中的別名'T')添加額外的需求。這 個(gè)"where“也能要求兩種類型是相等的。
蘋果有更多更完整的Generics章節(jié)。
選定Swift
現(xiàn)在你已經(jīng)準(zhǔn)備好了去看各種各樣的源碼了,甚至可以自己寫了 :-)
在把你自己留在一個(gè)新的大的Swift未知大陸之前,我有幾個(gè)最終的建議:
-
Apple推薦你使用Int作為所有Integer類型,即使你之前用無(wú)符號(hào)數(shù)的地方。“只有在你真的需要一個(gè)與平臺(tái)原生大小想相同的無(wú)符號(hào)integer類型時(shí)再使用UInt類型.”
-
如果你想知道@private和al.在哪:好吧,他們還沒(méi)完成呢,后續(xù)版本會(huì)加進(jìn)來(lái)的。
-
如果你創(chuàng)建了一個(gè)module,你可以Cmd+單擊你的module的名字來(lái)查看你的module的自動(dòng)生成的Swift頭。
-
Swift的modules其實(shí)是命名空間,因此在任何東西前加像 CF, NS, UI等的前綴。當(dāng)創(chuàng)建第三方庫(kù)是就不是那么絕對(duì)必要了。
Enjoy using Swift!