作者:老镇
出版社:《无线电》、《高保真音响》杂志社
格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT
Swift语言实战晋级试读:
前言
在2014年的苹果开发者大会上,苹果公司发布了新的开发语言——Swift语言,可以与Objective-C共同运行于Mac操作系统和IOS平台。用于开发基于苹果平台的应用程序。
全世界为之轰动。
这是一门更加简单、安全、高效和强大的语言,我也随之加入学习Swift的大洪流。
出于兴趣,我在网络上制作Swift的实战视频。有幸遇见人民邮电出版社的编辑,才有了本书面世的可能。
于是好不容易戒掉熬夜的我,又开始感悟熬夜。幸好每当夜深人静,女神也熟睡的时候还有萌猫——喵小萌陪伴着我。它常常睡在我的手边,这样我的手就能在鼠标和猫背之间做切换,也算是一种乐趣。
0.1 本书特点
本书是一本介绍Swift实战的实用图书,旨在帮有一定编程基础的同学能够快速上手Swift。
本书的结构是先讲解了Swift语言的精要,没有基础的同学可以学习,有基础的同学可以当做复习。
接着讲解如何用Swift控制UIKit控件。
实战分为两部分,第一部分是基于UIKit的应用实例,第二部分是基于SpriteKit的游戏编程。两部分的实战都是采取循序渐进的方式安排从简到繁的实例。在讲解实例的时候采取的是迭代的方式,先构建基础,然后一点一点完善功能。
0.2 本书读者
适合初学者,有一定编程经验的读者能够更快地掌握。第1章Swift开发环境的搭建
想要进行 Swift 的学习,就必须有开发环境。简单的说就是装好了 Xcode 的 Mac 系统。接下来我们就简单地了解一下这方面的内容。
学习目标
一、知道去哪里下载Xcode
二、了解装Mac OS的几种方法1.1下载Xcode
Xcode是苹果公司推出的编程工具,类似于微软出品的 Visual Studio,编写 Java 的 Eclipse,开发Flash的Flash IDE。常说工欲善其事必先利其器,所以我们首先要知道该去哪里下载Xcode,这里给出以下几个途径。
途径一:通过 AppStore 下载。如图1-1 所示,我们打开 AppStore,在右上角的搜索框输入“Xcode”,在搜索结果里面就能看到Xcode了(见图1-2)。图1-1 打开AppStore图1-2 搜索到Xcode
从 AppStore 里面下载的 Xcode 是苹果公司发布的正式版,如图1-2 所示。如果要尝鲜最新版,请接着往下看。
途径二:从 Swift 的官方博客下载,该博客的网址是:https://developer.apple.com/swift/blog/,进入网站后,点击“Resources”就能看到最新版的 Xcode 的下载地址,如图1-3 所示。不过,要从这里下载Xcode需要知道苹果开发者的账号。图1-3 Swift官方博客下载
途径三:从苹果开发者网站进入下载,网址是:https://developer.apple.com/cn/xcode/downloads/。途径三和途径二一样,需要开发者的账号。
途径四:百度搜搜看有没有好心人上传供下载。
本书所用的Xcode版本是6.1正式版,可从AppStore下载到。1.2关于Mac操作系统
Xcode必须装在Mac操作系统里,对Mac操作系统的版本要求是10.9.3及以上版本。刚才我们了解了如何搞到Xcode,现在再来说说如何搞到Mac操作系统。
最简单、最省事的办法当然是去买台苹果电脑。Mac Book Air、Mac Book Pro、Mac Mini等都可以。
除此之外,我们还可以在Windows下用虚拟机安装Mac操作系统。这是最廉价的,但是也是问题最多的,各种卡会让人无法忍受。还会有各种莫名其妙的没有人能说清的问题。所以用虚拟机我们是不推荐的。如果仅仅是体验一下 Swift 这门语言的语法,倒可以尝试一下。虚拟机的安装,大家可以在百度上搜索一下。想偷懒的话,也可以加入以下QQ群:8477435,有高手专门整理了一份虚拟机安装苹果操作系统的文档和虚拟机镜像文件。第2章Swift语法速读
在这一章,我们的目标是快速地将 Swift 的基础部分过一遍,让大家对 Swift 这门语言有个基本概念。我们的代码都在playground中进行演示。2.1基础知识2.1.1 变量与常量
我们使用var来声明变量,使用let来声明常量。一般来说,一个人的名字是不会变的,年龄则是会随着时间增长的。所以我们将名字声明为常量,年龄声明为变量。我们可以在 playground 中写下如下代码。
//有个大叔他叫老镇,今年34岁
let name = ”老镇”
var age = 34
//明年这家伙就35岁啦
var age = 35
//他想改名叫小镇
name = ”小镇”
//常量无法重新赋值,所以就出错了,如图2-1所示。图2-1 变量与常量2.1.2 注释
Swift的注释其实在上一小节已经出现过。一种是单行注释,还有一种多行注释,代码如下。
//我是一行注释
/*
我是多行注释
*/2.1.3 整数
Swift 有 8、16、32 和 64 位有符号和无符号的整数类型分别是 UInt8、UInt16、UInt32、UInt64 和 Int8、Int16、Int32、Int64。同时 Swift 提供了 UInt 和 Int 两种自适应平台的类型。即,在32位平台上,Int就是Int32,UInt就是UInt32。在64位平台上,Int就是Int64,UInt就是UInt64。2.1.4 浮点数
Swift有两种浮点数类型,64位浮点数的Double和32位浮点数的Float。2.1.5 类型推测
在Swift中,可以不用显式的指定数据类型,Swift能自行推测出数据类型。例如:
let name = “老镇”
//name 会被推测为 String 类型
var age = 34
//age 会被推测为 Int类型2.1.6 数值型类型转换
在 Swift 中,数值类型之间的类型转换很简单,语法是数值类型(被转换的数) ,让我们在playground中验证一下,代码如下。
//那个人身高1.73m
let height = 1.73
//将height转为Int类型
let iHeight = Int(height)
//将height转为Float类型
let fHeight = Float(height)
//将height转为Double类型
let dHeight = Double(height)
从图2-2所示的右边部分可以看出类型转换是成功了。图2-2 数值类型间的类型转换2.1.7 布尔值
Swift的布尔值类型为Bool,它有两个布尔常量——true和false。2.1.8 元组
元组能够把多个值组合成一个复合值,组成元组的值可以是任意类型。我们用元组来描述一下那个叫老镇的大叔。
//有个大叔他叫老镇,年龄34岁,身高1米73
let 大叔 = ("老镇",34,1.73)
我们可以这样来分解一个元组,在playground中输入如下代码。
//将元组分解成单独的常量
let (name,age,height) = 大叔
println("大叔叫:\(name)")
println("大叔年龄:\(age) 岁")
println("大叔身高:\(height) 米")
在playground中我们能看到如下结果(见图2-3):图2-3 分解元组
有时我们只需要取元素中的某个值,忽略的其他值可以用“_”来代替。就像下面的代码那样。
let (_,age,_) = 大叔
println("大叔的年龄是:\(age) 岁")
还能通过下标来获取元组中的值。
println("大叔叫:\(大叔.0)")
println("大叔年龄:\(大叔.1) 岁")
println("大叔身高:\(大叔.2) 米")
还可以在元组中给元素命名,让我们在playground中写下如下代码:
let 大叔2号 = (name:"老镇",age:34,height:1.73)
println("大叔2号叫:\(大叔2号.name)")
println("大叔2号年龄:\(大叔2号.age) 岁")
println("大叔2号身高:\(大叔2号.height) 米")
我们在playground中可以看到如下效果(见图2-4)。图2-4 给元素命名2.1.9 可选类型
可选类型是 Swift 的一个特色。它表示一个变量有可能有值,也有可能没有值(nil)。声明方式是“数据类型+问号”。
var age:Int?
这时候age的默认值就是nil了。我们可以在playground中写段代码来描述一下可选类型。
//声明一个
var age:Int?
if age == nil {
println("大叔,你到底多少岁啊?")
}else{
println("大叔,原来你都\(age!)岁了啊")
}
//给可选类型赋值
age = 34
if age == nil {
println("大叔,你到底多少岁啊?")
}else{
println("大叔,原来你都\(age!)岁了啊")
}
我们在playground可以看到的效果如图2-5所示。图2-5 可选类型
当age是一个可选类型的时候,就要用感叹号“!”来读取age的值,也就是这样:age!2.1.10 断言
断言在调试的时候非常有用,就像别的语言中的断点调试。Swift 中的断言通过一个全局函数assert 来实现。它有两个参数:一个表达式,一条信息。若表达式的结果是 false 就会中断程序并打印那条信息。我们在playground中演示一下。
var age = 34
assert(age<18, "如果你小于18岁就不是大叔了")
因为age的值是34,大于18。所以程序终止了,我们可以在playground中看到如下输出(见图2-6)。图2-6 断言
如果我们把age的值改为“14”,程序就可以正常运行了。2.2基本运算符2.2.1 赋值运算符
//常量赋值:
let name = “老镇”
//变量赋值:
var age = 34
//元组赋值:
let (name,age) = (“老镇”,34)2.2.2 数值运算
和大多数编程语言一样,Swift也支持加(+)减(-)乘(*)除(/)的四则运算,以及求余(%)运算。
1 + 2 //加法
4-3 //减法
5 * 6 //乘法
18 / 3 //除法
10 % 6 //求余 余4
9 % 2.5 //浮点求余 余1.52.2.3 自增(自减)运算
Swift也支持对整型和浮点变量加1(++)或者减1(--)的自增(自减)运算。
var age = 34
++age //age为35
--age //age为342.2.4 复合赋值
Swift 也支持+=,-=,*=等这类将运算符和赋值运算组合的操作。例如自增运算(++)其实就能看成+=1。2.2.5 比较运算
Swift支持的比较运算如下。
等于 a==b
不等于 a!=b
大于 a>b
小于 a
大等于 a>=b
小等于 a<=b
恒等 a===b
不恒等 a!==b2.2.6 三元运算符号
三元运算符:(表达式1)?(表达式2):(表达式3)。
它的逻辑是这样:当表达式1值为true的时候就执行表达式2,如果为false就执行表达式3。用Swift来描述就是这样。
let a = 1
let b = 2
// 如果b大于a,c等于3
// 如果b小于a,c等于4
let c = b > a ? 3 : 42.2.7 区间
Swift提供了两种区间的表达方式。
1.全闭区间 1...5 。包含了1,2,3,4,5五个值。
2.半闭区间 1..<5 。包含了1,2,3,4四个值。2.2.8 逻辑运算
Swift支持逻辑非,逻辑与,逻辑或这3个标准的逻辑运算。
逻辑非 !a
逻辑与 a&&b
逻辑或 a||b2.2.9 括号优先级
同样,Swift也能通过括号来明确表达式的优先级。例如
//优先执行a+b,它们的和在除以d
let a = ( b + c ) / d2.3字符串和字符2.3.1 字符串
在Swift中,字符串类型为String,为字符的有序集合。
初始化空字符串的两种方法:
let str1 = ""
let str2 = String()
判断字符串是否为空:
if str1.isEmpty {
println(“字符串为空”)
}2.3.2 字符
我们可以用全局函数countElement来计算字符串的字符数(见图2-7)。
//计算《面朝大海春暖花开》这首诗的字符数
let 面朝大海春暖花开= "从明天起,做一个幸福的人,喂马,劈柴,周游世界;从明天起,关心粮食和蔬菜;我有一所房子,面朝大海,春暖花开;明天起,和每一个亲人通信;告诉他们我的幸福,那幸福的闪电告诉我的,我将告诉每一个人;给每一条河每一座山取一个温暖的名字,陌生人,我也为你祝福,愿你有一个灿烂的前程,愿你有情人终成眷属,愿你在尘世获得幸福,而我只愿面朝大海,春暖花开。"
println("全诗\(countElements(面朝大海春暖花开))个字")图2-7 计算字符串的字符数
在swift中,可以通过加号(+)来连接字符串和字符。
//连接字符,字符串
let str1 = "从明天起,做一个大叔。"
let str2 = "文能控萝莉,武能定御姐。"
let str3 = str1 + str2
println(str3)
也能通过+=来给一个已经存在的字符串添加字符或者字符串。
//通过+=添加字符或者字符串
var str4 = "从明天起,就是一个大叔。"
str4 += "提笔,练武,"
str4 += "周游世界。"
println(str4)
我们通过playground可以看到,+=确实把字符串连接起来了(见图2-8)。图2-8 连接字符串
还可以用“\()”包裹着变量、常量,表达式这种全新的方式与字符串结合在一起。
//连接字符串、变量、常量、表达式
let name = "老镇"
let msg = "有一个大叔,他叫\(name)"
//输出有一个大叔,他叫老镇
最后在字符串与字符这节中我们来说说如何比较字符串。
在Swift中,有三种方式来进行字符串的比较。
1.字符串相等==
2.前缀是否包含 hasPrefix
3.后缀是否包含 hasSuffix
let msg = "有一个大叔,他叫老镇"
let msg2 = "有一个大叔,他叫老镇"
msg == msg2 //true
msg.hasPrefix("有一个") //true
msg.hasSuffix("老镇") //true2.4集合类型
Swift 提供了数组和字典两种集合类型。其中数组是有序存储的,而字典则是无序存储的。需要注意的是Swift的数组里面的数据必须是相同类型的数据。例如都是Int,或都是String。字典亦一样,在一个字典中,所有key的类型必须是相同的,所有value的类型也必须是相同的。2.4.1 数组
现在让我们来创建一个数组。
//创建一个都是字符串的空数组
var 我是空数组:[String] = [String]()
//创建一个存储了字符串的数组,省略类型
var 我最喜欢的漫画 = ["火影忍者","海贼王","死神"]
通过下标来访问数组。
println("我最喜欢的漫画是:\(我最喜欢的漫画[0])")
//输出我最喜欢的漫画是:火影忍者
通过下标修改数组中的数据。
我最喜欢的漫画[0] = "七龙珠"
println("我最喜欢的漫画是:\(我最喜欢的漫画[0])")
//输出我最喜欢的漫画是:七龙珠
可以看到,数组中的数据已经改变。接下来我们来通过append方法来添加数组中的数据。
我最喜欢的漫画.append("进击的巨人")
这时候数组“我最喜欢的漫画”里面的数据就变成了[“七龙珠”,“海贼王”,“死神”,“进击的巨人”]。可以看出通过append方法将数据添加在数组的末尾。
在Swift中还能通过一个下标区间来替换数组中的一组数据。
我最喜欢的漫画[0...2] = ["猎人"]
通过上面的代码,我们将数组“我最喜欢的漫画”前3个数据替换成了“猎人”。于是数据里的数据就从[“七龙珠”,“海贼王”,“死神”,“进击的巨人”]变成了[“猎人”,“进击的巨人”]。
那我们能不能将数据插入到特定的位置呢?当然可以了,我们可以通过 insert 方法来实现。例如我们将“城市猎人”插入到“我最喜欢的漫画”数组的第二位,就可以这么写:
我最喜欢的漫画.insert("城市猎人", atIndex: 1)
既然数组有插入的方法,自然也有移除的方法。那就是 removeAtIndex,我们只要传入要删除数据的索引即可。例如我们删除刚才插入的“城市猎人”,就可以这么写:
我最喜欢的漫画.removeAtIndex(1)
如果我们只想删除数组的最后一个数据,就可以使用removeLast方法。
我最喜欢的漫画.removeLast()
这时候数组中就只剩下“进击的巨人”了,我们可以通过count来获取数组中的数据数量。
我最喜欢的漫画.count
最后我们再来看一看如何遍历数组,这是非常重要的必须掌握的方法。我们先还原一下数组中的数据。
我最喜欢的漫画 = ["火影忍者","海贼王","死神"]
接着我们通过for in 来实现数组的遍历。
for 漫画 in 我最喜欢的漫画 {
println("\(漫画)")
}
//输出如下
//火影忍者
//海贼王
//死神
在Swift中,还能够通过enumerate函数来同时遍历数组的索引与数据。
for (索引,数据) in enumerate(我最喜欢的漫画) {
println("\(索引):\(数据)")
}
//输出
// 0:火影忍者
// 1:海贼王
// 2:死神2.4.2 字典
字典是比数组更为强大的一个存在,它是由键(key)值(value)对组成的集合用逗号分开。先来看看在Swift中如何创建字典。
//创建一个空字典
var 我是一个空字典 = Dictionary
//创建一个有数据的字典
var 我是一个字典:Dictionary
我是一个字典
//简写方式
var 漫画 = ["热血漫画":"海贼王","推理漫画":"名侦探柯南","坑爹漫画":"猎人"]
漫画
那么如何给字典增加新的数据项呢?可以通过键(key)来增加,例如:
漫画["经典漫画"] = "七龙珠"
我们还可以通过键(key)来读取和改变字典的值(value)。
//通过下标来读取字典的值
漫画["热血漫画"]
//通过下表来修改字典的值
漫画["热血漫画"] = "火影忍者"
通过图2-9我们就可以看出,字典的读取与修改起作用了。图2-9 字典的读取与修改
刚才讲了如何增加和修改字典中的数据,那么如何删除字典中的数据呢?也很简单,只要将某个键(key)的值(value)设为nil即可。例如,我们来删掉“热血漫画”。
漫画["热血漫画"] = nil
这时候,漫画字典中就只剩下这些数据了:["经典漫画":"七龙珠", "推理漫画":"名侦探柯南", "坑爹漫画":"猎人"]。也可以使用 removeValueForKey 方法删除数据。那么我们就利用这个方法来删“坑爹漫画”。
漫画.removeValueForKey("坑爹漫画")
说完字典的基本操作,接下来我们来讲讲怎么遍历字典。同数组一样,我们通过for循环来遍历字典。
for (key,value) in 漫画 {
println("\(key):\(value)")
}
//输出
//经典漫画:七龙珠
//推理漫画:名侦探柯南
我们也可以单独遍历字典的键(key)或者值(value),如下。
//遍历key
for key in 漫画.keys{
println("\(key)")
}
//输出
//经典漫画
//推理漫画
//遍历value
for value in 漫画.values{
println("\(value)")
}
//输出
//七龙珠
//名侦探柯南2.5流程控制
和大多数编程语言一样,Swift提供了for和while,也有if和switch,还有break和continue用来进行流程控制。那么就让我们来看看在Swift中它们有没有特殊的地方。2.5.1 for循环
Swift 的 for 循环提供了两种方式,一种是 for in,一种是带条件递增的。让我们用实际代码来看看有啥不同,首先是for in:
//创建一个数组先
var 漫画 = ["火影忍者","猎人","死神","海贼王"]
//for in
// 循环一个区间
for index in 0...3 {
println("\(漫画[index])")
}
//遍历一个数组
for 书 in 漫画{
println("\(书)")
}
//上面的for循环有着同样的输出,如下
//火影忍者
//猎人
//死神
//海贼王
接下来是很常见的带条件递增的for循环:
//for 条件递增
for var i=0;i<漫画.count;i++ {
println("\(漫画[i])")
}
//输出
/火影忍者
//猎人
//死神
//海贼王2.5.2 while循环
讲完了for循环,我们再来看看另外一种循环:while循环。在Swift中的while循环有两种形式。一种是先判断条件,然后再执行代码的比较常用的 while 循环。另外一种是先执行代码然后再进行条件判断的do-while形式。我们先来看看比较常用的while循环。
我们利用 while 写这么一个小程序:我记事的时候是 5 岁,每过一年我都会增加 1 岁。直到 65 岁的时候我实在不想让自己再变老了,就停止了循环。那么代码就该这么写:
//我记事的时候是5岁
var age = 5
//当age小于等于65岁之前每次循环都加1岁,当age不再小于65的时候就退出循环
while age < 65 {
age += 1
}
println("\(age)")
然后我们在playground中可以看到一个很好玩的效果。我们敲完代码,点击图2-10中1的位置,就能在2的位置看到如下效果图了,如图2-10所示。图2-10 while循环
图2-10中2处以图形的形式表现出了一个while循环。横向坐标轴是循环的次数,竖向的坐标轴是每次循环中age的值。playground的魅力就在于此。
接下来我们再用do-while的形式完成上面的代码。
//do-while循环
//时光穿梭又回到了5岁
age=5
do{
age += 1
}while age<65
println("\(age)")
从表面上看,while 和 do-while 没有什么差别。真的是这样吗?我们来修改上面的代码,在循环之前把 age 都设为 65,再来看结果,就会发现 while 没有执行大括号里面的代码,打印出 65。而 dowhile则执行了一次,打印出了66。
总地看来,Swift中的while循环和其他语言的while循环没有差别。2.5.3 条件语句
接下来要登场的是Swift语法中出镜率最高的大明星:if。当if后面的条件语句为true的时候就执行相关代码。例如我们给年龄(age)做一个条件判断,当年龄小于12岁时,我们称之为“正太”,大于30岁称之为“大叔”,那么在18岁和30岁之间称之为“骚年”。
var age = 5
if age < 12 {
println("正太")
}else if age > 30 {
println("大叔")
}else{
println("骚年")
}
依照上面的代码会输出“正太”。当我们修改 age 的年龄为 31 就输出“大叔”,改为 18 就输出“骚年”。
大明星退场了,接下来上场的是switch。与其他语言相比,Swift中的switch要强大得多。例如,我们用Swift来实现之前if的代码。
//switch
switch age {
case 0...11:
println("正太")
case 12...30:
println("骚年")
default:
println("大叔")
}
在上面的代码中,switch的case分支不仅可以与具体值进行匹配,还能匹配值age的值是否在一个区间内。而且和其他语言的switch相比,每个case分支都不需要break来结束分支。
Swift的case分支除了能进行区间匹配,还能进行多值匹配。具体是什么样呢?我们来写这样一个程序 ,通过人名判断属于哪部漫画。下面这段小程序将包含多值匹配与常见的单值匹配。
//switch多值匹配
let name = "路飞"
switch name {
case "路飞","娜美","山治":
println("它是海贼王里的角色")
case "黑崎一护","朽木露琪亚","朽木白哉":
println("它是死神里的角色")
case "小杰":
println("它是猎人里面的角色")
default:
println("它是哪部漫画的角色呢?")
}
我们可以将 name 的值改为“娜美”,“山治”,“黑崎一护”,“小杰”来看看执行结果有什么不同。感受完之后,我们回过头来看 case 分支,前两个分支都是多值匹配(用逗号分隔开)。最后一个 case分支是常见的单值匹配。是不是很有高大上的感觉?还不止呢,switch 还能进行元组匹配。我们来写这么一个程序,通过坐标的值来判断点属于哪个象限(见图2-11)。
//switch的元组匹配
let 坐标 = (2,2)
switch 坐标 {
case (0,0):
println("坐标在原点")
case (_,0):
println("坐标在x轴上")
case (0,_):
println("坐标在y轴上")
case (-3...3 ,-3...3):
println("坐标在长宽为6的正方形内")
default:
println("在什么地方呢")
}图2-11 利用坐标值判断点所属象限
注意 在元组中,下划线“_”能代表所有可能的值。
还有没有什么其他“高大上”的特性呢?有的,用switch还能进行值绑定。所谓值绑定就是将匹配的值在case程序块中进行引用。让我们来看段代码。
//值绑定
var height = 1.73
switch height {
case let h:
println("身高:\(h)")
fallthrough
case 1.73:
println("匹配到1.73的case分支了")
default:
println("嗯嗯,那啥")
}
以上代码中,第一个 case 分支将 height 的值绑定到了常量 h 中,并在代码块中引用并打印了出来。同时大家应该注意到,还有个关键字 fallthrough,这是用来干什么的呢?正常情况下,当 switch匹配到一个case后就不会再匹配其他case。但是,如果我们在一个case末尾写上fallthrough,就可以对下面的case分支再匹配一次。由此可见,fallthrough所起到的就是一个“向下穿透”的作用。
结束了吗?还没有呢。
最后还有一点需要说的,那就是在 switch 的 case 中还能使用 where 关键字来做额外的判断条件。我们还是拿身高来做文章。
var height = 1.73
switch height{
case 1...3 where height == 1.73:
println("case 1")
case 1...3 where height == 2:
println("case 2")
default:
println("default君,我们又见面了")
}
我们可以通过将height的值改为1.73和2来看输出结果。第一个case和第二个case的区间匹配是一致的,在这种情况下对匹配起关键作用的就是where之后的条件。关于swift的switch好像说了好多,让我们去泡杯茶听首歌,休息一会。2.5.4 控制转移语句
和其他语言一样, Swift 也有关键字来提前结束循环体,例如跳过当前循环继续下一轮循环的continue,和直接结束整个循环的 break。还有函数的返回关键字 return,这几个关键字和其他的大多数语言并没有区别。至于fallthrough,我们在上文已经讲过了。2.6函数
这一节,我们来看看Swift中的函数。在Swift中,函数也是有类型的,由传入函数的参数类型和函数的返回类型共同组成。
我们先来用关键字 func 定义一个 sayHi 函数,传入一个参数 name,利用“->”符号返回一个字符串。
//定义函数
func sayHi(name:String)->String{
var str = "你好\(name)"
return str
}
let str = sayHi("老镇")
println(str)
//输出你好老镇
//传入多个值的函数
func sayHi2(name:String,age:Int)-> String{
var str = "有个大叔叫\(name),今年\(age)岁"
return str
}
let str2 = sayHi2("老镇", 34)
println(str2)
//输出有个大叔叫老镇,今年34岁
//无参函数
func sayHi3()-> String{
var str="你够了,我已经打了很多次了招呼了!"
return str
}
let str3 = sayHi3()
println(str3)
//输出你够了,我已经打了很多次招呼了!
//无返回类型的函数
func sayHi4(){
//....
}
//返回多个值的函数
func sayHi5()->(name:String,age:Int,height:Int){
return ("老镇",34,173)
}
let eko = sayHi5()
println("有个大叔叫\(eko.name),他今年\(eko.age)岁,身高\(eko.height)")
//输出有个大叔叫老镇,它今年34岁,身高173
以上就是 Swift 函数的基本形态。那么,和其他语言比较,Swift 的函数总该有些特别的地方,对吗?是的,没错!
Swift 的函数特别的地方在于它的参数,例如,Swift 可以给函数的参数起个对外参数名。什么意思呢?我们通过和sayHi2来做比较:
//传入多个值
func sayHi2(name:String,age:Int)-> String{
var str = "有个大叔叫\(name),今年\(age)岁"
return str
}
let str2 = sayHi2("老镇", 34)
//外部参数名
func sayHi6(ho name:String,HowOld age:Int)->String{
var str = "有个大叔叫\(name),今年\(age)岁"
return str
}
let str6 = sayHi6(who: "老镇", HowOld: 34)
println("\(str6)")
sayHi2 和 sayHi6 返回的字符串是一样的。但我们可以在函数定义上看出有点不同。在 sayHi6中,我们给参数name前面加入了另一个参数名who,在age前面加入了HowOld参数名。那么在调用sayHi6的时候就派上了用场。
let str6 = sayHi6(who: "老镇", HowOld: 34)
对外参数名其实是用来描述参数的,这样可以在调用函数的时候更加明确各个参数的用途。但是,如果函数的参数名本身就很清晰,想让对外参数名和参数名用同一个字符串可不可以呢?可以的,我们来改造改造sayHi6。
func sayHi6(name name:String,age age:Int){
}
//调用的时候就是
sayHi7(name: "老镇", age: 34)
但是这样改造后会不会觉得很怪呢?name name ,age age,像个结巴。别担心,这种情况下有一个便捷的写法,就是在参数名前加个“#”符号。那么继续改造一下sayHi6函数:
func sayHi6(#name:String,#age:Int){
}
这样看上去是不是简洁优雅多了?
一般来说,我们会利用传入的参数进行计算或者其他操作。但是写个很简单的加法函数,它竟然报错了,怎么会这样?
var age = 34
func add(age:Int){
age += 1
}
在执行到age +=1的时候编辑器会有个红红的感叹号,告诉你不能这么操作。为什么呢?那是因为在Swift函数中,参数默认是常量。所以要改变参数的值,就需要在定义函数的时候加上关键字var,之前的函数改后如下:
func add(var age:Int)
讲到这,我们不禁会思考,有没有办法同时改变函数内外的 age 的值呢?答案是肯定的,利用inout关键字。同时在调用函数的时候给参数加上前缀符“&”:
//in-out参数
func changeAge(inout age:Int){
age = 35
}
changeAge(&age)
println(age)
//输出 35
通过playground我们可以看到,函数内外的age的值都被改变了。2.7闭包
闭包其实就是程序块,一共有3种表现形式:
• 函数:函数就是一个有名字的闭包。
• 嵌套函数:包含了其他函数的闭包。
• 闭包表达式:其实就是没有名字的函数。
综上,如果对闭包这个概念不是很清晰,可以把它理解为函数。闭包就是函数,嵌套函数,匿名函数。2.8枚举
Swift 的枚举和大多数语言的枚举没有太大的区别。需要留意的一点是,它的枚举值是没有默认值的。现在我们来看看它的基本语法。我们来定义一个年龄段的枚举,然后通过和switch配合,来判断老镇属于哪个年龄段:
//枚举语法
enum 年龄段 {
case 正太
case 骚年
case 大叔
case 大爷
}
let 老镇的年龄段 = 年龄段.大叔
switch 老镇的年龄段 {
case 年龄段.正太:
println("绝对不可能是正太,一定搞错了")
case 年龄段.骚年:
println("大叔你装嫩呢")
case 年龄段.大叔:
println("果然是一个大叔啊")
case 年龄段.大爷:
println("大叔还年轻着呢")
default:
println("不知道他属于哪个年龄段,真是个奇怪的生物")
}2.9类和结构体
在 Swift 中,类和结构体非常相似。它们都能定义属性,定义方法,定义下标,能偶扩展,能遵循协议。但是类的功能更为丰富些,例如类可以继承,可以对实例进行类型检查,有析构过程,还有引用计数。
接下来,我们使用class来定义一个类,使用struct来定义一个结构体。
//定义一个类
class 女神类 {
}
//定义一个结构体
struct 女神结构体{
}
然后我们给它们添加上属性。
//定义一个类
class 女神类 {
var 相貌 = "95分"
var 三围 = "讨厌,不告诉你"
}
//定义一个结构体
struct 女神结构体{
var 相貌 = "95分"
var 三围 = "讨厌,不告诉你"
}
接着来看看怎么对它们进行实例化
let 女神A = 女神类()
let 女神B = 女神结构体()
实例化了类和结构体后怎么访问属性呢?在 Swift 中是通过“.”来访问的,例如我们想知道女神的相貌和三围:
女神A.相貌 //95分
女神B.三围 //讨厌,不告诉你
以上就是类和结构体相同的地方,接下来我们看看它们之间有什么差异。首先,结构体会有一个设置所有属性值的默认构造器,例如可以在实例化女神结构体的时候给相貌和三围赋值。
let 女神C = 女神结构体(相貌: "100分", 三围: "……")
而类就不会有这样一个默认的构造过程。除此之外,结构体和类还有一些不同的地方。例如结构体和枚举是值类型,而类是引用类型。
那么,我们在结构体和类之间该如何选择呢?根据 Apple 的官方文档建议,符合以下条件的时候推荐实用结构体:
• 封装少量、简单的数据的时候;
• 数值传递时数据是被拷贝的时候;
• 不需继承的时候。
其他时候就考虑用使用类吧。2.10属性
关于属性,其实我们之前接触过很多次。
在上一节女神类和女神结构体中的“相貌”和“三围”都属于存储属性,用来存储某个数据。比较有意思的是,在Swift中还能定义一种延迟赋值操作的延迟存储属性,用关键字lazy来定义,如下:
//定义一个类
class 女神类 {
lazy var 相貌 = "95分"
}
let 女神 = 女神类()
//调用相貌属性
女神.相貌
以上的代码中,当女神类被实例化的时候,它的实例的属性“相貌”并没有被赋值。只有当第一次调用相貌这个属性的时候才会将“95”分赋值给相貌。这就是延迟存储属性。巧妙地使用延迟属性可以提升程序的性能。
除了存储属性,Swift还支持get和set的计算属性。下面我们就来写一个关于萝莉视力的计算存储属性。
class 萝莉类{
var 左眼视力 = 1.3
var 右眼视力 = 1.4
var 平均视力:Double{
get {
return (( 左眼视力 + 右眼视力 ) / 2)
}
set(新平均视力){
左眼视力 = 新平均视力-0.1
右眼视力 = 新平均视力 + 0.1
}
}
}
let 萝莉 = 萝莉类 ()
萝莉.平均视力
//输出 1.35
萝莉.平均视力=1.4
//输出 {1.3 ,1.5}
其中,如果 set 后面没有声明新值的参数名,就可以使用默认的参数名 newValue,于是我们萝莉类的set(新平均视力)就可以这么写:
set{
左眼视力= newValue-0.1
右眼视力= newValue + 0.1
}
如果平均视力只是只读,那么连get都能省略掉,让我们来改造一下萝莉类。
//只读属性
class 萝莉类{
var 左眼视力 = 1.3
var 右眼视力 = 1.4
var 平均视力:Double{
return (( 左眼视力 + 右眼视力 ) / 2)
}
}
let 萝莉 = 萝莉类 ()
萝莉.平均视力
//输出1.35
除了属性的get和set之外,Swift还有两个属性监视器:willSet和didSet。顾名思义,willSet在赋值之前调用, didSet 在赋值之后调用。在 willSet 中如果不指定新值的参数名,则参数名默认为newValue。相应的,在 didSet 如果不指定参数名就会有一个默认的参数名为 oldValue。需要注意的是,这两个监视器在初始化之外的地方才会起作用。让我们写一个正太类来进行代码说明。
//属性监视器
class 正太类{
var 身高 : Int = 130 {
willSet{
println("正太的身高将要变为:\(newValue)")
}
didSet{
println("正太之前的身高为:\(oldValue)")
}
}
}
let 正太 = 正太类 ()
正太.身高 = 135
//输出
//正太的身高将要变为:135
//正太之前的身高为:130
我们还有一个内容要讲的就是类型属性。在结构体和枚举中,类型属性用 static 来声明,在类中用class来声明。我们依旧用代码诠释一下。
struct 某个结构体{
static let 类型属性 = 1024
}
enum 某个枚举{
static let 类型属性 = 1024
}
某个结构体.类型属性
某个枚举.类型属性
class 某个类 {
class var 类型属性 : String{
return "我是类型属性"
}
}
某个类.类型属性
综上,声明类的类型属性的写法是需要注意的,稍有不同,是一个只读的计算属性。2.11方法
和大多数语言一样,Swift 的方法也分为实例方法和类型方法。顾名思义,实例方法只有在类实例化之后才能调用,用关键字 func 来定义。而类型方法则不需,能直接通过类名+“.”+方法名进行调用,在func前加上关键字class来定义。让我们写一个女仆类来诠释一下,这个类里面有个实例方法叫打招呼,有个类型方法叫“暖床”。
//实例方法
class 女仆类 {
//实例方法
func 打招呼(){
println("主人,您回来了。")
}
//类型方法
class func 暖床(){
println("主人,电热毯君已经把床暖好了")
}
}
//调用类型方法
女仆类.暖床()
//输出主人,电热毯君已经把床暖好了
//调用实例方法
let 女仆 = 女仆类()
女仆.打招呼()
//输出主人,您回来了。
需要额外注意的是,当我们实例化类型之后,实例都会有一个隐藏属性叫 self,用来表示实例本身。它的经典应用场景就是:当实例参数名和外部传进来的参数名一样的时候进行区分。例如我们定义一个御姐类,她有个年龄属性 age,还有一个改变年龄的方法,这个方法包含的参数名也是 age,self 华丽登场了。
//self 属性
class 御姐类{
var age = 30
func change(age :Int){
self.age = age
}
}
在方法这节有什么需要特别注意的地方吗?有的。拿刚才我们写的御姐类来作比较,当御姐是一个结构体或者枚举类型的时候,一旦实例化,它的 age 值是不允许改变的。如果我们非要改变结构体或者枚举类型的age值,那么就要在func前面加上mutating关键字。让我们来写一个御姐结构体看看:
struct 御姐结构体{
var age = 30
mutating func change(age:Int){
self.age = age
}
}
方法change前如果没有mutating关键字就会直接报错。2.12下标
嗯,Swift 的下标相当有趣。像数组可以用过索引下标匹配数据,字典可以通过键(key)做下标匹配值(value)。也可以给我们的类、结构体、枚举类型定义自己的下标。它使用关键字 subscript 来定义下标,基本语法如下:
subscript(index:Int)->Int{
get{
}set(newValue){
}
}
其中 subscript 的括号内也可以是字符串。这里让我们写一个女神类,通过下标获取“女神”的年龄。一般“女神”都会隐瞒自己的真实年龄,所以我们返回的值是真实年龄的一半。
class 女神类{
let age:Int = 30
subscript(str:String)->Int{
return age / 2
}
}
let 女神 = 女神类()
女神["年龄"]
//输出 15
下标是可以根据类型的不容来进行重载的。下面我们就来实现一下,通过Int类型的下标来获取“女神”的真实年龄。
//下标
class 女神类{
let age:Int = 30
subscript(str:String)->Int{
return age / 2
}
subscript(index:Int)->Int{
return age
}
}
let 女神 = 女神类()
女神["年龄"]
//输出 15
女神[1]
//输出 30
到目前为止,我们所举的例子和传入的下标似乎都没有什么关系,那么让我们再深入一点。既然身为“女神”,自然有很多备胎,我们可以用代码来实现一下,通过下标来获取第n号备胎的名字。
//下标
class 女神类{
//定义一个备胎数组
let 备胎 = ["潇洒哥","多金男","气质大叔"]
let age:Int = 30
subscript(str:String)->Int{
return age / 2
}
//修改Int类型下标返回值
subscript(index:Int)->String{
return 备胎[index]
}
}
let 女神 = 女神类()
//通过下标获取第0号备胎
女神[0]
//输出潇洒哥2.13继承
终于进入了面向对象的内容,继承一般都是对于类来说的。和大多数语言一样,Swift 的类可以继承另外一个类的属性、方法和其他一些特性。同样也能调用父类的方法、属性、下标。还能重写父类的方法。
为了演示 Swift 中的继承,我们需要先定义一个基类,叫“妹纸”类。它有两个属性,一个是名字name,另一个是年龄age,还有一个打招呼的方法sayHi会进行自我介绍。
//定义一个基类
class 妹纸类 {
var name:String = "一个妹纸"
var age:Int = 18
func sayHi(){
println("你好,我是 \(name) ,今年\(age)岁")
}
}
接下来我们定义一个子类,如果没有意外,“女神”都先是一个“妹纸”。所以我们可以写一个女神类作为“妹纸”类的子类。继承是用冒号“:”来实现。
class 女神类:妹纸类{
}
因为“女神”类继承了“妹纸”类,所以“女神”类可以使用“妹纸”类中属性和方法。那我们就实例化一个“女神”类,然后设置一下name和age,并执行sayHi方法。
let 女神 = 女神类()
女神.name = "缪斯"
女神.age = 22
女神.sayHi()
//输出你好,我是缪斯,今年22岁
从代码的执行结果来看,“女神”类完美地继承了“妹纸”类。讲完继承,我们再说说另外一个重要的概念——重写。
子类除了能够执行基类的方法,还能重写基类的方法。例如,我们创建一个“女王”类来重写一下“妹纸”类的sayHi方法,只需要在func前加上关键字override。
//女王类,继承妹纸类
class 女王类:妹纸类{
override func sayHi() {
println("叫我女王,颤抖吧凡人")
}
}
let 女王= 女王类()
女王.sayHi()
//输出叫我女王,颤抖吧凡人
除了方法之外,还能对属性与以及属性监视器进行重写,同样也是使用 override 关键字。那么假如“女王”类要重写name属性,以及age的属性监视器,则需按照下面的方式编写代码:
//重写属性的get和set方法
override var name:String{
get{
return self.name
}set{
self.name = newValue
}
}
//重写属性监视器
override var age:Int{
willSet{
//代码
}
didSet{
//代码
}
}
讲到这里,关于类的继承就说得差不多了,最后还要讲一点,就是如何防止被重写。我们只需要用final这个关键字就可以了,例如final var,final func,final class func 和final subscript。2.14构造过程
构造过程简而言之其实就是对类、结构体或者枚举进行初始化,通过构造器来实现。在 Swift 中就是 init 方法。例如,我们定义一个“女神”类,它有个属性是相貌,但没有赋值,这时我们就可以在构造器中进行赋值。具体代码如下:
//定义一个类
class 女神类{
var 相貌:String
//构造方法
init(){
相貌="95分"
}
}
我们还可以给 init 传入一些参数来定制构造器。就让我们来写一个“妹纸”类,它的构造方法接收一个参数年龄age。
//自定义构造方法
class 妹纸类{
var age:Int
init(age:Int){
self.age = age
}
}
let 妹纸= 妹纸类(age: 18)
在构造过程中,必须保证所有存储属性都能得到赋值。所以Swift提供了两种构造器,即
• 指定构造器
• 便利构造器
指定构造器,是类中最主要的构造器,负责在类初始化时给所有
试读结束[说明:试读内容隐藏了图片]