北京
为什么要写这本书
由于苹果公司一直以来以生产硬件闻名,在2014年的WWDC上竟然发布了一种新的编程语言—— Swift。这一举动引起了业界不小的震动。在不到1 个月的时间里,Swift 就挤进流行语言前列,这在以前从未发生过。
Swift目前可用于开发iOS和OS X 平台的应用和游戏程序。但由于Swift刚诞生不久,中文资料还不多,而且由于 Swift 语言具有功能强大和效率开发高的特点,很有可能在将来取代Objective-C,成为iOS 和OS X平台上的主流开发语言。所以,为了让国内广大程序员能尽早掌握Swift开发技术,特意撰写了本书,以便可以让更多的人对Swift语言有所了解,更希望让更多的人成为国内乃至世界上第一批Swift语言专家。
本书的内容
Swift语言基础部分(第1章~第17章)主要介绍了Swift语言的基本语法,尤其是和其他语言不同的地方。项目实战部分(第18章~第20章)主要介绍了如何使用Swift语言开发iOS平台的应用和游戏,在最后一章还给出了一个Flappybird游戏以供大家学习Swift项目开发的全过程。
本书适合我吗
当您走进书店,看到书的标题中熟悉的字眼“Swift”,想了解这本书是否适合自己时,下面的提示对您的选购很有帮助:
● 您听说过iOS吗?
● 您知道App Store吗?
● 您听说过擅长做硬件的苹果公司居然推出了Swift开发语言了吗?
如果上述问题中有一个以上是肯定的,可以很高兴地告诉您,拿在手中的这本书确实是这个方向上的,下面需要进一步确认:
● 您对软件开发有经验或者有兴趣吗?
● 您对开发语言有了解吗?
● 您做过手机应用开发吗?
● 您是iOS或移动开发爱好者吗?
如果上述问题,您的回答中有肯定的,那么您已经具备了阅读本书需要的基础,不用担心读不懂了,那么:
● 您想快速了解并进入Swift应用开发吗?
● 您想找到一本系统介绍Swift开发的参考资料吗?
● 您想选择一本有原理剖析又有真实例子演示的教材吗?
● 您想选一本通俗易懂,符合自己阅读习惯的图书吗?
如上问题中,如果您有大多数回答都是肯定的,那么非常恭喜您,现在拿着的这本书差不多正是您需要的,可以放心地带回去开始自己的Swift之旅了。
如果还在犹豫,那么让下面几个提示告诉您,尽早开始学习的重要性:
● IT界中移动开发的热潮推动了移动互联网的快速发展,而Swift是一个非常强大的开发语言,其让您可以快速切入无线互联网领域;
● 在 App Store 发布应用的数量在快速增长,早日发布自己的 App 可以体现自己的开发价值和乐趣;
● 掌握了 Swift 开发就可以很快开发出供全球 iOS 用户使用的应用,有人已经在 App Store上赚到许多钱了!
本书的特点
● 国内第一本含金量超过Swift官方文档的原创图书。
● 第一本将Swift和最新的SpriteKit游戏引擎深度结合的原创图书。
● 实战性地讲解了Swift的开发技术和和技巧。
● 精彩游戏应用Flappybird让读者一览Swift项目开发全过程。
● 不仅介绍了Swift语言方面的知识,还结合了iOS应用和游戏开发进行讲解。尤其是讨论了基于SpriteKit的2D游戏开发技术。
● 推出了国内首套Swift视频课程:http://edu.51cto.com/course/course_id-1387.html。
● 随时提供答疑和完整资源下载:http://blog.csdn.net/nokiaguy。□ 从事iOS平台应用和游戏开发的程序员。
读者对象
□ 从事iOS平台应用和游戏开发的程序员。
□ 对Swift语言感兴趣的程序员。
□ 以前使用Objective-C,但想摆脱Objective-C繁琐的程序员。
□ 所有对新知识感兴趣的程序员。
源代码和工具下载
读者可以到作者的Blog:http://blog.csdn.net/nokiaguy下载相关的源程序和相关开发工具。
其他学习资源
由于目前Swift语言仍然是测试版,所以在读者拿到本书时,Swift的某些语法或API可能会有变化。为此,作者在51CTO上开了关于Swift的视频课程,这套教材会随着Swift的更新而不断更新。
视频地址:http://edu.51cto.com/course/course_id-1387.html。
勘误和支持
由于作者的水平有限,编写时间仓促,书中难免会出现一些错误或不准确的地方,恳请读者批评指正。如有问题或建议,请发送至 techcast@126.com 或在新浪微博(http://weibo.com/638012593)上留言。非常期待能够得到你们的真挚反馈。编辑联系邮箱为zhangtao@ptpress.com.cn。
致谢
感谢所有在本书写作过程中给予我指导、帮助和鼓励的朋友,尤其是人民邮电出版社的编辑,他们不仅对本书提出了宝贵的写作建议,而且还对本书进行了仔细的审阅。
感谢一直以来信任、鼓励、支持我的家人和朋友。
谨以此书献给我最亲爱的家人,以及众多热爱移动开发的朋友们!
Swift语言为函数提供了丰富多彩的功能。但从功能上来说,Swift函数和其他语言的函数相比是非常强大的,在后面章节要介绍的方法与函数基本上一致。不过在本章先不讨论方法的细节,只介绍函数的各种功能和使用方法。
本章要点
□ 函数的定义和调用
□ 返回多种的函数
□ 扩展参数
□ 扩展参数和内部参数使用同一个名字
□ 默认参数值
□ 可变参数
□ 常量和变量参数
□ 输入/输出参数
□ 函数类型
□ 嵌套函数
源代码文件:src/ch06/function/function/main.swift
Swift并不是纯的面向对象语言,所以和C++一样,支持函数。函数和方法的区别就是函数是全局的,而方法的作用域仅限于方法内。要想引用方法,必须先要引用包含该方法的对象。也可以将方法看成是定义在类中的函数。因此,函数和方法的定义规则基础一致。在后面讲到类时再详细讨论方法的细节。本节先来讨论如何定义一个函数。
不管是什么语言,函数都必须由下面几部分组成。
□ 函数名。
□ 返回值类型。
□ 函数的参数列表,在列表中包含参数名(形参)和参数类型。
□ 函数体。
定义Swift语言的函数也逃不出这几项。下面看一下Swift函数的语法形式。
func functionName(paramName1:paramType1, paramName2,paramType2,...) -> returnType
{
function body
}
很明显,Swift函数和C语言的函数在定义上差异很大。首先,Swift函数必须以func开头,然后跟着函数名,接下来是函数参数列表,最后是返回值类型。其中函数参数列表和返回值类型之间需要用“→”分隔。最后需要用一对花括号({...})将函数体括起来,这里面函数参数类型列表和返回值类型都是可选的。如果不指定函数参数列表,则函数没有参数,但必须在函数名后面指定一对圆括号。如果不指定返回值类型,则函数没有返回值,相当于C语言函数前面指定了void。
下面是一个标准的Swift函数的代码,该函数接收一个String类型的参数,返回一个String类型的值,最后调用了sayHello函数,并输出了函数的返回值。
func sayHello(personName: String) -> String
{
let greeting = "hello " + personName + "!"
return greeting
}
// 调用sayHello函数
println(sayHello("李宁"))
执行这段代码后,会输出如下内容。
hello 李宁!
下面是一些其他形式的函数(多个参数、没有参数、没有返回值)。
// 多个参数的函数
func add(a:Int, b:Int) -> Int
{
return a + b
}
// 调用add函数
println(add(20, 30))
// 没有参数,但又返回值的函数
func process() -> Float
{
return 3*20
}
// 调用process函数
println(process())
// 既没有参数,也没有返回值的函数
func method()
{
println("hello world")
}
// 调用method方法
method()
执行这段代码后,会输出如下内容。
50
60.0
hello world
源代码文件:src/ch06/function/function/main.swift
不管是数学上的定义,还是各种语言中的实现,函数都只能返回一个值,如果非要返回多个值,就返回一个对象,然后将要返回的值以字段、属性或方法形式体现,但这也是返回一个值。不过在Swift函数中,却彻底颠覆了我们对函数的印象。Swift函数是真真正正地可以返回多个值。
可能有的读者会想到,在前面学习数据类型时,有一个元组类型,这个类型可以同时表示多个值,难道函数返回的是这个类型的值?没错,Swift函数就是通过元组类型实现返回多个值的功能的。
其实函数返回元组类型和返回其他类型在写法上没什么区别,只是“→”后面需要用圆括号定义元组类型,定义方式如下。
(returnValue1:valueType1, returnValue2:valueType2,...)
下面是一个典型的返回元组类型的例子。在这个例子中,统计了一个字符串中有多少个元音字母,有多少个辅音字母,有多少个其他字符。很明显,这需要通过元组返回3个值。函数的实现调用代码如下。
func count(string: String) -> (vowels:Int, consonants:Int, others:Int)
{
var vowels = 0
var consonants = 0
var others = 0
for c in string
{
switch String(c).lowercaseString
{
case "a", "e", "i", "o", "u":
++vowels // 元音计数器加1
case "b", "c", "d","f", "g", "h", "j", "k", "l", "m","n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
++consonants // 辅音计数器加1
default:
++others // 其他字符计数器加1
}
}
// 返回元组值
return (vowels, consonants, others)
}
// 调用count函数
let total = count("some arbitrary string!")
println("\(total.vowels) vowels and \(total.consonants) consonants")
执行这段代码,会输出如下内容。
6 vowels and 13 consonants
源代码文件:src/ch06/function/function/main.swift
我们发现,Swift 语言中的很多函数/方法在调用时不只是传入一个参数值,在前面还要跟一个有意义的英文名,该英文名和参数值之间用冒号(:)分隔。例如,Dictionary 有一个updateValue方法,该方法的第二个参数需要给一个forKey,然后才能传入参数值,调用形式如下。
dict.updateValue("飞机", forKey:21)
实际上,这个forKey就是扩展参数名,该参数也可以成为命名参数。也就是说,在定义函数时指定的参数名是函数内部使用的,可以成为函数的内部参数名。而为了让代码更容易理解,在定义函数时还为其指定了一个参数名,该参数名就是扩展参数名。只在调用函数时指定。定义扩展参数名也很简单,只需要在内部参数名之前指定扩展参数名即可,两侧参数之间用空格分隔。
例如,下面两个函数,前者(process)是没有扩展参数名的函数,后者(process1)是有扩展参数名的函数。我们可以观察它们在调用时的差别。
// 没有扩展参数名的函数
func process(p1: String, p2:Int) -> String
{
return "name:" + p1 + " age:" + String(p2)
}
process("bill", 20)
// 有扩展参数名的函数,其中name和age是扩展参数名,需要在调用时指定
func process1(name p1: String, age p2:Int) -> String
{
return "name:" + p1 + " age:" + String(p2)
}
// 调用process1函数,在调用时必须指定扩展参数名
println(process1(name: "Mike", age:17))
执行这段代码后,会输出如下的内容。
name:Mike age:17
注意
在使用扩展参数名调用函数时,不能将参数顺序写错了,否则无法成功编译 [1]
源代码文件:src/ch06/function/function/main.swift
很多读者可能会抱怨,扩展参数是很好,但每次定义函数时都要为一个参数指定两个参数名,岂不是太麻烦了吗?别急!Swift已经考虑到了这一点,这就是将扩展参数和内部参数合二为一。其中做法很简单,只需要在内部参数名之前加一个井号(#)即可。
// name和age即时内部参数,又是扩展参数
func process2(#name: String, #age:Int) -> String
{
return "name:" + name + " age:" + String(age)
}
// 调用process2函数
println(process2(name:"Bill", age:30))
执行这段代码后,会输出如下的内容。
name:Bill age:30
源代码文件:src/ch06/function/function/main.swift
Swift函数支持对参数设置默认参数值 [2],当调用时不指定该参数值时,就会使用这个默认的参数值。默认参数值需要在实现函数时指定,直接在参数类型后面用等号赋值即可。下面是两个典型的使用默认参数值的函数的代码。
// 默认参数
func process3(name p1: String = "Mike", age p2:Int = 30) -> String
{
return "name:" + p1 + " age:" + String(p2)
}
// process3的两个参数都使用了默认参数值
println(process3())
// 只有第二个参数使用了默认参数值
println(process3(name:"John"))
func process4(name: String = "John", age:Int = 30) -> String
{
return "name:" + name + " age:" + String(age)
}
// 第二个参数使用了默认参数
println(process4(name:"Mike"))
源代码文件:src/ch06/function/function/main.swift
可变参数必须是函数的最后一个参数,表示该参数可以传递任意多个值。在函数体中可以通过数组的方式读取这些值。定义的方法就是在参数类型后面加3个点(...)。这个定义方法和Java是相同的。
下面是使用可变参数的典型示例。
// strArray是可变参数
func process5(header:String, strArray:String...) -> String
{
var result = header
// 以数组的方式读取可变参数的值
for s in strArray
{
result += " " + s
}
return result
}
// 调用时最后一个参数可传递任意多个值("a","b","c","d"都是最后一个参数的值)
println(process5("bill", "a","b","c","d"))
执行这段代码,会输出如下内容。
bill a b c d
源代码文件:src/ch06/function/function/main.swift
Swift函数的所有参数默认都是常量,无法修改,如果要想修改参数,可以使用var将参数声明为变量。
// header参数是变量,可以在函数体内修改header的值
func process6(var header:String, strArray:String...) -> String
{
for s in strArray
{
header += " " + s
}
return header
}
println(process6("bill", "a","b","c","d"))
源代码文件:src/ch06/function/function/main.swift
函数的参数都是值传递,即使参数是变量,在函数体内修改了参数值,当函数结束后,也不能将修改结果保留。如果要想利用参数传递值,可以用 inout 关键字将参数修改为输入输出参数。这样在函数体内修改该参数值后,当函数结束后,仍然可以保留修改的结构。
// name是输入输出参数
func process7(inout name:String, age:Int) -> String
{
name = "Mike"
return "Name:" + name + "Age:" + String(age)
}
var name:String = "bill"
println(process7(&name, 40));
// 输出name最后的值
println(name)
在传递输入输出参数值时应注意如下几点。
□ 指定输入输出参数值时必须使用变量,不能使用常量或值。
□ 指定变量时前面需要加&。
□ 在声明变量时,必须初始化。
}
源代码文件:src/ch06/function/function/main.swift
Swift语言支持定义函数类型(类似于C语言的函数指针)。一个函数类型需要指定下面3个要素。
□ 函数参数个数。
□ 函数参数类型。
□ 函数返回值。
函数类型和其他数据类型的使用方法完全相同,在冒号(:)后面指定类型。定义函数类型时除了不需要指定参数名和函数体外,和函数定义完全相同。
下面的代码定义了两个函数类型变量(fun1和fun2)。
var fun1: (inout String, Int) -> String
var fun2: (String, String...) -> String
我们直接将符合要求的函数赋给对应的函数变量,例如,下面两个函数分别符合 fun1和fun2的要求,所以可以直接初始化这两个变量。
func myFun1(inout name:String, age:Int) -> String
{
return "name:" + name + " age:" + String(age)
}
func myFun2(var header:String, strArray:String...) -> String
{
for s in strArray
{
header += " " + s
}
return header
}
// 初始化fun1
fun1 = myFun1
// 初始化fun2
fun2 = myFun2
在调用时可以直接使用函数类型变量。例如,调用fun1和fun2的代码如下。
var name:String = "bill"
println(fun1(&name, 50))
println(fun2("Mike", "a","b"))
函数类型也可以作为函数的参数类型,这样就可以将外部函数传入另一个函数或方法,然后在方法中调用该外部函数了。这样可以很容易实现函数的多态。
// fun是函数类型参数
func myFun3(fun:(inout String, Int) -> String, str:String)
{
var name = ""
println(fun(&name, 40))
}
// 调用myFun3函数,fun1在前面的代码中已经定义了
myFun3(fun1, "hello")
当然,函数的返回值类型也可以是函数类型的,也就是说,函数可以返回另外一个函数。
func method1(n:Int) -> Int
{
return n*n
func method2(n:Int) -> Int
{
return 2*n
}
// myFun4的返回值是函数类型((Int) -> Int)
func myFun4(flag:Bool) -> (Int) -> Int
{
// 根据参数值返回method1或method2
return flag ? method1:method2
}
println(myFun4(true)(20))
println(myFun4(false)(20))
执行这段代码会输出如下的内容。
400
40
源代码文件:src/ch06/function/function/main.swift
Swift函数还支持在函数内部嵌套定义函数,示例代码如下。
func myFun5(flag:Bool, m:Int, n:Int) -> Int
{
// method1为内嵌函数
func method1() ->Int{return m+n}
// method2为内嵌函数
func method2() ->Int{return m - n}
return flag ? method1() : method2()
}
println(myFun5(true, 10,20))
println(myFun5(false, 10,20))
执行这段代码,会输出如下的内容。
30
-10
尽管函数不是一个程序中必须的要素,但有了函数,可以让代码更容易理解。函数是功能封装的最基本单元。在后面章节要介绍的类、方法、属性等元素都或多或少有一些函数的影子。
注 释
[1]. 个人认为Swift 应该加入这个功能,就是当使用扩展参数时可以改变参数的指定顺序。因为通过扩展参数名完全可以恢复正确的参数调用顺序。
[2]. 很多面向对象语言并不支持方法的默认参数,在这种情况下,可以使用方法的重载来实现默认参数值的功能。不过这仍然没有默认参数值方法。所以如果语言支持默认参数值,应尽量使用默认参数值。