SWIFT之旅

11k 词

对于一个新语言的第一个程序来说,比较传统的搞法是,在屏幕上,打印一行”hello,word” 。看看swift吧,一行语句就能搞定。

println(“Hello, world”)
如果你之前玩过C或是Objective-C, Swift的这种语法你可能眼熟。这一行代码就完成了这个程序,你都不用提前引用那些像输入输出控制或是字符串那些乱七八糟的库。程序的代码的全局范围有效的,所以,你都不需要像main函数的那种东西,你甚至也不需要在每个语句的后面加上一个分号。

这篇小文会提供给你足够的信息,给你秀一下,如何完成一个编程任务。如果有一些东西你暂时搞不懂,不用担心。几乎所有的细节介绍都会在本书的其他部分出现。

提示:

最好的经验就是,在Xcode中,打开本篇涉及到的例子代码,那样就允许你编辑代码,还可以立马的看到运行效果。

简单的变量值
使用let关键字定义一个常量,使用var关键字定义一个变量。这个常量值不需要再编译的时候知道。但你必须进行一次赋值。就是说,你可以使用一个常量命名一个值,再很多的地方使用。

var myVariable = 42
myVariable = 50
let myConstant = 42

无论是常量还是变量,都必须和你给他赋的值,保持相同的类型。可但是,你不需要总是显式的这么做。在创建常量或是变量的时候赋一个值,然后让编译器去推测类型。在上面的这个例子,编译器推测myVariable 是一个整形,因为她的初始化值是一个整数。

如果初始化值,没有提供一个足够的信息(或是压根就没有初始化值),特殊的类型,使用引号在变量的后面进行标注。

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70

实践
显式的创建一个浮点类型的常量,并赋值4。

变量时不能隐式的转换成其他类型的。 如果你需要转换一个值到不同的类型。显式的生成一个想要类型的实例。

let label = "The width is "
let width = 94
let widthLabel = label + String(width)
实践
把最后一行的String去掉,你猜你会得到一个什么错误?
有一个简单的方法包含一个字符串变量: 把值写到一对括号里,然后在双括号的前面写一个反斜杠,如下:

let apples = 3
let oranges = 5
let appleSummary = “I have (apples) apples.”
let fruitSummary = “I have (apples + oranges) pieces of fruit.”
实践
使用()在字符串中包含一个浮点值和包含一个特定的名字。

使用括号和中括号创建数组和字典。在中括号中写索引值或是键来访问他们的元素。

var shoppingList = [“catfish”, “water”, “tulips”, “blue paint”]
shoppingList[1] = “bottle of water”
var occupations = [
“Malcolm”: “Captain”,
“Kaylee”: “Mechanic”,
]
occupations[“Jayne”] = “Public Relations”
下面是创建一个空数组和空字典的语法。

let emptyArray = String
let emptyDictionary = Dictionary<String, Float>()

如果类型信息可以被推测出来,你可以写一个像[]的空数组和一个像[:]的空字典的例子。如果你想为一个函数设定形参变量。

shoppingList = [] // Went shopping and bought everything.
控制流

使用if和switch去创建条件判断语句,使用for-in,for,while和do-while去创建循环。括号周围都是条件或是循环变量选项,并且周围需要有大括号。

let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
teamScore
在if判断语句中,条件必须是布尔型表达式,也就是说想if score {} 这种表达是错误的,不能隐式的和零进行比较。

var optionalString: String? = “Hello”
optionalString == nil
var optionalName: String? = “John Appleseed”
var greeting = “Hello!”
if let name = optionalName {
greeting = “Hello, (name)”
}
你可以一起使用if和let语句,值的选项表达式。选项值要么包含一个值,要么包含nil去指向一个值。问好表达式是用值的类型区标记一个值得选项。

实践
把optionName指向nil空,greeting的值会是多少?如果optionName指向了nil空,加上一个else分支,去设定不同的greeting值。

如果条件值是空,条件表达式的结果是false假,并且会跳出大括号。其他情况,其他的条件值是被展开的,并且是用let赋值的常量,让展开的值在代码块之内。

switchs 支持各种类型的语句和广泛的变量比较操作,而且不局限于整数和等值测试。

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.”
}

实践
如果把default分支去掉,你会得到一个什么错误那?

如果在switch case 段内,匹配执行了相应的代码,不会继续执行其他分子段的代码。所以这个不需要在每个switch分子段内,显式的声明break语句。

你会使用for-in语句去迭代字典数据结构, 只要提供key-value简直值所对应的主键。

let interestingNumbers = [
“Prime”: [2, 3, 5, 7, 11, 13],
“Fibonacci”: [1, 1, 2, 3, 5, 8],
“Square”: [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
largest

实践

添加一个变量去跟踪最大值,并且得到那个最大值。

使用while语句去重复的执行代码,直到循环条件发生了变更。循环条件的判断也可以再结尾出现,但可以确定的是,这种情况下,循环至少要执行一次。

var n = 2
while n < 100 {
n = n * 2
}
n
var m = 2
do {
m = m * 2
} while m < 100
m
你仍然可以使用数字索引的说话呢方式,给出一个索引的范围值去显式的初始化,还有循环条件和递增的步长, 下面的两个循环异曲同工。

var firstForLoop = 0
for i in 0…3 {
firstForLoop += i
}
firstForLoop
var secondForLoop = 0
for var i = 0; i < 3; ++i {
secondForLoop += 1
}
secondForLoop
使用…指定上限值范围,使用…指定一个区间的值。

函数与闭包

使用func进行函数声明,调用函数使用下面的名字,使用括号内参数列表。使用->符号来去本函数的返回类型。

func greet(name: String, day: String) -> String {
return “Hello (name), today is (day).”
}
greet(“Bob”, “Tuesday”)
实践

消除参数day, 添加一个包含今天午餐的特别需求在函数 中。

使用一个复数的元组作为函数返回值。

func getGasPrices() -> (Double, Double, Double) {
return (3.59, 3.69, 3.79)
}
getGasPrices()
函数也可以使用可变长参数, 作为一个数组集合传入值。

func sumOf(numbers: Int…) -> Int {
var sum = 0
for number in numbers {
sum += number
}
return sum
}
sumOf()
sumOf(42, 597, 12)
实践

写一个函数,计算函数参数的平均值。

在swift中,函数式可以嵌套的, 被嵌套函数可以使用嵌套函数中的变量。你可以使用嵌套函数组织一个长的或是复杂的代码块。

func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()
嵌套函数都是首类类型, 就是说嵌套函数可以使用被嵌套函数的结果作为函数返回值。

func makeIncrementer() -> (Int -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
函数可以作为函数的参数。并共享形参。

func hasAnyMatches(list: Int[], condition: Int -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(numbers, lessThanTen)
函数其实是一种特殊的函数分支, 你可以写一个没有名字的分子语句块,并用大括号括起来, 使用in关键字来区别函数块的参数和返回值。

numbers.map({
(number: Int) -> Int in
let result = 3 * number
return result
})
实践

重写这段closure封闭代码为所有的奇数返回零。

你有一些列的选项让封闭块写的更简洁。如果一个封闭代码块的类型已知,比如一个回调授权,你可以省略参数的类型,他是一个返回类型,或是两个,单体封闭段代码隐式的返回他们自己的段。

numbers.map({ number in 3 * number }
你而引用参数名称代替数字,在短的闭包段中,这种办法很灵。

把闭包作为最后一个参数传给函数,可以在后面的大括号立即出现。

sort([1, 5, 3, 12, 2]) { $0 > $1 }
对象和类

使用class关键字创建一个新类。只要在同类的上下文中,类属性的声明的方式和一个变量或是常量是相同的。函数方法同理。

class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return “A shape with (numberOfSides) sides.”
}
}
实践

创建一个常量属性,在类中添加一个有参数的方法。

通过在类名的后面紧跟一个括号来生成类的实例。使用点语法规则去访问类的属性方法。

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
这个版本的Shape类貌似少了写东西,引用一个初始化方法去设定类,当类的实例创建的时候,使用init方法完成初始化设定工作。

class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name: String) {
self.name = name
}
func simpleDescription() -> String {
return “A shape with (numberOfSides) sides.”
}
}
注意self如果初始化name参数。在类的实例创建的时候去执行初始化方法。每个属性都需要赋值,要么在类中直接声明赋值(例如: 变量numberOfSide)要么通过初始化方法初始化。(例如:变量name)

如果想在类销毁的时候,去做一些终了清理动作,那就使用deinit方法去实现。

子类的名称放到父类名称的前面,用分号隔开。子类不一定有父类,其实你也可以省略父类。

子类方法覆盖父类方法实现,使用override方法,没有ovverride,编译器会检测一个错误,编译器也检测子类是否覆盖了父类。

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 test = Square(sideLength: 5.2, name: “my test square”)
test.area()
test.simpleDescription()
创建一个NamedShape的子类Circle,使用初始化器初始化radius属性,在Circle类中实现area和describe方法。

使用脚手架方法设定和读取属性值。

class EquilateralTriangle: NamedShape {
var sideLength: Double = 0.0
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 3
}
var perimeter: Double {
get {
return 3.0 * sideLength
}
set {
sideLength = newValue / 3.0
}
}
override func simpleDescription() -> String {
return “An equilateral triagle with sides of length (sideLength).”
}
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: “a triangle”)
triangle.perimeter
triangle.perimeter = 9.9
triangle.sideLength
perimeter设定,有一个显式的名称 newValue. 你可以提供隐式名字在set代码段的括号范围内。

注意,初始化EquilateralTriange类分3步:

  1. 声明子类,设定属性值。

2.调用父类的初始化函数。

3.改变父类的属性值,额外的设置工作使用,getters 和 setters方法。

如果你不需要计算,但是仍然需要在set新变量之前或之后执行代码,使用willSet和didSet.例如, 例如, 计算三角的边长和计算矩形的边长相同。

类的方法和函数有一个很重的区别。函数的参数名只在函数中使用, 方法在调用的时候可以使用缺省值,在方法的调用过程中,函数有相同的参数名称,你可以再方法内指定别名。

class Counter {
var count: Int = 0
func incrementBy(amount: Int, numberOfTimes times: Int) {
count += amount * times
}
}
var counter = Counter()
counter.incrementBy(2, numberOfTimes: 7)
形参是否可写?之前的操作想一个方法,属性和下标。如果之前的值是nil, 每个变量后都有一个? 无视并且整个表达式的的值都是nil, 其他情况,选项值是展开的,每个后面都?作用于整个展开值。在两种情况,整个表达式的值都是选项值。

let optionalSquare: Square? = Square(sideLength: 2.5, name: “optional square”)
let sideLength = optionalSquare?.sideLength
枚举和结构体

使用eum去创建一个枚举。像类和其他名称类型, 枚举有一个方法和她关联。

enum Rank: Int {
case Ace = 1
case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
case Jack, Queen, King
func simpleDescription() -> String {
switch self {
case .Ace:
return “ace”
case .Jack:
return “jack”
case .Queen:
return “queen”
case .King:
return “king”
default:
return String(self.toRaw())
}
}
}
let ace = Rank.Ace
let aceRawValue = ace.toRaw()
实践

写个函数,使用他们的RAW值 比较两个rank值。

以上的例子,raw值类型是一个整形枚举值,你可以只指定第一个RAW值,raw值得赋值是哟顺序的,你也可以使用字符串和浮点数枚举值。

使用toRaw和fromRaw函数去在raw值和枚举值间进行转换。

if let convertedRank = Rank.fromRaw(3) {
let threeDescription = convertedRank.simpleDescription()
}
枚举的成员变量是一个实际的数值,写raw值得方式,不仅仅是一种,事实上,没有意义的raw值得这种情况,你不能只提供一个。

enum Suit {
case Spades, Hearts, Diamonds, Clubs
func simpleDescription() -> String {
switch self {
case .Spades:
return “spades”
case .Hearts:
return “hearts”
case .Diamonds:
return “diamonds”
case .Clubs:
return “clubs”
}
}
}
let hearts = Suit.Hearts
let heartsDescription = hearts.simpleDescription()
实践

添加一个color方法到Suit中,返回一个黑桃和黑梅花,返回一个红心和红方片。

注意,两种方法应用上面的红桃成员,当给hearts赋一个常量,枚举成员Suit, Hearts是原名引用,因为常量没有显式指定类型。在switch分支判断内,枚举使用简写形式,Hearts因为slef的值是已知的,如果类型已知,你尅在任何的时候使用简写形式。

使用struct关键之创建结构体。结构体是支持许多相同类型的类,包括方法和初始值化器。类和结构体之间最大的的区别是结构体在你的代码中是传值得,而类是传引用的。

struct Card {
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return “The (rank.simpleDescription()) of (suit.simpleDescription())”
}
}
let threeOfSpades = Card(rank: .Three, suit: .Spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
实践

添加一个创建一个完成扑克牌方法,一个卡时候各种组合情况。

一个枚举成员可以给一个枚举实例赋值,相同的枚举成员可以赋予不同的值。你创建一个实例的时候,提供一个赋值。管理安置和rawv值是不同的,一个枚举的raw值可以赋予所有的实例,你定义一个枚举然后赋一个raw值。

例如,考虑从服务器上请求日出日落时间需求,服务器要么返回一个有效信息,要么返回一个错误信息。

enum ServerResponse {
case Result(String, String)
case Error(String)
}
let success = ServerResponse.Result(“6:00 am”, “8:09 pm”)
let failure = ServerResponse.Error(“Out of cheese.”)
switch success {
case let .Result(sunrise, sunset):
let serverResponse = “Sunrise is at (sunrise) and sunset is at (sunset).”
case let .Error(error):
let serverResponse = “Failure… (error)”
}
实践
添加第三个服务器相应分支。
注意如何从ServerResponse截取日出和日落的日期,如另一个想匹配的分支。
协议与扩展
使用protocol关键字声明一个协议。
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
类,枚举,结构体都可以采用协议。

class SimpleClass: ExampleProtocol {
var simpleDescription: String = “A very simple class.”
var anotherProperty: Int = 69105
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = “A simple structure”
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription

class SimpleClass: ExampleProtocol {
var simpleDescription: String = “A very simple class.”
var anotherProperty: Int = 69105
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = “A simple structure”
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription
实践
写一个枚举来遵循一个协议。
提示:使用mutating关键字声明SimpleStructure去标示一个可以修改结构体的方法。SimpleClass不需要任何的方法mutating标示,因为类的方法总是可以改变类的属性。
使用extension添加一个已存在的类型,就一个新的方法可以计算属性,你可以使用一个扩展添加一个协议来适应一个新类型,在任何的地方声明,甚至从框架中引入一个类型。
extension Int: ExampleProtocol {
var simpleDescription: String {
return “The number (self)”
}
mutating func adjust() {
self += 42
}
}
7.simpleDescription
实践

写一个double的扩展类型,添加absoluteValue绝对值属性。

你可以使用协议名称想任何其他的名字类型,例如, 创建一个有不同类型但是遵循一个单独协议的对象集合。当你的工作变量时一个协议类型,方法外的协议定义是不可变的。

let protocolValue: ExampleProtocol = a
protocolValue.simpleDescription
// protocolValue.anotherProperty // Uncomment to see the error
虽然protocolValue是SimpleClass的实时类型,编译器协商一个ExampleProtocol类型,就是说你不能偶然的访问实现了一个辅助协议的类的方法和属性。

泛型

在尖括号内写名字来创建泛型函数或类型。

func repeat(item: ItemType, times: Int) -> ItemType[] {
var result = ItemType
for i in 0…times {
result += item
}
return result
}
repeat(“knock”, 4)
你可以创泛型方法,泛型函数, 如类,枚举,结构体。

// Reimplement the Swift standard library’s optional type
enum OptionalValue {
case None
case Some(T)
}
var possibleInteger: OptionalValue = .None
possibleInteger = .Some(100)
使用where关键字在类型名称的后面,指定一个列表需求,例如, 需要一个实现协议类型, 需要实现两个相同的类型,或是需要一个类有特定的父类。

func anyCommonElements <T, U where T: Sequence, U: Sequence, T.GeneratorType.Element: Equatable, T.GeneratorType.Element == U.GeneratorType.Element> (lhs: T, rhs: U) -> Bool {
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
}
}
}
return false
}
anyCommonElements([1, 2, 3], [3])
实践

修改anyCommonElments函数,让函数返回一个有任意两个序列的数组。

这种简单的情况,你可以省略where关键字,在引号的后面简单的写一个协议或是类名。的写法,等同于