概述
Swift是苹果2014年推出的全新的编程语言,它继承了C语言、ObjC的特性,且克服了C语言的兼容性问题。Swift发展过程中不仅保留了ObjC很多语法特性,它也借鉴了多种现代化语言的特点,在其中你可以看到C#、Java、Javascript、Python等多种语言的影子。同时在2015年的WWDC上苹果还宣布Swift的新版本Swift2.0,并宣布稍后Swift即将开源,除了支持iOS、OS
X之外还将支持linux。
本文将继续iOS开发系列教程,假设读者已经有了其他语言基础(强烈建议初学者从本系列第一章开始阅读,如果您希望从Swift学起,那么推荐你首先阅读苹果官方电子书《the
swift programming language》),不会从零基础一点点剖析这门语言的语法,旨在帮助大家快速从ObjC快速过度到Swift开发中。即便如此,要尽可能全面的介绍Swift的语法特点也不是一件容易的事情,因此本文将采用较长的篇幅进行介绍。
基础部分
第一个Swift程序
创建一个命令行程序如下:
import Foundation
* Swift没有main函数,默认从top level code的上方开始自上而下执行(因此不能有多个top
level代码)
println("Hello, World!") |
从上面的代码可以看出:
1.Swift没有main函数,从top level code的上方开始往下执行(就是第一个非声明语句开始执行[表达式或者控制结构,类、结构体、枚举和方法等属于声明语句]),不能存在多个top
level code文件(否则编译器无法确定执行入口,事实上swift隐含一个main函数,这个main函数会设置并调用全局
“C_ARGC C_ARGV”并调用由top level code构成的top_level_code()函数);
2.Swift通过import引入其他类库(和Java比较像);
3.Swift语句不需要双引号结尾(尽管加上也不报错),除非一行包含多条语句(和Python有点类似);
数据类型
Swift包含了C和ObjC语言中的所有基础类型,Int整形,Float和Double浮点型,Bool布尔型,Character字符型,String字符串类型;当然还包括enum枚举、struct结构体构造类型;Array数组、Set集合、Dictionary字典集合类型;不仅如此还增加了高阶数据类型元组(Tuple),可选类型(Optinal)。
基础类型
Xcode 从6.0开始加入了Playground代码测试,可以实时查看代码执行结果,下面使用Playground简单演示一下Swift的基础内容,对Swift有个简单的认识:
import Foundation
var a:Int=1 //通过var定义一个变量
//下面变量b虽然没有声明类型,但是会自动进行类型推断,这里b推断为Int类型
var b=2
var c:UInt=3
let d=a+b //通过let定义一个变量
//下面通过"\()"实现了字符串和变量相加(字符串插值),等价于println("d="+String(d))
println("d=\(d)") //结果:d=3
//注意由于Swift是强类型语言,a是Int类型而c是UInt类型,二者不能运算,下面的语句报错;但是注意如果是类似于:let
a=1+2.0是不会报错的,因为两个都是字面量,Swift会首先计算出结果再推断a的类型
//let e=a+c
//Int.max是Int类型的最大值,类似还有Int.min、Int32.max、Int32.min等
let e=Int.max //结果:9223372036854775807
var f:Float=1.0
var g=2.0 //浮点型自动推断为Double类型
var h:String="hello "
//emoj表情也可以作为变量或者常量,事实上所有Unicode字符都是可以的
var ????="love and apple"
//两个字符串相加,但是注意不同类型不能相加
var i=h+???? //结果:hello love and apple
//布尔类型只有两个值true、false,类似于if语句中的条件只能是布尔类型不能像ObjC一样非0即真
var j:Bool=true
//字符类型,同样使用双引号,但是只能是一个字符,如果不指定类型则"c"默认会推断为字符串(var
k:Character="c"是字符类型,但是var k="c"是字符串类型)
var k:Character="c"
var l=00100 //等于100,可以在前面添加额外的0
var m=10_000_000 //等于10000000,可以使用增加额外的下划线方便阅读而不改变值的大小 |
1.Swift通过var进行变量定义,通过let进行常量定义(这和其他高级语言比较类似,例如F#);
2.Swift添加了类型推断,对于赋值的常量或者变量会自动推断其具体类型;
3.Swift是强类型语言(应该说它比C#、Java等强类型语言控制还要严格),不同的数据类型之间不能隐式转化,如果需要转化只能强制转化;
4.在Swift中类型转换直接通过其类型构造函数即可,降低了API的学习成本;
集合类型
Swift提供了三种集合类型:数组Array、集合Set、字典Dictionary。和ObjC不同的是,由于Swift的强类型,集合中的元素必须是同一类型,而不能像ObjC一样可以存储任何对象类型,并且注意Swift中的集合是值类型而非引用类型(事实上包括String、结构体struct、枚举enum都是值类型)。
首先看一下Swift中的数组:
//声明数组的时候必须确定其类型,下面使用[String]声明一个字符串数组([String]是Array<String>简单表达形式)
//var a:Array<String>=["hello","world"]
var a:[String]=["hello","world"]
a[0] //访问数组元素
//下面创建一个Double类型的数组,这里没有使用字面量,当前是一个空数组,当然也可以写成var
b:[Double]=[]
var b=[Double]()
for i in a{
println("i=\(i)")
}
//添加元素,Swift中可变类型不再由单独的一个类型来表示,统统使用Array,如果想声明为不可变数组只要使用let定义即可
a.append("!")
a+=["I" ,"am" ,"Kenshin"]
//追加元素
println("a.count=\(a.count)") //结果:a.count=6
a[3...5]=["I","Love","Swift"]
//修改元素,但是注意无法用这种方式添加元素
//a[6]=["."]//这种方式是错误的
a.insert("New", atIndex: 5) //插入元素:hello
world! I Love New Swift
a.removeAtIndex(5) //删除指定元素
//使用全局enumerate函数遍历数据索引和元素
for (index,element) in enumerate(a){
println("index=\(index),element=\(element)")
}
//使用构造函数限制数组元素个数并且指定默认值,等价于var c=Array(count:
3, repeatedValue: 1),自动推断类型
var c=[Int](count: 3, repeatedValue: 1) |
Set表示没有顺序的集合:
//注意集合没有类似于数组的简化形式,例如不能写成var a:[String]=["hello","world"]
var a:Set<String>=["hello","world"]
var b:Set=[1,2] //类型推断:Set<Int>
a.insert("!") //注意这个插入不保证顺序
if !a.isEmpty { //判断是否为空
a.remove("!")
}
if !a.contains("!"){
a.insert("!")
} |
Dictionary字典同样是没有顺序的,并且在Swift中字典同样要在使用时明确具体的类型。和ObjC中一样,字典必须保证key是唯一的,而这一点就要求在Swift中key必须是可哈希的,不过幸运的是Swift中的基本类型(如Int、Float、Double、Bool、String)都是可哈希的,都可以作为key。
//通过字面量进行字典初始化,注意等价于var a:Dictionary<Int,String>=[200:"success",404:"not found"]
var a:[Int:String]=[200:"success",404:"not
found"]
var b=[200:"success",404:"not
found"] //不声明类型,根据值自动推断类型
a[200] //读取字典
a[404]="can not found" //修改
a[500]="internal server error" //添加
//a=[:] //设置为空字典,等价于:a=[Int:String]()
for code in a.keys{
println("code=\(code)")
}
for description in a.values{
println("description=\(description)")
}
for (code,description) in a{
println("code=\(code),description=\(description)")
} |
注意:在Swift中集合的可变性不是像ObjC一样由单独的数据类型来控制的,而是通过变量和常量来控制,这一点和其他高级语言比较类似。
元组(Tuple)
在开发过程中有时候会希望临时组织一个数据类型,此时可以使用一个结构体或者类,但是由于这个类型并没有那么复杂,如果定义起来又比较麻烦,此时可以考虑使用元组。
/**
* 元组的基本用法
*/
var point=(x:50,y:100) //自动推断其类型:(Int,Int)
point.x //可以用类似于结构体的方式直接访问元素,结果:50
point.y //结果:100
point.0 //也可以采用类似数组的方式使用下标访问,结果:50
point.1 //结果:100
//元组也可以不指定元素名称,访问的时候只能使用下标
let frame:(Int,Int,Int,Float)=(0,0,100,100.0)
println(frame) //结果:(0, 0, 100, 100.0)
//注意下面的语句是错误的,如果指定了元组的类型则无法指定元素名称
//let frame:(Int,Int,Int,Int)=(x:0,y:0,width:100,height:100)
var size=(width:100,25) //仅仅给其中一个元素命名
size.width //结果:100
size.1 //结果:25
var httpStatus:(Int,String)=(200,"success")
//元组的元素类型并不一定相同
var (status,description)=httpStatus //一次性赋值给多个变量,此时status=200,description="success"
//接收元组的其中一个值忽略另一个值使用"_"(注意在Swift中很多情况下使用_忽略某个值或变量)
var (sta,_)=httpStatus
println("sta=\(sta)") //结果:sta=200
/**
* 元组作为函数的参数或返回值,借助元组实现了函数的多个返回值
*/
func request()->(code:Int,description:String){
return (404,"not found")
}
var result=request()
result.0 //结果:404
result.1 //结果:not found
result.code //结果:404
result.description //结果:not found |
可选类型
所谓可选类型就是一个变量或常量可能有值也可能没有值则设置为可选类型。在ObjC中如果一个对象类型没有赋值,则默认为nil,同时nil类型也只能作为对象类型的默认值,对于类似于Int等基本类型则对应0这样的默认值。由于Swift是强类型语言,如果在声明变量或常量时没有进行赋值,Swift并不会默认设置初值(这一点和其他高级语言不太一样,例如C#虽然也有可选类型,但是要求并没有那么严格)。
/**
* 可选类型基础
*/
var x:Float? //使用?声明成一个可选类型,如果不赋值默认为nil
x=172.0
var y:Float=60.0
//var z=x+y //注意此句报错,因为Int和Int?根本就是两种不同的类型,在Swift中两种不同的类型不能运算(因为不会自动进行类型转化)
var z=x!+y //使用!进行强制解包
var age="29"
var ageInt=age.toInt() //注意ageInt是Int可选类型而不是Int类型(因为String的toInt()方法并不能保证其一定能转化为Int类型) |
1.Swift中类似于Int和Int?并不是同一种类型,不能进行相关运算,如果要运算只能解包;
2.可选类型其本质就是此类型内部存储分为“Some”和“None”两个部分,如果有值则存储到“Some”中,没有值则为“None”(早期Playground中可以看到两个部分,如今已经取消显示Some等描述了),使用感叹号强制解包的过程就是取出“Some”部分;
既然可选类型有可能有值,也可能没有值那么往往有时候就需要判断。可以使用if直接判断一个可选类型是否为nil,这样一来就可以根据情况进行强制解包(从Some部分取出值的过程);另一个选择就是在判断的同时如果有值则将值赋值给一个临时变量或常量,否则不进入此条件语句,这个过程称之为“可选绑定”。
/**
* 可选类型判断
*/
var age="29"
var ageInt=age.toInt() //注意ageInt是Int可选类型而不是Int类型(因为String的toInt()方法并不能保证其一定能转化为Int类型)
if ageInt==nil {
println("ageInt=nil")
}else{
println("ageInt=\(ageInt!)") //注意这里使用感叹号!强制解析
}
/**
* 可选类型绑定
* 如果可选类型有值则将值赋值给一个临时变量或者常量(此时此变量或者常量接受的值已经不是可选类型),如果没有值则不执行此条件
*/
if let newAge=ageInt{ //此时newAge可以定义成常量也可以定义成变量
println("newAge=\(newAge)") //注意这里并不需要对newAge强制解包
}else{
println("ageInt=nil")
} |
通过前面的演示可以看出Swift中的可选绑定如果实际计算不得不进行强制解包,如果一个可选类型从第一次赋值之后就能保证有值那么使用时就不必进行强制解包了,这种情况下可以使用隐式可选解析类型(通过感叹号声明而不是问号)
/**
* 隐式解析可选类型
*/
var age:Int!=0 //通过感叹号声明隐式解析可选类型,此后使用时虽然是可选类型但是不用强制解包
age=29
var newAge:Int=age //不用强制解包直接赋值给Int类型(程序会自动解包)
if var tempAge=age {
println("tempAge=\(tempAge)")
}else{
println("age=nil")
} |
运算符
Swift中支持绝大多数C语言的运算符并改进以减少不必要的错误(例如等号赋值后不返回值),算术运算会检查溢出情况,必要时还能使用新增的溢出运算符。另外Swift中还可以对浮点数使用取余运算符,新增了区间运算符。对于基本的运算符这里不再一一介绍,简单看一下Swift中的区间运算符和溢出运算符。
/**
* 区间运算符,通常用于整形或者字符范围(例如"a"..."z")
*/
for i in 1...5 { //闭区间运算符...(从1到5,包含5)
println("i=\(i)")
}
for i in 1..<5{ //半开区间运算符..<(从1到4)
println("i=\(i)")
}
var str = "hello world."
var range="a"..."z"
for t in str {
if range.contains(String(t)) {
print(t) //结果:helloworld
}
}
/**
* 溢出运算符
*/
var a=UInt8.max //a=255
//var b:UInt8=a+1 //注意b会出现溢出,此句报错
//下面使用溢出运算符,结果为:0,类似的还有&-、&*、&/
//使用溢出运算符可以在最大值和最小值之前循环而不会报错
var b:UInt8=a &+ 1 |
溢出运算符的原理其实很简单,例如对于UInt8,如果8位均为1则十进制表示是255,但是当加1之后则变成了9位“100000000”,出现了溢出但是UInt8本身值只能存储8位,所以取后面8位就变成了“00000000”,十进制表示就是0。
控制流
Swift中的多数控制流和其他语言差别并不大,例如for、while、do
while、if、switch等,而且有些前面已经使用过(例如for in循环),这里将着重介绍一些不同点。
var a=["a","b","c","d","e","f","g"]
let b=a[1]
/**
* switch支持一个case多个模式匹配,同时case后不用写break也会在匹配到种情况后自动跳出匹配,不存在隐式贯穿,如果想要贯穿在case之后添加"fallthrough"关键字
*/
switch b{
case "a","b":
println("b=a or b=b")
case "c","d","e","f":
println("b in (c,d,e,f)")
default:
println("b=g")
}
/**
* 匹配区间,同时注意switch必须匹配所有情况,否则必须加上default
*/
let c:Int=88
switch c{
case 1...60:
println("1-60")
case 61...90:
println("61-90")
case 91...100:
println("91-100")
default:
println("1>c>100")
}
/**
* 元组匹配、值绑定、where条件匹配
* 注意下面的匹配没有default,因为它包含了所有情况
*/
var d=(x:900,y:0)
switch d{
case (0,0):
println("d in (0,0)")
case (_,0): //忽略x值匹配
println("d in y")
case (0,let y)://值绑定
println("d in x,y=\(y)")
case (-100...100,-100...100): //注意这里有可能和第一、二、三个条件重合,但是Swift允许多个case匹配同一个条件,但是只会执行第一个匹配
println("x in(0-100),y in (0-100)")
case let (x,y) where x==y: //where条件匹配,注意这里的写法等同于:(let
x,let y) where x==y
println("x=y=\(x)")
case let (x, y):
println("x=\(x),y=\(y)")
} |
在其他语言中通常可以使用break、continue、return(Swift中添加了fallthrough)等来终止或者跳出某个执行语句,但是对于其行为往往是具有固定性的,例如break只能终止其所在的内层循环,而return只能跳出它所在的函数。在Swift中这种控制转移功能得到了加强,那就是使用标签。利用标签你可以随意指定转移的位置,例如下面的代码演示了如何直接通过标签跳出最外层循环:
var a=5
whileLoop:
while --a>0 {
for var i=0;i<a;++i{
println("a=\(a),i=\(i)")
break whileLoop
//如果此处直接使用break将跳出for循环,而由于这里使用标签直接跳出了while,结果只会打印一次,其结果为:a=4,i=0
}
} |
函数和闭包
函数
函数是一个完成独立任务的代码块,Swift中的函数不仅可以像C语言中的函数一样作为函数的参数和返回值,而且还支持嵌套,并且有C#一样的函数参数默认值、可变参数等。
//定义一个函数,注意参数和返回值,
如果没有返回值可以不写返回值或者写成Void、空元组()(注意Void的本质就是空元组)
func sum(num1:Int,num2:Int)->Int{
return num1 + num2
}
sum(1, 2) |
可以看到Swift中的函数仅仅表达形式有所区别(定义形式类似于Javascript,但是js不用书写返回值),但是本质并没有太大的区别。不过Swift中对函数参数强调两个概念就是局部参数名(又叫“形式参数”)和外部参数名,这极大的照顾到了ObjC开发者的开发体验。在上面的例子中调用sum函数并没有传递任何参数名,因为num1、num2仅仅作为局部参数名在函数内部使用,但是如果给函数指定一个外部参数名在调用时就必须指定参数名。另外前面也提到关于Swift中的默认参数、可变长度的参数,包括一些高级语言中的输入输出参数,通过下面的例子大家会有一个全面的了解。
/**
/**
* 函数参数名分为局部参数名和外部参数名
*/
func split(string a:String,seperator b:Character)->[String]{
return split(a, maxSplit: Int.max, allowEmptySlices:
false, isSeparator: {$0==b})
}
//由于给split函数设置了外部参数名string和seperator,所以执行的时候必须带上外部参数名,此处可以看到一个有意义的外部参数名大大节省开发者使用成本
split(string: "hello,world,!", seperator:
",") //结果:["hello", "world",
"!"]
//下面通过在局部参数名前加上#来简写外部参数名(此时局部参数名和外部参数名相同)
func split2(#string:String,#seperator:Character)->[String]{
return split(string, maxSplit: Int.max, allowEmptySlices:
false, isSeparator: {$0==seperator})
}
split2(string: "hello,world,!", seperator:
",")
//上面的split函数的最后一个参数默认设置为",",注意如果使用默认参数那么此参数名将默认作为外部参数名(此时局部参数名和外部参数名相同)
func split3(#string:String,seperator:Character=",")->[String]{
return split(string, maxSplit: Int.max, allowEmptySlices:
false, isSeparator: {$0==seperator})
}
split3(string: "hello,world,!", seperator:
",") //结果:["hello", "world",
"!"]
split3(string: "hello world !", seperator:
" ") //结果:["hello", "world",
"!"]
//但是如果有默认值,又不想指定局部参数名可以使用“_”取消外部参数名
func split4(string:String,_ seperator:Character=",")->[String]{
return split(string, maxSplit: Int.max, allowEmptySlices:
false, isSeparator: {$0==seperator})
}
split4("hello,world,!", ",")
//结果:["hello", "world", "!"]
/**
* 可变参数,一个函数最多有一个可变参数并且作为最后一个参数
* 下面strings参数在内部是一个[String],对于外部是不定个数的String参数
*/
func joinStr(seperator:Character=",",strings:String...)->String{
var result:String=""
for var i=0;i<strings.count;++i{
if i != 0{
result.append(seperator)
}
result+=strings[i]
}
return result
}
joinStr(seperator:" ", "hello","world","!")
//结果:"hello world !"
/**
* 函数参数默认是常量,不能直接修改,通过声明var可以将其转化为变量(但是注意C语言参数默认是变量)
* 但是注意这个变量对于外部是无效的,函数执行完就消失了
*/
func sum2(var num1:Int,num2:Int)->Int{
num1 = num1 + num2
return num1
}
sum2(1, 2) //结果:3
/**
* 输入输出参数
* 通过输入输出参数可以在函数内部修改函数外部的变量(注意调用时不能是常量或字面量)
* 注意:下面的swap仅仅为了演示,实际使用时请用Swift的全局函数swap
*/
func swap(inout a:Int ,inout b:Int){
a=a+b
b=a-b
a=a-b
}
var a=1,b=2
swap(&a, &b) //调用时参数加上“&”符号
println("a=\(a),b=\(b)") //结果:"a=2,b=1" |
和很多语言一样,Swift中的函数本身也可以看做一种类型,既可以作为参数又可以作为返回值。
/**
* 函数类型
*/
var sum3=sum //自动推断sum3的类型:(Int,Int)->Int,注意不同的函数类型之间不能直接赋值
sum3(1,2) //结果:3
//函数作为返回值
func fn()->(Int,Int)->Int{
//下面的函数是一个嵌套函数,作用于是在fn函数内部
func minus(a:Int,b:Int)->Int{
return a-b
}
return minus;
}
var minus=fn()
//函数作为参数
func caculate(num1:Int,num2:Int,fn:(Int,Int)->Int)->Int{
return fn(num1,num2)
}
caculate(1, 2, sum) //结果:3
caculate(1,2, minus) //结果:-1 |
闭包
Swift中的闭包其实就是一个函数代码块,它和ObjC中的Block及C#、Java中的lambda是类似的。闭包的特点就是可以捕获和存储上下文中的常量或者变量的引用,即使这些常量或者变量在原作用域已经被销毁了在代码块中仍然可以使用。事实上前面的全局函数和嵌套函数也是一种闭包,对于全局函数它不会捕获任何常量或者变量,而对于嵌套函数则可以捕获其所在函数的常量或者变量。通常我们说的闭包更多的指的是闭包表达式,也就是没有函数名称的代码块,因此也称为匿名闭包。
在Swift中闭包表达式的定义形式如下:
{ ( parameters ) -> returnType in
statements
}
|
下面通过一个例子看一下如何通过闭包表达式来简化一个函数类型的参数,在下面的例子中闭包的形式也是一而再再而三的被简化,充分说明了Swift语法的简洁性:
func sum(num1:Int,num2:Int)->Int{
return num1 + num2
}
func minus(num1:Int,num2:Int)->Int{
return num1 - num2
}
func caculate(num1:Int,num2:Int,fn:(Int,Int)->Int)->Int{
return fn(num1,num2)
}
var (a,b)=(1,2)
caculate(a, b, sum) //结果:3
caculate(a, b, minus) //结果:-1
//利用闭包表达式简化闭包函数
caculate(a, b, {(num1:Int,num2:Int)->Int in
return num1 - num2
}) //结果:-1
//简化形式,根据上下文推断类型并且对于单表达式闭包(只有一个语句)可以隐藏return关键字
caculate(a, b, { num1,num2 in
num1 - num2
}) //结果:-1
//再次简化,使用参数名缩写,使用$0...$n代表第n个参数,并且此in关键字也省略了
caculate(a, b, {
$0 - $1
}) //结果:-1 |
考虑到闭包表达式的可读取性,Swift中如果一个函数的最后一个参数是一个函数类型的参数(或者说是闭包表达式),则可以将此参数写在函数括号之后,这种闭包称之为“尾随闭包”。
func sum(num1:Int,num2:Int)->Int{
return num1 + num2
}
func minus(num1:Int,num2:Int)->Int{
return num1-num2
}
func caculate(num1:Int,num2:Int,fn:(Int,Int)->Int)->Int{
return fn(num1,num2)
}
var (a,b)=(1,2)
//尾随闭包,最后一个参数是闭包表达式时可以卸载括号之后,同时注意如果这个函数只有一个闭包表达式参数时可以连通括号一块省略
//请注意和函数定义进行区分
caculate(a, b){
$0 - $1
} //结果:-1 |
前面说过闭包之所以称之为“闭包”就是因为其可以捕获一定作用域内的常量或者变量进而闭合并包裹着。
func add()->()->Int{
var total=0
var step=1
func fn()->Int{
total+=step
return total
}
return fn
}
//fn捕获了total和step,尽管下面的add()执行完后total和step被释放,但是由于fn捕获了二者的副本,所以fn会随着两个变量的副本一起被存储
var a=add()
a() //结果:1
a() //结果:2,说明a中保存了total的副本(否则结果会是1)
var b=add()
b() //结果:1 ,说明a和b单独保存了total的副本(否则结果会是3)
var c=b
c() //结果:2,说明闭包是引用类型,换句话说函数是引用类型(否则结果会是1) |
Swift会自动决定捕获变量或者常量副本的拷贝类型(值拷贝或者引用拷贝)而不需要开发者关心,另外被捕获的变量或者常量的内存管理同样是由Swift来管理,例如当上面的函数a不再使用了那么fn捕获的两个变量也就释放了。
|