iOS 8 Day-by-Day--Day1-Swift引子

发布于:2014-08-15 17:11阅读数:

既然已经有很多资料和文档能帮助你学习如何使用Swift语言,所以这个博客系列不会将已有的知识点重复讲解。相反,我会根据初学者在使用Swift语言中可能会遇到的一些陷阱、难以理解的知识

本文由CocoaChina翻译组成员DevTalking (博客)翻译自:iOS8 Day-by-Day :: Day 1 :: Blagger's Guide to Swift
 
在今年的WWDC大会上绝对不容忽视的一点就是苹果除了宣布iOS 8的发布,还介绍了一门新的编程语言Swift。它是与Objective-C大不相同的一门语言,它不但是强类型的语言,而且吸收了当前多种优秀编程语言的特点。
 
为了能够涵盖到Swift语言所有的新特性,该博客系列将只使用Swift语言来进行讲解。我认为,学习如何使用Swift语言、如何与Cocoa框架交互最有价值的资料应该官方提供的两个文档。我相信如果你一开始就跟着这两个文档学习,你能避免不少弯路。
 
你还应该经常去看 Swift官方博客 以及Apple提供的关于该语言的其他资源。
 
既然已经有很多资料和文档能帮助你学习如何使用Swift语言,所以这个博客系列不会将已有的知识点重复讲解。相反,我会根据初学者在使用Swift语言中可能会遇到的一些陷阱、难以理解的知识点进行讲解,尤其当和系统框架一起使用的时候。
 
今天这篇文章主要使用Xcode 6提供的Playground演示每个章节的代码示例。你可以在ShinobiControls的Github主页上下载这些示例代码。地址:github.com/ShinobiControls/iOS8-day-by-day
 
如果你对这篇文章里的知识点有什么问题或者其他建议,你可以联系我。我会在最新一期的博客中进行说明。你可以在底部进行评论或者给我发 Twitter-@iwantmyrealname
 
构造函数
Swift围绕着对象构造过程形成了一个概念,包括命名、方便的构造器以及在对象的构造阶段指定了严格的调用顺序等。在未来几周内,该博客系列将会有一篇文章深入详细地介绍Swift中对象的构造过程,以及对你写的Objective-C有何影响。
 
Swift与Objective-C另外一个最大的不同之处就是构造函数的返回值和构造失败的处理。在Objective-C中的构造器大多数都是这样:
  1. - (instancetype)init { 
  2.     self = [super init]; 
  3.     if (self) { 
  4.         // Do some stuff 
  5.     } 
  6.     return self; 
然而在Swift中是这样:
  1. init { 
  2.     variableA = 10 
  3.     ... 
  4.     super.init() 
这里要注意的是,Objective-C中的构造器负责创建并返回self,但是Swift中的构造函数却没有返回值。这意味着在Swift的构造函数中不可能也没有机会返回一个nil的返回值,而这在Objective-C中恰恰是表明构造失败的通用模式。
 
这显然是Swift语言新的Beta版中需要修改的问题。那么现在我们唯一的解决办法是通过类方法返回一个Optional类型用于表明是否构造成功:
  1. class MyClass { 
  2.     class func myFactoryMethod() -> MyClass? { 
  3.         ... 
  4.     } 
有趣的是Objective-C中的工厂方法被转换成了Swift中的构造方法,这一点显然是不可取的。但是,到目前为止,我们只能选择Swift提供的有潜在隐患的构造方法。
 
可变性
在Cocoa开发者中,可变性这个概念已然不是什么新鲜的东西了,比如在合适的情景下,我们会使用NSArray的可变类型NSMutableArray。Swift将这个概念进行了升华,并且它将不可变性升级为一个基本概念。
 
Swift 中用 let 关键字定义一个不可变的变量,也就是常量。比如说:
  1. let a = MyClass() 
  2. a = MySecondClass() // 不允许 
这意味着,我们不能改变被 let 关键字定义的常量的值,以及该常量引用对象的类型。这里需要注意的是,如果引用的类型是一个值类型(比如结构体)那么不论是引用类型还是引用类型自身都是不可变的。如果引用的是一个类,那么它的引用类型也是是不可变的,但是引用类型自身是可以改变的。
 
我们来看看下面这个结构体:
  1. struct MyStruct { 
  2.   let t = 12 
  3.   var u: String 
如果你用 var 关键字定义一个变量名叫 struct1,那么你可以对其做以下操作:
  1. var struct1 = MyStruct(t: 15, u: "Hello"
  2. struct1.t = 13 // 编译错误,因为t是一个常量,也就是不可变属性 
  3. struct1.u = "GoodBye" 
  4. struct1 = MyStruct(t: 10, u: "You"
你可以对 u 属性重新赋值,也可以对 struct1 重新赋值,因为他们都是用var定义的变量。但是你不能对t属性重新赋值,因为它是用let定义的常量属性。下面我们再来看看如果用 let 关键字定义一个 MyStruct 的实例常量,会发生什么:
  1. let struct2 = MyStruct(t: 12, u: "World"
  2. struct2.u = "Planet" // 编译错误,struct2是一个常量,也就是不可变的 
  3. struct2 = MyStruct(t: 10, u: "Defeat"// 编译错误,struct2的类型是不可变的 
在上述代码片段的情况下,你不能改变 struct2 的类型,同样也不能改变这个类型自身(比如该类型的属性u),这是因为结构体是一个值类型。
 
但如果引用的是一个类,那么上述代码示例的结果就会有所不同了,我们先看看下面这个类的定义:
  1. class MyClass { 
  2.   let t = 12 
  3.   var u: String 
  4.   
  5.   init(t: Int, u: String) { 
  6.     self.t = t 
  7.     self.u = u 
  8.   } 
我们使用 var 定义一个变量 class1 :
  1. var class1 = MyClass(t: 15, u: "Hello"
  2. class1.t = 13 // 编译错误,因为t是一个常量,也就是不可变属性 
  3. class1.u = "GoodBye" 
  4. class1 = MyClass(t: 10, u: "You"
从上述代码示例可以看出,你可以改变类型以及该类型中使用 var 定义的属性,但是不能改变使用 let 定义的属性。当我们用let定义一个常量 class2,看看会发生什么:
  1. let class2 = MyClass(t: 12, u: "World"
  2. class2.u = "Planet" // 编译通过 
  3. class2 = MyClass(t: 11, u: "Geoid"// 编译错误,class2的类型是不可变的 
从上述代码示例中可以看出,你可以改变类型自身,也就是可以改变类型中使用 var 定义的属性。这是因为类是一个引用类型。
 
这种行为很好理解,在文档中也有较全面的解释。但是当我们查看 Swift 中的集合类型时,就会产生一些疑惑。
 
NSArray 是一个引用类型。也就是说,当你实例化一个 NSArray 时,你创建的变量实际是指向了该数组在内存中的位置,所以在 Objective-C 中,用*号定义该变量。如果你回顾一下我们刚才说的使用let和var关键字定义引用类型和值类型的知识点,那么你应该基本清楚let与var关键字的用法和区别。但实际上,在 Objective-C 中,要想使用一个可变的 NSArray,你需要使用另外一个类 NSMutableArray。
 
Swift 中的数组与 Objective-C 中的就有很大区别了,数组不再是引用类型,而是值类型。这意味着在使用中,它们像上述代码示例中的结构体一样使用,而不像类那样使用。因此,let 和 var 关键字不仅仅是指定变量是否可以被重新定义,同时也会指定创建的数组是否是一个可变数组。
 
使用 var 定义的数组不仅可以对其重新赋值,也可以改变数组:
  1. var array1 = [1,2,3,4] 
  2. array1.append(5)  // [1,2,3,4,5] 
  3. array1[0] = 27    // [27,2,3,4,5] 
  4. array1 = [3,2]    // [3,2] 
但是用 let 定义的数组都不能进行上述的改变:
  1. let array2 = [4,3,2,1] 
  2. array2.append(0) // 编译错误,array2是不可变数组 
  3. array2[2] = 36   // 编译错误,array2是不可变数组 
  4. array2 = [5,6]   // 编译错误,不能对array2重新赋值 
在这一点,Swift 与 Objective-C 有巨大的差异,所以会给我们在实际开发中造成一些概念上混淆。也许在 Swift 之后的版本中对此会有所改变,所以要时刻关注 Swift 的文档。
 
通过上面的示例代码我们可以得出一个结论,因为在 Swift 中数组是一个值类型,所以当你创建一个数组时,实际上是将数组的副本赋值给了变量或常量。但在 Objective-C 中,创建一个数组时,是将一个指针赋值给变量,也就是引用了该数组在内存中的地址。所以在 Objective-C 中将它作为方法的参数传递时,始终传递的是数组在内存中的那块相同的地址。而在Swift中,传递的是数组的副本。同时根据数组中存储的对象的类型你可以对数组进行完全复制或者部分复制。在开发过程中要时刻注意这点。
 
强类型和 AnyObject
强类型是 Swift 语言最大的一个特点。它可以提高我们代码的安全性,因此,以前在 Objective-C 中会在运行时才能发现的异常,现在在编译时就可以发现了。
 
这一点非常棒。但是当你在 Swift 中使用 Objective-C 框架进行开发时,你要特别注意 AnyObject 这个类型。它相当于 Objective-C 中的 id。在有些时候,AnyObject 给我们的感觉好像又不太符合 Swift 的特性。因为你可以将 AnyObject 作为任何的类型去使用,但是这会导致程序在运行时发生异常。实际上,AnyObject 的使用方式大多时候都和 Objective-C 中的 id 一样。不同的一点是,当你用 AnyObject 代表一个类型,但是你使用了该类型中并不存在的方法或属性时,它会返回 nil:
  1. let myString: AnyObject = "hello" 
  2. myString.cornerRadius // 返回 nil 
为了更符合 Swift 语言的特性,在使用 Cocoa API 时,你也许该使用如下的模式:
  1. func someFunc(parameter: AnyObject!) -> AnyObject! { 
  2.   if let castedParameter = parameter as? NSString { 
  3.     // Now I know I have a string :) 
  4.     ... 
  5.   } 
如果你确定你传入的参数就是 String 类型的,你就没必要在强制转换时用 ? 来保护了:
  1. let castedParameter = parameter as NSString 
根据上述内容我们得知,转换数组其实也是一件很容易的事情。你从 Cocoa 框架接收的所有数组的类型都是 [AnyObject] ,因为 NSArray 不支持泛型。然而在大多数情况下,不是所有的元素都是同一类型,但是他们都有已知类型。所以你可以使用有条件判断的转换,也可以不用条件判断进行转换,像下面代码中的语法:
  1. func someArrayFunc(parameter: [AnyObject]!) { 
  2.   let newArray = parameter as [String] 
  3.   // Do something with your strings :) 
协议一致性
协议在 Swift 很好理解,定义方式如下:
  1. protocol MyProtocol { 
  2.   func myProtocolMethod() -> Bool 
有件事一定是你经常想做的,那就是判断一个对象是否遵循了指定的协议,我们可以这样写:
  1. if let class1AsMyProtocol = class1 as? MyProtocol { 
  2.   // We're in 
但是这里存在一个错误,因为判断指定的这个协议必须是一个 Objective-C 协议,所以在定义协议时要加上 @objc 标签;
  1. @objc protocol MyNewProtocol { 
  2.   func myProtocolMethod() -> Bool 
  3.   
  4. if let class1AsMyNewProtocol = class1 as? MyNewProtocol { 
  5.   // We're in 
虽然看起来只是加了 @objc 标签,但实际上不仅如此,因为在协议前加上了 @objc 标签,所以协议里的所有属性和方法返回值类型都被解析成 Objective-C 中的类型了。
 
枚举
枚举在 Swift 中变得更加实用。现在不仅可以在枚举中关联值(它们可以是不同类型的),也可以包含函数。
 
  1. enum MyEnum { 
  2.   case FirstType 
  3.   case IntType (Int) 
  4.   case StringType (String) 
  5.   case TupleType (Int, String) 
  6.   
  7.   func prettyFormat() -> String { 
  8.     switch self { 
  9.     case .FirstType: 
  10.       return "No params" 
  11.     case .IntType(let value):       
  12.       return "One param: \(value)"     
  13.     case .StringType(let value): 
  14.       return "One param: \(value)" 
  15.     case .TupleType(let v1, let v2): 
  16.       return "Some params: \(v1), \(v2)" 
  17.     default
  18.       return "Nothing to see here" 
  19.     } 
  20.   } 
 
这非常有用:
  1. var enum1 = MyEnum.FirstType 
  2. enum1.prettyFormat() // "No params" 
  3. enum1 = .TupleType(12, "Hello"
  4. enum1.prettyFormat() // "Some params: 12, Hello" 
只需要做一些小练习,你就能体会出它的强大之处。
 
总结
Swift 的确很强大,它还需要我们通过实践去摸索新的开发模式,而不必局限在 Objective-C 的模式中。这篇文章列举了一些从 Objective-C 转到 Swift 中可能遇到的潜在的问题和疑惑,但希望不要让你对 Swift 却步。所有与本博客系列相关的项目和代码示例都是用 Swift 写的,并且都很通俗易懂。
 
本文中的代码示例都是在 Playground 中完成的,你可以在 github.com/ShinobiControl/iOS8-day-by-day 下载这些示例。

CocoaChina是全球最大的苹果开发中文社区,官方微信每日定时推送各种精彩的研发教程资源和工具,介绍app推广营销经验,最新企业招聘和外包信息,以及Cocos2d引擎、Cocos Studio开发工具包的最新动态及培训信息。关注微信可以第一时间了解最新产品和服务动态,微信在手,天下我有!

请搜索微信号“CocoaChina”关注我们!

搜索CocoaChina微信公众号:CocoaChina

顶部