从Objective-C到Swift——Swift糖果

移动开发 iOS
Swift带来很多确实很棒的特性,使得很难再回到Objective-C。主要的特性是安全性,不过这也被看成是一种额外副作用。

[[115298]]

Swift带来很多确实很棒的特性,使得很难再回到Objective-C。主要的特性是安全性,不过这也被看成是一种额外副作用。

带类型接口的强型别

Swift有强型别,这意味着除非你要求,不然Swift不会为你做类型之间的转换。所以,例如你无法把Int型赋给Double型。你不得不首先转换类型:

  1. let i: Int = 42 
  2. let d: Double = Double(i) 

或者你必须给Double类扩展一个方法用来转换Int型:

  1. extension Double { 
  2.         func __convert(i: Int) -> Double { 
  3.             return Double(i) 
  4.         } 
  5.     } 
  6.     let another_d: Double = i 

强型别对于安全性是非常非常有利的。但是,如果它没有为你给类型接口添加许多类型信息的话,它也可能变成有一点令人生畏的事情,有点像是在写脚本语言。

  1. let ary = ["Hello""world"// NOTE: 'ary' is of type String[] or Array<String> 
  2.     for s in ary { // NOTE: 's' is of type String 
  3.         print(s + " "
  4.     } 

如果你想要创建一个包含很多类型(无共同祖先)的数组,你应该用枚举(它可以包含值,见如下)。如果你想要它能够包含所有值,你可以用Any型。如果想让它包含任何Objective-C的类型,那就用AnyObject型。

请注意类型接口不会在申明函数的时候为你添加类型。你必须明确地说明你所申明函数的类型。

Blocks

Swift 中的Blocks很像Objective-C中的Blocks, 不过有两点不同: 类型推断和避免weakify dance.

对于类型推断,你不必每次写block时都包含完整类型信息:

  1. sort([2,1,3], { 
  2.         (a: Int, b: Int) -> Bool in return a < b 
  3.     }) 
  4.       
  5.     // Using Type Inference 
  6.     // Using the Trailing Closures feature 
  7.       
  8.     sort([2,1,3]) { 
  9.         a, b in return a < b 
  10.     } 
  11.       
  12.     // Implicit 'return' for single-expression blocks 
  13.       
  14.     sort([2,1,3]) { a,b in a<b } 
  15.       
  16.     // Shorthand Argument Names 
  17.       
  18.     sort([2,1,3]) { $0 < $1 } 
  19.       
  20.     // Operators are functions, and functions are blocks too! 
  21.       
  22.     let sorted: Int[] = sort([2,1,3], <) 

访问 Closures 了解更多blocks信息。

除此之外,Objectvie-C 的weakify dance有点容易,只需在block的开始处加上  [unowned self] 或 [weak self]  即可。

  1. class CallbackTest { 
  2.         var i = 5 
  3.         var callback: (Int -> ())? // NOTE: The optional callback takes an Int 
  4.         deinit { // NOTE: This is like -dealloc in Objective-C 
  5.             println("Deinit"
  6.         } 
  7.     } 
  8.       
  9.     var obj = CallbackTest() 
  10.     obj.callback = { 
  11.         [unowned obj] // NOTE: Without this, deinit() would never be invoked! 
  12.         a in 
  13.         obj.i = a 
  14.     } 

请注意Introduction post文章中介绍了Optional(像上面的callback)。

请参考 ARC 章节来了解更多关于Swift中ARC的信息。

强劲的Enumerations

Swift中的枚举比Objective-C中的有很大提高。

修复 Enums

Apple一直提倡显示的提供枚举类型的大小,不过被Objective-C搞乱了:

  1. // Apple recommended enum definition in Objective-C 
  2.     typedef NS_ENUM(NSInteger, UIViewAnimationCurve) { 
  3.         UIViewAnimationCurveEaseInOut, 
  4.         UIViewAnimationCurveEaseIn, 
  5.         UIViewAnimationCurveEaseOut, 
  6.         UIViewAnimationCurveLinear 
  7.     }; 
  8.       
  9.     // Previous Apple recommended enum definition in Objective-C. No link between  
  10.     // enum values and theUIViewAnimationCurve type. 
  11.     typedef enum { 
  12.         UIViewAnimationCurveEaseInOut, 
  13.         UIViewAnimationCurveEaseIn, 
  14.         UIViewAnimationCurveEaseOut, 
  15.         UIViewAnimationCurveLinear 
  16.     }; 
  17.     typedef NSInteger UIViewAnimationCurve; 
  18.       
  19.     // Traditional enum definition in Objective-C. No explicit fixed size. 
  20.     typedef enum { 
  21.         UIViewAnimationCurveEaseInOut, 
  22.         UIViewAnimationCurveEaseIn, 
  23.         UIViewAnimationCurveEaseOut, 
  24.         UIViewAnimationCurveLinear 
  25.     } UIViewAnimationCurve; 

Swift中的修复:

  1. enum UIViewAnimationCurve : Int { 
  2.         case EaseInOut 
  3.         case EaseIn 
  4.         case EaseOut 
  5.         case Linear 
  6.     } 

拓展Enums

Enums 在Swift中更进一步,只作为一个独立的选项列表。你可以添加方法(以及计算属性):

  1. enum UIViewAnimationCurve : Int { 
  2.         case EaseInOut 
  3.         case EaseIn 
  4.         case EaseOut 
  5.         case Linear 
  6.         func typeName() -> String { 
  7.             return "UIViewAnimationCurve" 
  8.         } 
  9.     } 

使用类型拓展,你可以向枚举中添加任何你想要的方法:

  1. extension UIViewAnimationCurve { 
  2.         func description() -> String { 
  3.             switch self { 
  4.             case EaseInOut: 
  5.                 return "EaseInOut" 
  6.             case EaseIn: 
  7.                 return "EaseIn" 
  8.             case EaseOut: 
  9.                 return "EaseOut" 
  10.             case Linear: 
  11.                 return "Linear" 
  12.             } 
  13.         } 
  14.     } 

#p#

向Enums中添加值

Swift中的枚举跟进一步,允许每一个独立的选项都有一个对应的值:

  1. enum Shape { 
  2.         case Dot 
  3.         case Circle(radius: Double) // Require argument name! 
  4.         case Square(Double) 
  5.         case Rectangle(width: Double, height: Double) // Require argument names! 
  6.         func area() -> Double { 
  7.             switch self { 
  8.             case Dot: 
  9.                 return 0 
  10.             case Circle(let r): // Assign the associated value to the constant 'r' 
  11.                 return π*r*r 
  12.             case Square(let l): 
  13.                 return l*l 
  14.             case Rectangle(let w, let h): 
  15.                 return w*h 
  16.             } 
  17.         } 
  18.     } 
  19.     var shape = Shape.Dot 
  20.     shape = .Square(2) 
  21.     shape = .Rectangle(width: 3, height: 4) // Argument names required 
  22.     shape.area() 

如果你喜欢,你可以把它当做一个安全的union类型。或者只用枚举应该做的事情。

Enumerations 文章介绍了更多关于Apple对此的看法。

Swift Switch语句

就像你看到的,Swift中switch语句有很多优化。

 隐式的fall-through行为已经改为了显示的:

  1. var (i, j) = (4, -1) // Assign (and create) two variables simultaneously 
  2.     switch i { 
  3.     case 1: 
  4.         j = 1 
  5.     case 2, 3: // The case for both 2 and 3 
  6.         j = 2 
  7.     case 4: 
  8.         j = 4 
  9.         fallthrough 
  10.     case 5: 
  11.         j++ 
  12.     default
  13.         j = Int.max // The Swift version of INT_MAX 
  14.     } 

就像前面看到的,Switch 语句可以访问枚举的关联值,不过它还可以做更多:

  1. var tuple: (Int, Int) // Did I mention that Swift has tuples? :-) 
  2.     var result: String 
  3.       
  4.     tuple = (1,3) 
  5.       
  6.     switch tuple { 
  7.     case (let x, let y) where x > y: 
  8.         result = "Larger" 
  9.     case (let x, let y) where x < y: 
  10.         result = "Smaller" 
  11.     default
  12.         result = "Same" 
  13.     } 

甚至可以使用String:

  1. var s: String = "Cocoa" 
  2.     switch s { 
  3.     case "Java":   s = "High caffeine" 
  4.     case "Cocoa":  s = "High sugar" 
  5.     case "Carbon": s = "Lots of bubbles" 
  6.     default: () 
  7.     } 

另外,如果你觉得他可以使你的代码更可读,你可以重载~=操作符来改变switch语句的行为。

  1. func ~=(pattern: String, str: String) -> Bool { 
  2.         return str.hasPrefix(pattern) 
  3.     }  
  4.     var s = "Carbon" 
  5.     switch s { 
  6.     case "J":  s = "High caffeine" 
  7.     case "C":  s = "No caffeine" 
  8.     default: () 
  9.     } 

你可以从 Conditional Statements 这篇文章中了解更多关于switch语句的知识。

类与结构体

类似于C++,Swift的类与结构体初看是一样的:

  1. class Apple { 
  2.         var color = "green" // Property declaration 
  3.         init() {} // Default initializer 
  4.         init(_ color: String) { // '_' means no argument name 
  5.             self.color = color 
  6.         } 
  7.         func description() -> String { 
  8.             return "apple of color \(color)" 
  9.         } 
  10.         func enripen() { 
  11.             color = "red" 
  12.         } 
  13.     } 
  14.       
  15.     struct Orange { 
  16.         var color = "green" 
  17.         init() {} 
  18.         init(_ color: String) { 
  19.             self.color = color 
  20.         } 
  21.         func description() -> String { 
  22.             return "orange of color \(color)" 
  23.         } 
  24.         mutating func enripen() { // NOTE: 'mutating' is required 
  25.             color = "orange" 
  26.         } 
  27.     } 
  28.       
  29.     var apple1 = Apple() 
  30.     var apple2 = apple1 // NOTE: This references the same object! 
  31.     apple1.enripen() 
  32.     apple2.description() // Result: "apple of color red" 
  33.       
  34.     var orange1 = Orange() 
  35.     var orange2 = orange1 // NOTE: This makes a copy! 
  36.     orange1.enripen() 
  37.     orange2.description() // Result: "orange of color green" 

主要的不同点在于类是(和块相似的)引用类型,而结构体是(和枚举相似的)数值类型。所以两个变量能够指向同一个(类的)对象,而把一个结构体赋给 另外一个变量则必须做一个此结构体的(缓慢的)拷贝。关键词'mutating'告诉调用者enripen()方法不能被常结构体调用。把一个常引用 mutating给一个类对象则没有问题。

Swift中大多数内建类型实际上都是结构体。甚至Int型也是。通过点击Cmd你能够看到内建类型的申明,比如Int型的Swift(或者 Playground)源码。数组和词典类型也是结构体,但是数组在某些方面表现得像是引用类型:赋值数组并不拷贝每一个元素,实际上你可以更新常数组只 要元素的个数保持不变。

  1. let array1 = [1, 2, 3] 
  2.     let array2 = array1 // A copy, but references the same elements! 
  3.     array1[1] = 5 // Legal, as it doesn't modify the struct but a referenced element 
  4.     array2 // Result: [1, 5, 3] 

在苹果的文档中,你可以读到更多关于Collection Types的内容。

#p#

对象的生命周期

另一个类与结构体的不同是类可以被子类化。 雷和结构体都可以被拓展,并且实现protocol,但是只用类可以继承其他类。

  1. class Pineapple : Apple { 
  2.         init(color: String) { 
  3.             super.init(color) 
  4.         } 
  5.         convenience init() { 
  6.             self.init("green"
  7.         } 
  8.         convenience init(ripe: Bool) { 
  9.             self.init() 
  10.             if ripe { 
  11.                 color = "yellow" 
  12.             } else { 
  13.                 color = "green" 
  14.             } 
  15.         } 
  16.         deinit { 
  17.             println("Pineapple down"
  18.         } 
  19.         override func description() -> String { 
  20.             return "pine" + super.description() 
  21.         } 
  22.         override func enripen() { 
  23.             color = "yellow" 
  24.         } 
  25.     } 

就像你看到的,Swift为继承添加了一点更有趣的需要学习的东西。对于初学者来说,你需要清除你覆盖父类中某个方法的意图。如果你想阻止子类覆盖一些东西,你可以在一个单独声明或整个类的前面加上@final属性。阅读 Apple’s documentation了解更多。

初始化

Swift的对象分两步进行初始化: 首先对象必须是有效的,然后它能被替换。

  1. class ChildShoe { 
  2.         var size: Double // Uninitialized properties are not allowed unless taken care of in init() 
  3.         init(foot_size: Double) { 
  4.             size = foot_size // First make the object valid 
  5.             addGrowthCompensation() 
  6.         } 
  7.         func addGrowthCompensation() { 
  8.             size++ 
  9.         } 
  10.     } 

使对象有效必须要调用一个超级类指定的init()方法。类可以同时拥有指定的以及便捷的(用关键词'convenience'标记)初始化方法。 便捷初始化方法调用同一个类中的其他初始化方法(最终还是一个指定的初始化方法),而指定的初始化方法调用超级类的初始化方法。

如果你给所有的超级类指定初始化方法添加初始化方法,你的类也会自动继承所有便捷初始化方法。如果没有添加任何指定的初始化方法,你的类则继承超级类的所有(指定的和便捷的)初始化方法。

深入阅读请参见Initialization

类型转换

类之间的转换,特别是向下转换,你可以使用"is","as?"和"as"关键词:

  1. let apple: Apple = Pineapple() 
  2.       
  3.     let exotic: Bool = apple is Pineapple 
  4.       
  5.     let pineappleOption: Pineapple? = apple as? Pineapple 
  6.     let pineapple: Pineapple = apple as Pineapple // NOTE: Throws if not! 
  7.       
  8.     if let obj = apple as? Pineapple { // If executed, 'obj' is a Pineapple 
  9.         "sweet" 
  10.     } 

想了解更多这方面内容请参见Type Casting一章.

泛型

泛型是Swift的一个加分的特点。他们看起来有一点像C++里面的模板,但是有更强的型别,也更简单(更简单使用,功能稍逊)。

  1. 01    // Mark both Int and Double as being convertible to a Double using the '+' prefix 
  2. 02  protocol DoubleConvertible { 
  3. 03      @prefix func +(v: Self) -> Double 
  4. 04  } 
  5. 05  @prefix func +(v: Int) -> Double { return Double(v) } 
  6. 06  extension Double: DoubleConvertible {} 
  7. 07  extension Int: DoubleConvertible {} 
  8. 08  // NOTE: Repeat this for all Int*, UInt*, and the Float type 
  9. 09    
  10. 10  // The traits of a generalized point 
  11. 11  protocol PointTraits { 
  12. 12      typealias T 
  13. 13      class var dimensions: Int { get } 
  14. 14      func getCoordinate(dimension: Int) -> T 
  15. 15  } 
  16. 16    
  17. 17  // Generalized Pythagoras 
  18. 18  struct Pythagoras<P1: PointTraits, P2: PointTraits where P1.T: DoubleConvertible, P2.T: DoubleConvertible> { 
  19. 19      static func apply(a: P1, b: P2, dimensions: Int) -> Double { 
  20. 20          if dimensions == 0 { 
  21. 21              return 0 
  22. 22          } 
  23. 23          let d: Double = +a.getCoordinate(dimensions-1) - +b.getCoordinate(dimensions-1) // NOTE: '+' to convert to Double 
  24. 24          return d * d + apply(a, b: b, dimensions: dimensions-1) 
  25. 25      } 
  26. 26      static func apply(a: P1, b: P2) -> Double { 
  27. 27          let dimensions = P1.dimensions 
  28. 28          assert(P2.dimensions == dimensions) 
  29. 29          return apply(a, b: b, dimensions: dimensions) 
  30. 30      } 
  31. 31  }; 
  32. 32    
  33. 33  import func Foundation.sqrt // NOTE: You can import a typealias&shy;, struct&shy;, class&shy;, enum&shy;, protocol&shy;, var&shy;, or func only 
  34. 34    
  35. 35  func distance<P1: PointTraits, P2: PointTraits where P1.T: DoubleConvertible, P2.T: DoubleConvertible>(a: P1, b: P2) -> Double { 
  36. 36      assert(P1.dimensions == P2.dimensions) 
  37. 37      return sqrt(Pythagoras.apply(a, b: b)); 
  38. 38  } 
  39. 39    
  40. 40  // A generalized 2D point 
  41. 41  struct Point2D<Number> : PointTraits { 
  42. 42      static var dimensions: Int { return 2 } 
  43. 43      var x: Number, y: Number 
  44. 44      func getCoordinate(dimension: Int) -> Number { return dimension == 0 ? x : y } // NOTE: The typealias T is inferred 
  45. 45  } 
  46. 46  let a = Point2D(x: 1.0, y: 2.0) 
  47. 47  let b = Point2D(x: 5, y: 5) 
  48. 48  Pythagoras.apply(a, b: b) // NOTE: Methods require all argument names, except the first 
  49. 49  distance(a, b) // NOTE: Functions do not require argument names 
  50. 50    
  51. 51  // UIColor 
  52. 52  extension UIColor : PointTraits { 
  53. 53      class var dimensions: Int { return 4 } 
  54. 54      func getCoordinate(dimension: Int) -> Double { 
  55. 55          var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 
  56. 56          getRed(&red, green: &green, blue: &blue, alpha: &alpha) 
  57. 57          switch dimension { 
  58. 58          case 0: return red 
  59. 59          case 1: return green 
  60. 60          case 2: return blue 
  61. 61          defaultreturn alpha 
  62. 62          } 
  63. 63      } 
  64. 64  } 
  65. 65  distance(UIColor.redColor(), UIColor.orangeColor()) 

以上代码是受Boost中的Design Rationale的启发.几何的C++库。Swift中的泛型功能不是那么强,但是却能使源码比C++中泛型更具阅读性。

Swift的泛型是通过类型参数化的。每个参数类型要求实现一个特定的协议或者继承自一个特定的基类。在申明参数类型后,有一个可选 的"where"项目能用于给这个类型(或者是他们的内部类型,就如上面那个PointTraits协议中的别名'T')添加额外的需求。这 个"where“也能要求两种类型是相等的。

苹果有更多更完整的Generics章节。

选定Swift

现在你已经准备好了去看各种各样的源码了,甚至可以自己写了 :-)

在把你自己留在一个新的大的Swift未知大陆之前,我有几个最终的建议:

  • Apple推荐你使用Int作为所有Integer类型,即使你之前用无符号数的地方。“只有在你真的需要一个与平台原生大小想相同的无符号integer类型时再使用UInt类型.”

  • 如果你想知道@private和al.在哪:好吧,他们还没完成呢,后续版本会加进来的。

  • 如果你创建了一个module,你可以Cmd+单击你的module的名字来查看你的module的自动生成的Swift头。

  • Swift的modules其实是命名空间,因此在任何东西前加像 CF, NS, UI等的前缀。当创建第三方库是就不是那么绝对必要了。

Enjoy using Swift!

责任编辑:闫佳明 来源: oschina
相关推荐

2014-10-13 09:54:08

Objective-CSwift

2015-06-08 10:02:40

swiftOC兼容

2014-06-05 13:54:03

SwiftiOSObjective-C

2014-09-24 11:15:05

Objective-CSwift

2014-09-26 09:49:48

SwiftObjective-C

2015-07-08 10:47:57

Using Swift CocoaObjective-C

2015-02-05 00:18:44

SwiftObjective-C

2017-04-07 16:00:59

SwiftObjective-CFramework

2014-06-16 10:02:42

SwiftiOSWWDC

2022-07-11 10:17:19

Swift编程语言项目

2015-06-25 11:21:33

C++Objective-C

2015-05-14 10:26:37

Objective-CSwift

2017-02-10 09:55:53

SwiftObjective-C

2015-06-15 14:58:25

swiftOC

2014-06-09 12:58:48

编程语言排行榜

2011-07-06 14:41:34

Objective-C

2016-03-30 09:56:07

c语言变量声明objectivec

2014-06-16 14:01:01

IT技术周刊

2021-04-13 10:14:12

编程语言PythonJava

2013-03-27 12:54:00

iOS开发Objective-C
点赞
收藏

51CTO技术栈公众号