本文是《开始学习iOS开发》的翻译,为苹果官方文档。学习需要,先翻译出来自己看看,随着学习深入会加入一些工程案例。
1 概述
文档中的所有代码都使用XCode的Playground工具进行演示。
Playground允许用户同自己的代码进行实时交互。通过Playground,用户可以快速对自己的代码功能进行验证。
所以,利用它可以快速掌握Swift的基本概念。
1.1 要点一览
经过本文的学习,您将能够:
- 区分Swift中的常量(Constant)和变量(Variable);
- 知道何时需要进行显式类型声明;
- 了解使用optional以及optional binding的优势之处;
- 知道optional和隐式解包optional的区别;
- 明白条件语句和循环语句的作用;
- 知道switch语句的用法,以及它同二元选择的不同;
- 使用where语句为条件语句增加额外的条件限制;
- 知道函数、方法及构造器的不同之处;
- 知道类、结构体和枚举之间的不同;
- 知道如何使用继承(inheritance)和协议(protocol),以及它们背后的基本原理;
- 知道如何使用Xcode的快速帮助来确定变量隐式类型以及额外信息;
- 了解如何导入和使用UIKit框架。
1.2 基本数据类型
1.2.1 常量和变量:
常量:指的是值在定义时指定,并且运行时保持不变的量。
变量:指的是值可以随意改变的量。
一个常量也被称为不可变量(immutable),即它的值在运行时不能被改变;而变量则被称为可变量。
如果确定一个值在代码中不会被改变,则可以将它定义为一个常量。
1.2.2 常量和变量的定义
在Swift中,用let关键字来定义常量,用var关键字来定义变量:
var myVariable = 42 //定义一个变量myVariable,值为42
myVariable = 50 //改变myVariable的值成50
let myConstant = 42 //定义一个常量myConstant,值为42
1.2.3 类型推断
Swift中所有的常量或变量都有自己的数据类型,但并不是每次都需要进行显式类型声明。在实际中,可以直接为变量或常量提供一个值,然后由编译器来推断它的数据类型。上面的代码中,编译器推断myVariable的类型为integer,因为它是由一个整型数据初始化而来的,这个由编译器确定量值类型的过程被称为类型推断(type inference)。并且一旦常量或变量拥有类型,便无法再次修改。
如果用于初始化的值无法提供足够的类型信息(或者没有在定义变量的时候对其进行初始化),此时就必须显式指定类型:
let implicitInteger = 70 //常量类型为integer
let implicitDouble = 70.0 //常量类型为double
let explicitDouble: Double = 70 //显式指定类型为double,即使用于初始化这个常量的数为一个整型数
1.2.4 类型转换
Swift中不会进行隐式类型转换。如果你想将一个值转换为其他类型,需要显式地进行类型转换。下面的代码中将一个整型转换为一个字符串类型:
let label = "The width is "
let width = 94
let widthLabel = label + String(width) //将width转换为字符串类型,并连接到label字符串的尾部
还有一种简单的方法可以实现上面代码中的功能:将数值写在括号内,然后再加上“\”前导。这个方法被称为字符串插值法(string interpolation):
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples." //I have 3 apples.
let fruitSummary = "I have \(apples + oranges) pieces of fruit." //I have 8 pieces of fruit.
1.2.5 可选类型
(optionals)
使用可选类型来表示那些值可能是nil的量。可选类型量或含有值,或包含nil(即无值)。在变量类型之后添加一个问号“?”表示可选类型:
let optionalInt: Int? = 9
1.2.6 可选类型解包
(unwrap)
为获取可选类型量的值,需要对其进行解包(unwrap,对应封包wrap),在后面将学习如何对可选类型量进行解包操作(unwrapping)。这里有一个最为直接的解包办法,就是使用强制解包操作符“!”,需要注意被解包的可选类型量值必须非空:
let actualInt: Int = optionalInt! //对optionalInt解包后,将其赋值给actualInt
可选类型在Swift中使用十分普遍,尤其是在尝试类型转换时:
var myString = "7"
//possibleInt的声明自动为:var possibleInt : Int?
var possibleInt = Int(myString) //possibleInt值为7
//若将myString改变为无法转换成整型的值,则possibleInt为nil
myString = "banana"
possibleInt = Int(myString) //possibleInt值为nil
代码中,possibleInt的值为7,因为myString字符串里为“7”。但是如果你改变myString的值,让它无法转换为整型,那么possibleInt的值就会是nil。(这样的好处是不用考虑myString是否可以转换为整型,编程更加灵活)
1.2.7 数组
(Array)
数组:是一个包含多个元素的有序集合。
数组可以对自己内部的所有元素进行追踪。使用方括号“[]”建立数组,如果想访问数组中某个元素,则在方括号内指定元素下标进行访问,并且,数组起始下标为0:
//定义数组ratingList
var ratingList = ["Poor", "Fine", "Good", "Excellent"]
//修改第二个元素为OK
ratingList[1] = "OK"
//打印数组
ratingList
可以使用构造器(initializer)语法建立一个空数组(之后会学习更多关于构造器的相关内容):
// 建立一个空数组,元素类型为String
let emptyArray = [String]()
1.2.7 隐式已解包可选类型
(implicitly unwrapped optional)
隐式已解包可选类型:指的是不需要每次访问都进行解包操作的可选类型。
隐式已解包可选类型假定该类型量的值一旦初始化后就一直都是非空。
使用“!”代替"?"进行定义:
var implicitlyUnwrappedOptionalInt: Int!
实际使用时很少自定义隐式已解包可选类型,它们更多地被用在界面接口和源代码之间的连接追踪中(即Outlet中)。
1.3 流程控制
Swift中有两类流程控制语句:条件语句,如if或switch。循环语句,如for in以及while。
1.3.1 条件语句:
let number = 23
if number < 10
{
print("The number is small")
}
else if number > 100
{
print("The number is pretty big")
}
else
{
print("The number is between 10 and 100")
}
1.3.2 语句嵌套
下面代码中在if-else中嵌套了一个for-in语句(for in 语句按顺序遍历集合中每一个元素):
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores
{
if score > 50
{
teamScore += 3
}
else
{
teamScore += 1
}
}
print(teamScore)
1.3.3 可选绑定
在if中使用可选绑定(optional binding)来判断可选类型量中是否包含有值:
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
//let name = optionalName,若optionalName不空,则条件为真
if let name = optionalName
{
greeting = "Hello, \(name)"
}
如果可选类型的值为nil,则条件为假。反之,则将可选类型变量optionalName解包并赋值给常量name。
1.3.4 where语句
利用where语句,可以在单一if条件判断中绑定多个值,即利用where可以扩展条件语句的判断范围。下面的例子中,只有当绑定的所有值和所有条件都真时才执行if后面的语句块内容:
var optionalHello: String? = "Hello"
//当optionalHello非空,且hello的前缀为H,并且optionalName非空,才为真
if let hello = optionalHello where hello.hasPrefix("H"), let name = optionalName
{
greeting = "\(hello), \(name)"
}
1.3.5 switch语句
switch语句支持任意类型数据的判断,以及多种比较操作:并不局限于整型和判等测试。下面的例子中对vegetable字符串进行判断,匹配相应条件:
let vegetable = "red pepper"
switch vegetable {
case "celery":
let vegetableComment = "Add some raisins and make ants on a log."
case "cucumber", "watercress":
let vegetableComment = "That would make a good tea sandwich."
case let x where x.hasSuffix("pepper"):
let vegetableComment = "Is it a spicy \(x)?"
default:
let vegetableComment = "Everything tastes good in soup."
}
需要注意的是:每一个switch都必须有一个default。
在switch语句中同样可以使用where来添加更多判断条件。并且不同于if,如果有逗号分隔的多个条件,则只要任意一个条件满足,就执行对于语句。
当执行完匹配的case后,程序会自动退出switch语句(不用再break了)。
另外,switch语句必须拥有一个default分支。
1.3.6 循环区间
可以使用区间(Range)来表示循环下标。使用半开区间操作符“..<”指定下标范围:
var firstForLoop = 0
for i in 0..<4 {
firstForLoop += i
}
print(firstForLoop)
半开区间操作符中不包含上界,即上面的例子中0..<4,实际是从0到3循环4次。如果需要包含范围的上界,则可以使用闭区间操作符“...”。
var secondForLoop = 0
for _ in 0...4 {
secondForLoop += 1
}
print(secondForLoop)
上面的代码中循环区间为0到4,共5次循环。下划线“_”表示一个通配符,即当你不需要使用特定循环变量的时候,就可以用下划线代替特定循环变量。
1.4 函数和方法
1.4.1 函数
(function)
函数:指的是一段有名字的,可重用的代码段。它可被用在程序中的许多地方。
使用func来声明一个函数(function)。函数可包含0到多个参数,以"名字:类型"的格式书写。函数参数的作用是当函数调用时,可传递更多的信息到函数中。函数也可以有返回值,写在“->”后面。返回值的作用是当函数调用结束时,返回函数调用的结果。而函数的实现则是写在大括号内:
func greet(name: String, day: String) -> String
{
return "Hello \(name), today is \(day)."
}
使用函数名后面跟上参数列表的形式来调用函数,只有第一个参数不用写参数名:
greet("Anna", day: "Tuesday")
greet("Bob", day: "Friday")
greet("Charlie", day: "a nice day")
1.4.2 方法
(method)
在一个特定类中定义的函数称为方法(method)。方法被显式绑定到该特定类上,也只能被该类所调用(或是该类型的子类)。在之前的例子中,有一个String类型的方法:hasSuffix(),如下所示:
let exampleString = "hello"
if exampleString.hasSuffix("lo") {
print("ends in lo")
}
如你所见,使用点操作符“.”来调用该类型中的方法。
调用时第一个参数省略参数名,其余各个参数都必须写上名称。例如下面代码中,该方法有两个参数,只写出第二个参数的名称“atIndex:”(代码可读性得到很大提升):
var array = ["apple", "banana", "dragonfruit"]
array.insert("cherry", atIndex: 2)
array
1.5 类和构造器
在面向对象编程中,程序活动大多都依赖于程序内部对象之间的交互。
对象(object):是类的实例化产物。
类(class):可以理解为是该对象的蓝图。
在类中使用属性(property)来存储它自身的额外信息,并且由方法(method)来定义它的行为。
1.5.1 类的定义
使用class后面跟上类名来定义一个类。而类属性的声明语法和常量变量的声明语法类似,不同之处仅在于它们是在类中声明的。类中方法的声明和函数声明类似。
下面的代码中声明了一个Shape类,它有一个numberOfSides属性,并且有一个simpleDescription()方法:
class Shape
{
var numberOfSides = 0
func simpleDescription() -> String
{
return "A shape with \(numberOfSides) sides."
}
}
1.5.2 对象的定义及访问
创建一个类的实例,即创建一个对象。
创建方法是在类名之后加上括号“()”,使用点操作符来访问所创建的实例的属性和方法。这里,shape是一个Shape类实例化出来的对象:
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
1.5.3 构造器
(initializer)
Shape类中还缺少一个重要内容:构造器(initializer)。
构造器实际是类的方法,它可用于初始化类的实例对象。包括初始化所有属性,以及完成一些其他的初始化工作。
您可以使用init来创建一个构造器。
下面例子中定义了一个NamedShape类,它含有一个构造器,构造器接收一个name参数:
class NamedShape
{
var numberOfSides = 0
var name: String
init(name: String)
{
self.name = name
}
func simpleDescription() -> String
{
return "A shape with \(numberOfSides) sides."
}
}
这里使用self关键字来区分类的属性名name和构造器参数名name。
所有的属性都需要被赋值(在声明时赋值,或是在构造器中进行赋值)。
在类名后面加上括号来调用构造器,而不需要使用init来调用它。当调用构造器时,需要写出所有的参数名,如下所示:
let namedShape = NamedShape(name: "my named shape")
1.5.4 类的继承和方法覆盖
通过继承(inherit),一个类可以拥有另外一个类行为。被继承的类称为父类(superclass),而继承了父类行为的类称为子类(subclass)。继承的语法格式为:子类名:父类名。一个子类只能继承一个父类,以此类推,从而构成了一个树状的继承层次关系。
子类中的方法覆盖父类中同名方法时,需要写上override,若在覆盖时不写override,则编译器会报错。另外,编译器也会检查那些写了override的,但实际上并没有覆盖父类方法的子类方法。
下面的代码中定义了Square类,它是NamedShape的子类:
class Square: NamedShape {
var sideLength: Double
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
func area() -> Double {
return sideLength * sideLength
}
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
let testSquare = Square(sideLength: 5.2, name: "my test square")
testSquare.area()
testSquare.simpleDescription()
1.5.5 构造器的写法
需要注意,Square类的构造器进行了三个操作:
- 设置Square类属性sideLength;
- 调用父类NamedShape的构造器;
- 修改父类NamedShape的属性numberOfSides。
这样可以完成更多的初始化工作。
实际上,有时需要初始化工作不总是成功,因为如果提供了超出范围的初始化参数,则会初始化出错误的对象。
有时可能会初始化对象失败的构造器称为可失效构造器(failable initializer)。一个可失效构造器有可能返回nil。使用init?来声明一个可失效构造器:
class Circle: NamedShape {
var radius: Double
init?(radius: Double, name: String) {
self.radius = radius
super.init(name: name)
numberOfSides = 1
if radius <= 0 {
return nil
}
}
override func simpleDescription() -> String {
return "A circle with a radius of \(radius)."
}
}
let successfulCircle = Circle(radius: 4.2, name: "successful circle")
let failedCircle = Circle(radius: -7, name: "failed circle")
构造器可以有一些相关扩展。指定构造器(designated initializer)指示它是类的一个主要构造器。任何的类构造器最终都要调用指定构造器。一个快捷构造器是一个次要的构造器,它主要用于添加额外行为或进行自定义操作,但是也必须最终调用指定构造器。这主要和次要构造器分别使用designate和convenience关键字进行定义。
如果在构造器后面跟上了required关键字,则表示每一个该类的子类都必须重写该构造器。
1.5.6 对象类型转换
类型转换是一个检测对象类型的方法,即将该对象看作是它的继承层次中的另外的父类或子类对象来对待。
某类型的常量或变量可能实际是该类的子类实例(父类指针引用子类对象)。如果你遇到这样的情况,你可以尝试使用类型转换操作符将它向下类型转换为子类类型。
由于向下类型转换可能会失败,故类型转换操作符有两种不同的形式。第一种是可选类型的形式,即“as?”,它返回一个你尝试转换的目标类型对应的可选类型值。第二种是强制转换形式,即“as!”,尝试转换并强制解包。
如果你认为类型转换可能会失败,则使用可选类型转换操作符“as?”。这种形式的类型转换总会返回一个可选类型结果,如果转换失败,则会返回nil。你可以通过返回结果来查看转换是否成功。
如果你确信类型转换一定成功,则可以使用强制类型转换操作符“as!”。但如果转换不成功,则这个形式的向下类型转换将导致运行时错误。
下面例子中使用可选类型转换操作符检查shape数组中的每一个shape对象是一个circle还是一个square。如果对应形状找到,则计数器加一,最后打印计数结果:
class Triangle: NamedShape {
init(sideLength: Double, name: String) {
super.init(name: name)
numberOfSides = 3
}
}
let shapesArray = [Triangle(sideLength: 1.5, name: "triangle1"), Triangle(sideLength: 4.2, name: "triangle2"), Square(sideLength: 3.2, name: "square1"), Square(sideLength: 2.7, name: "square2")]
var squares = 0
var triangles = 0
for shape in shapesArray {
if let square = shape as? Square {
squares++
} else if let triangle = shape as? Triangle {
triangles++
}
}
print("\(squares) squares and \(triangles) triangles.")
Start Developing iOS App(Swift)未完待续