简介
从上面一章中,你已经了解了Swift中的变量,常量,元组,字符以及控制流。这章内容让你进一步了解Swift的语法特点。
这章节重心仍然放在基础语法上,但会有些更高级的内容。就像上一章你做的那样,在playground中一边敲代码,一边直接在右边查看结果。
这章结束后,Swift的基本内容就被你记得七七八八了吧。然后我们就可以在真实的应用中开始练习了。所以,有没有觉得playground拿来练习Swift的基础语法溜的飞起。(真心无力,天哪!!!这么多废话,后面还有两百页,~~(>_<)~~)
奔跑吧!骚年,继续Swift吧!
Optionals - 可选类型
你只要编过程序,就一定知道关于null的指针是有多恼火。当你准备赋值或者用一个对象调用方法时,对象还没初始化,不用说会发生什么!内心中万匹草泥马奔腾而过!在java中,对于null指针会抛出null指针异常让你处理。
在Object-C运行时,数据传递的是nil则返回的也是nil。这貌似说明null指针也是安全的。然而这并没有什么卵用,千万别指望他的运行情况和非null时是一样的。所以我们通常的做法是添加assertion断言以确保所有的变量都不是nil。在调试模式中,当碰到nil时应用程序会崩溃,所以你可以在发布你的代码前用这个方法找到并修复你有nil的地方。
在Swift中,处理是不一样的。
打开xcode,选择File\New\Playground…, 起名叫Chapter_2并选择iOS platform ,然后点击next将文件保存在本地。
尝试演练下吧:
var str = "Hello, playground"
print(str)
正如你看到的,第一行创建了一个叫str的新的变量,并且初始化为”Hello, playground” 。但是如果在这个时候没有初始化内容呢?其他语言中,会将nil,null或者一个初始值赋值给没有值的变量。
删除第一行str后面的所有内容,你可以看到提示报错:Type annotation missing in pattern (模板丢失类型说明)
这很明显的可以看出编译器不知道str应该是什么类型的数据,毕竟你毛都不给别个一根,你让别个怎么推断类型。
如果他需要一个明确的值来进行推断数据的类型的话,我们可以试试直接指定变量的类型看看:
var str: String
(这次会提示一个不同的报错:Variable ‘str’ used before being initialized. (变量str没有初始化)
闪瞎氪金狗眼!Swift在编译的时候就不允许你使用未初始化的变量。这意味着你再也不会出现意外的nil值。)
事实证明在这行代码后并没有这个报错,也就是说Swift2.0或3.0去掉了,只有当你调用这个str变量的时候才会报这个错。即在使用print(str)提示此错误。
但是如果你就是想要一个nil值的变量呢?(些许蛋疼),有时,你可能就是需要一个nil值的变量,比如用nil来表示返回的是一个error错误或者是没有值的情况。
或许你觉得,如果你需要nil的变量,只需赋值成nil就ok了:
var str: String = nil
但是,但是,又报错了。这次编译器提示:Type ‘string’ does not conform to protocol ‘NilLiteralConvertible’. (string 不符合 NilLiteralConvertible协议)。这是Swift的另一个安全机制起作用了,这是因为你声明的变量是String,那他就一定要是String,nil不是String。
废话这么多,终于来正题了,你想用nil的变量,这个时候你就需要用到optional(可选类型)了.在这个语言中可选类型的广义概念是说包含有有值和可能有值的两种情况。在其它语言中你可能经常看到,我们用-1或0这样的值来表示没有值。null指针虽然是null,但他也是一种明确的值类型,这样你大概能猜出上面为什么会提示报错了吧,因为null指针也是一种对象类型,只是不会指向任何一个有效的对象而已。
Declaring optionals - 可选类型声明
先看下怎么用可选类型吧
var str: String?
你只需要添加一个问号到类型声明的后面,标明这个String 是个可选类型就可以了。因为这个str变量是可选类型的,所以他既可以有nil又可以有一个String实例。str会被初始化为nil因为你还没有给这个String指明一个明确的值。
接着改变代码如下:
var str: String? = "Hello Swift by Tutorials!"
如你所料控制台输出:”Hello Swift by Tutorials!” ,看起来就像是等号右边的值直接赋值给了左边的String可选类型中的有值部分。实在是很神奇,在分配的时候,Swift自动分配了可选类型中值是赋值给nil那部分还是给有值的那部分。
当你输出这个变量时,注意playground的右边显示:
直接说明了括号里的字符串是可选类型的。
虽然看起来像是普通的string值,但一样吗?,做个测试
str = str.uppercaseString
报错提示:error: value of optional type ‘String?’ not unwrapped; did you mean to use ‘!’ or ‘?’?
提示你还没有解包,这个内容后面再讲,以前版本会提示error: ‘String?’ does not have a member named ‘uppercaseString’. 明显在Swift3.0上提示又修改了
可以简单的理解虽然String?和String看上去很相似,有些方法用着也一样,不过这是两种不同的数据类型,String?里面没有字符串String中的方法。
Swift提供了一种安全的方式让你可以使用可选类型中包含的值,就是用if判断语句来重新赋值给一个新的变量,让新的变量来调用
if let unwrappedStr = str {
print("Unwrapped! \(unwrappedStr.uppercaseString)")
}
用if语言块,可以直接将str解开,并将值赋值给一个叫unwrappedStr的常量,如果str解开包含了一个实例字符串,那么unwrappedStr就会变成String类型的常量并让if语句通过。
修改代码,删除str的初始化语句,并修改if语句添加如下
} else {
print("Was nil")
}
因为这个时候str解开是一个nil值,所以这个if语句无法通过。这说明如果你想使用可选类型中的值,Swift提供了一种非常安全的方式让你调用。另外顺带一提,如果赋值let unwrappedStr = str,则unwrappedStr是个可选类型,也就是说只有在刚刚的if语句中,解开赋值后unwrappedStr会变成单纯的String类型,而不是可选类型。
Forced unwrapping - 强解
如果你确定可选类型中有值,那你可以使用一种叫强制解析的手段直接使用可选类型中的值,也就是说你可以不通过if语句块来检查可选类型中是否包含有值。
修改代码:
var str: String? = "Hello Swift by Tutorials!"
print("Force unwrapped! \(str!.uppercaseString)")
在变量名后面添加一个感叹号便是强解,无论str中是否有值你都可以直接使用uppercaseString。在当前示例下,因为str中有个实际的字符串实例,所以一切运行正常,修改代码,删除str的初始化赋值,会报错:unexpectedly found nil while unwrapping an Optional value.
这是个运行时错误,会导致应用崩溃。这个崩溃是因为你在强制解析一个只有nil值的可选变量后并进行了操作。
小技巧:虽然强解非常的有用,但是他破坏了Swift在可选类型中提供的安全机制。只有在你绝对需要以及百分百肯定这个可选类型不可能为nil时再用这个代码执行。
Implicit unwrapping - 隐式解包
你也可以在不使用手动解包或者强制解包的情况下使用可选类型的变量或常量。修改代码:
var str: String! = "Hello Swift by Tutorials!"
客官可曾发现有何不同?仔细看类型声明的后面,现在用一个感叹号替代了以前的问号,用来表示想要隐式的解开这个变量并使用。
现在你就可以用这个隐式解包的类型如其他你所知道的类型一样正常使用了,for example:
str = str.lowercaseString
print(str)
他看上去完全不像是一个可选类型了。再来试试删除掉赋值内容,就会发现报和刚才强制解包时一样的错误:unexpectedly found nil while unwrapping an Optional value
你可能现在很难看到有用隐式解包展开的。但他们这种类型的本身特点以及可以用来处理的哪些问题,便已经决定了隐式解包也是有用的。
你可以用if语句块来检查可选类型。将最后两行代码用if语句块包裹起来,如下:
if str != nil {
str = str.lowercaseString
print(str)
}
现在代码可以正常运行了,像上面这种检查可选类型是不是nil在Object-C中你可能也是这样用的。在Object-C中,你通过是否是nil来判断是否是错false。在Swift中,稍有不同。nil返回的则真正是“没有值”这种状态类型。
小技巧:你用隐式解包也要像强制解包一样谨慎小心,除了在他的声明的时候,隐式解包类型的变量实在是和平常的标准的变量太像了,所以用的时候一定要小心小心在小心。
Optional chaining - 可选链
Optional chaining(可选链)是一种使用可选类型的简洁方式,不用每次使用都使用if/else 条件语句块。
如果你做过Object-C开发,那你肯定对delegate的代理模式很熟悉。一个对象的代理负责调用另外一个对象的方法。常见的委托代理实现是可选的,如果代理的方法没有设置,则代理的对象不会去调用他的代理方法。这种实现原理对可选类型来说就非常的合适。
我们不想你马上就去了解类和代理内容(我们在第三章“类和结构体”以及第八章“Swift和Cocoa”进行了解)。所以我们先用一个简单的String来进行示范,虽然简单,但是原理是一样的。
更新代码:
var maybeString: String? = "Hello Swift by Tutorials!”
let uppercase = maybeString?.uppercaseString
第一行我们声明了一个叫maybeString的String可选类型变量,在第二行,我们用一个问号添加在变量名后面形成一个可选链。运行代码时,他会检查可选类型maybeString的内容,如果内容是一个实例,则继续执行uppercaseString 方法。如果是nil,则返回nil。
这样uppercase操作也变成可选的了,因为他要操作的对象可能是maybeString也可能是nil了。这和Object-C中操作nil信息很像。操作nil则返回nil。
Collections - 集合
所有的语言都需要有像arrays数组,dictionaries字典以及set集的集合。在Object-C中Foundation framework 提供了很多不同类型的集合,最常用的是NSArray和NSDictionary 。在苹果中这都变成了非常精简好用的语法,所以怎么可能不在Swift中也尝试下呢?
事实证明,在Swift中只提供了两种最原始的集合类型:数组和字典,在Swift的库中有提供。他们的使用方式与其他语言还是有丁点不一样。
Arrays - 数组
如你所料的,在Swift中数组是一个有序的元素集合,在playground中添加如下代码:
var array = [1, 2, 3, 4, 5]
生成了一个含有5个元素的数组
你可以使用下标来访问数组的元素。
print(array[2])
Swift的下标也是从0开始的,所以输出第三个元素。
你可以用append() 来增加数组的内容,如下:
array.append(6)
print(array)
现在你的数组里面有6个元素了。
你甚至可以通过一个序列的内容直接添加到数组中,如范围类型range。如下所示:
array.extend(7...10) //被废弃,现在用下面的方法
array.appendContentsOf(7...10),//现在你就有10个元素了
挑战:找出从数组中删除元素的方法!
现在来试试数组中添加不同类型的元素。
array.append("11")
编译器报错。如果你是从Object-C转过来的,你肯定会有些蛋蛋的忧伤,在Object-C中,数组可以包含有任何类型的对象,但在Swift中数组是强约束类型,里面只能有一种明确的数据类型。上面的例子是数组从元素中推断出当前array是一个integer的数组,因为你在初始化时便在数组中装的全是integer。
修改初始化代码:var array: [Int] = [1, 2, 3, 4, 5]
上面的声明演示了在Swift中如何给一个数组的元素定义类型:简单的方式就是直接添加一个[],你也可以用一个较长的泛型语法,Array(一个有着Int类型约束的数组)。明显的前面的方式比较简单,我们在第四章“Generics 泛型”中再去了解这种语法吧。
你可以和Object-C中NSArray那样生成一个数组,只是要指定一个数组中元素的类型。你应该创建一个特定类型的数组,这样会更安全些。有没有觉得Swift的安全机制到处都是。
挑战:创建不同类型的的数组进行练习,比如字符串之类的。
Dictionaries - 字典
数组提供了一个序列的元素列表,字典提供了一个有键值对映射的列表。
代码演示:
var dictionary = [1: "Dog", 2: "Cat"]
生成了一个有着两个键值对的字典。语法和数组很像,不过使用逗号将每个对象列表分开,每个键值对以冒号分隔开。Object-C的开发者可能会发现字典的语法有点不一样,用的依然是中括号而不是花括号。
和数组一样,字典也是强约束类型。在这个例子中,这是一个Int类型的key,String类型的值的Dictionary
print(dictionary[1])
输出“Dog”
你也可以通过key来修改字典。代码示例:
dictionary[3] = "Mouse"
print(dictionary)
现在字典中有个三个值,如果你对Object-C或其它编程语言熟悉,想必对这个语法也不会感到陌生。变量后面的方括号表示字典的key。在这个示例中,因为值被分配到了一个叫3的key中。
挑战:修改key为2的内容为“Elephant” 。
也可以用这种方法移除对应的对象:
dictionary[3] = nil
这示范的是给key叫3的值设置为nil,所以就相当于是直接移除了。
也可以直接用key来获取key对应的值。试试下面的:
print(dictionary[1])
你可能已经发现playground上显示的是”Optional(“Dog”)} ,这意味着他返回的内容是一个可选类型。这完全说的通,当你访问一个并不存在的key时,可不就要返回nil嘛。
和上面提到的解包一起使用下:
if let value = dictionary[1] {
print("Value is \(value)")
}
这又很好的展示了Swift的默认的安全机制,迫使你时刻考虑着值为nil的情况,毕竟大多数情况下你都是需要值有内容的。
References and copies - 引用和复制
数组和字典都在Swift中展示了引用类型和值类型的操作。编程语言在运行时都有他们自己对应的处理方式,所以明白Swift是怎么工作相当重要。
首先,我们看一下字典。就像你看到的,字典和数组十分相似。代码如下:
var dictionaryA = [1: 1, 2: 4, 3: 9, 4: 16]
var dictionaryB = dictionaryA
print(dictionaryA)
print(dictionaryB)
这样做你期待什么?明显是两个一模一样的字典不是。
再来添加一行代码修改下:
dictionaryB[4] = nil
print(dictionaryA)
print(dictionaryB)
输出显示b字典比a字典的内容少。同样的,如果你修改字典的内容而不是删除,你会发现,你修改的也只是你修改的那个字典,另外一个字典并不会改变。
这是一个非常重要的注意点:当你分配一个新的变量或常量字典,或者是将字典以参数形式传递给方法,字典都是复制一份新的内容。
让我们在数组上做一点相同的事,更新代码如下:
var arrayA = [1, 2, 3, 4, 5]
var arrayB = arrayA
print(arrayA)
print(arrayB)
arrayB.removeAtIndex(0)
print(arrayA)
print(arrayB)
结果自然也不会出人意料才对,当你修改或删除一个数组时,并不会修改另一个数据
现在来试试修改里面的内容
arrayB[0] = 10
不出所料,也只有数组b内容被修改了,这意味着在分配的时候是复制了一份新的内容。
和其他语言中如Object-C的引用对比。当一个NSArray的指针被分配到另一个变量时,他们的指针指向的是同一个数组实例。修改一个数组,另外一个数组也会改变。
Constant collections - 常量集合
正如你第一章看到的,Swift有常量和变量两种。到目前为止,我们都只使用了用var声明的数组和字典。你也可以用let 来定义数组和字典,这种声明方式可能和你想的有点出入。
代码君:
let constantArray = [1, 2, 3, 4, 5]
除了是常量,声明和前面的声明是一样的。常量数组对元素无法进行添加和删除功能。
让我们look look~~
constantArray.append(6)
constantArray.removeAtIndex(0)
提示错误:let类型的不可修改。
这是Swift的再次安全控制,让你不会意外性的修改了数组内容。常量字典和常量数组一样。
挑战:创建一个常量字典,尝试下删除等不允许改变的操作。
接下来干点什么呢?
从上面两章内容过后,你已经了解了Swift的的基本变量,常量以及可选类型,集合等。
那现在做what? 是时候把现在知道的内容运用到实际的应用中了,我们下一章便开始。
抓紧时间该休息的休息,该上厕所的上厕所。新的挑战开始了。