概述
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程序
- 数据类型
- 基础类型
- 集合类型
- 元组
- 可选类型
- 运算符
- 控制流
- 函数和闭包
- 函数
- 闭包
- 类
- 属性
- 方法
- 下标脚本
- 继承
- 协议
- 扩展
- 枚举和结构体
- 结构体
- 枚举
- 泛型
基础部分
第一个Swift程序
创建一个命令行程序如下:
|
1
2
3
4
5
6
|
import Foundation
println("Hello, World!")
|
从上面的代码可以看出:
- Swift没有main函数,从top level code的上方开始往下执行(就是第一个非声明语句开始执行[表达式或者控制结构,类、结构体、枚举和方法等属于声明语句]),不能存在多个top level code文件(否则编译器无法确定执行入口,事实上swift隐含一个main函数,这个main函数会设置并调用全局 “C_ARGC C_ARGV”并调用由top level code构成的top_level_code()函数);
- Swift通过import引入其他类库(和Java比较像);
- 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有个简单的认识:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
import Foundation
var a:Int=1
var b=2
var c:UInt=3
let d=a+b
println("d=\(d)")
let e=Int.max
var f:Float=1.0
var g=2.0
var h:String="hello "
var 💖🍎="love and apple"
var i=h+💖🍎
var j:Bool=true
var k:Character="c"
var l=00100
var m=10_000_000
|
- Swift通过var进行变量定义,通过let进行常量定义(这和其他高级语言比较类似,例如F#);
- Swift添加了类型推断,对于赋值的常量或者变量会自动推断其具体类型;
- Swift是强类型语言(应该说它比C#、Java等强类型语言控制还要严格),不同的数据类型之间不能隐式转化,如果需要转化只能强制转化;
- 在Swift中类型转换直接通过其类型构造函数即可,降低了API的学习成本;
集合类型
Swift提供了三种集合类型:数组Array、集合Set、字典Dictionary。和ObjC不同的是,由于Swift的强类型,集合中的元素必须是同一类型,而不能像ObjC一样可以存储任何对象类型,并且注意Swift中的集合是值类型而非引用类型(事实上包括String、结构体struct、枚举enum都是值类型)。
首先看一下Swift中的数组:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
var a:[String]=["hello","world"]
a[0]
var b=[Double]()
for i in a{
println("i=\(i)")
}
a.append("!")
a+=["I" ,"am" ,"Kenshin"]
println("a.count=\(a.count)")
a[3...5]=["I","Love","Swift"]
a.insert("New", atIndex: 5)
a.removeAtIndex(5)
for (index,element) in enumerate(a){
println("index=\(index),element=\(element)")
}
var c=[Int](count: 3, repeatedValue: 1)
|
Set表示没有顺序的集合:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var a:Set<String>=["hello","world"]
var b:Set=[1,2]
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。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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"
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)
在开发过程中有时候会希望临时组织一个数据类型,此时可以使用一个结构体或者类,但是由于这个类型并没有那么复杂,如果定义起来又比较麻烦,此时可以考虑使用元组。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
var point=(x:50,y:100)
point.x
point.y
point.0
point.1
let frame:(Int,Int,Int,Float)=(0,0,100,100.0)
println(frame)
var size=(width:100,25)
size.width
size.1
var httpStatus:(Int,String)=(200,"success")
var (status,description)=httpStatus
var (sta,_)=httpStatus
println("sta=\(sta)")
func request()->(code:Int,description:String){
return (404,"not found")
}
var result=request()
result.0
result.1
result.code
result.description
|
可选类型
所谓可选类型就是一个变量或常量可能有值也可能没有值则设置为可选类型。在ObjC中如果一个对象类型没有赋值,则默认为nil,同时nil类型也只能作为对象类型的默认值,对于类似于Int等基本类型则对应0这样的默认值。由于Swift是强类型语言,如果在声明变量或常量时没有进行赋值,Swift并不会默认设置初值(这一点和其他高级语言不太一样,例如C#虽然也有可选类型,但是要求并没有那么严格)。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var x:Float?
x=172.0
var y:Float=60.0
var z=x!+y
var age="29"
var ageInt=age.toInt()
|
- Swift中类似于Int和Int?并不是同一种类型,不能进行相关运算,如果要运算只能解包;
- 可选类型其本质就是此类型内部存储分为“Some”和“None”两个部分,如果有值则存储到“Some”中,没有值则为“None”(早期Playground中可以看到两个部分,如今已经取消显示Some等描述了),使用感叹号强制解包的过程就是取出“Some”部分;
既然可选类型有可能有值,也可能没有值那么往往有时候就需要判断。可以使用if直接判断一个可选类型是否为nil,这样一来就可以根据情况进行强制解包(从Some部分取出值的过程);另一个选择就是在判断的同时如果有值则将值赋值给一个临时变量或常量,否则不进入此条件语句,这个过程称之为“可选绑定”。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
var age="29"
var ageInt=age.toInt()
if ageInt==nil {
println("ageInt=nil")
}else{
println("ageInt=\(ageInt!)")
}
if let newAge=ageInt{
println("newAge=\(newAge)")
}else{
println("ageInt=nil")
}
|
通过前面的演示可以看出Swift中的可选绑定如果实际计算不得不进行强制解包,如果一个可选类型从第一次赋值之后就能保证有值那么使用时就不必进行强制解包了,这种情况下可以使用隐式可选解析类型(通过感叹号声明而不是问号)
|
1
2
3
4
5
6
7
8
9
10
11
12
|
var age:Int!=0
age=29
var newAge:Int=age
if var tempAge=age {
println("tempAge=\(tempAge)")
}else{
println("age=nil")
}
|
运算符
Swift中支持绝大多数C语言的运算符并改进以减少不必要的错误(例如等号赋值后不返回值),算术运算会检查溢出情况,必要时还能使用新增的溢出运算符。另外Swift中还可以对浮点数使用取余运算符,新增了区间运算符。对于基本的运算符这里不再一一介绍,简单看一下Swift中的区间运算符和溢出运算符。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
for i in 1...5 {
println("i=\(i)")
}
for i in 1..<5{
println("i=\(i)")
}
var str = "hello world."
var range="a"..."z"
for t in str {
if range.contains(String(t)) {
print(t)
}
}
var a=UInt8.max
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循环),这里将着重介绍一些不同点。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
var a=["a","b","c","d","e","f","g"]
let b=a[1]
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")
}
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")
}
var d=(x:900,y:0)
switch d{
case (0,0):
println("d in (0,0)")
case (_,0):
println("d in y")
case (0,let y):
println("d in x,y=\(y)")
case (-100...100,-100...100):
println("x in(0-100),y in (0-100)")
case let (x,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中这种控制转移功能得到了加强,那就是使用标签。利用标签你可以随意指定转移的位置,例如下面的代码演示了如何直接通过标签跳出最外层循环:
|
1
2
3
4
5
6
7
8
9
10
|
var a=5
whileLoop:
while --a>0 {
for var i=0;i<a;++i{
println("a=\(a),i=\(i)")
break whileLoop
}
}
|
函数和闭包
函数
函数是一个完成独立任务的代码块,Swift中的函数不仅可以像C语言中的函数一样作为函数的参数和返回值,而且还支持嵌套,并且有C#一样的函数参数默认值、可变参数等。
|
1
2
3
4
5
6
|
func sum(num1:Int,num2:Int)->Int{
return num1 + num2
}
sum(1, 2)
|
可以看到Swift中的函数仅仅表达形式有所区别(定义形式类似于Javascript,但是js不用书写返回值),但是本质并没有太大的区别。不过Swift中对函数参数强调两个概念就是局部参数名(又叫“形式参数”)和外部参数名,这极大的照顾到了ObjC开发者的开发体验。在上面的例子中调用sum函数并没有传递任何参数名,因为num1、num2仅仅作为局部参数名在函数内部使用,但是如果给函数指定一个外部参数名在调用时就必须指定参数名。另外前面也提到关于Swift中的默认参数、可变长度的参数,包括一些高级语言中的输入输出参数,通过下面的例子大家会有一个全面的了解。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
/**
/**
* 函数参数名分为局部参数名和外部参数名
*/
func split(string a:String,seperator b:Character)->[String]{
return split(a, maxSplit: Int.max, allowEmptySlices: false, isSeparator: {$0==b})
}
split(string: "hello,world,!", seperator: ",")
func split2(#string:String,#seperator:Character)->[String]{
return split(string, maxSplit: Int.max, allowEmptySlices: false, isSeparator: {$0==seperator})
}
split2(string: "hello,world,!", seperator: ",")
func split3(#string:String,seperator:Character=",")->[String]{
return split(string, maxSplit: Int.max, allowEmptySlices: false, isSeparator: {$0==seperator})
}
split3(string: "hello,world,!", seperator: ",")
split3(string: "hello world !", seperator: " ")
func split4(string:String,_ seperator:Character=",")->[String]{
return split(string, maxSplit: Int.max, allowEmptySlices: false, isSeparator: {$0==seperator})
}
split4("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","!")
/**
* 函数参数默认是常量,不能直接修改,通过声明var可以将其转化为变量(但是注意C语言参数默认是变量)
* 但是注意这个变量对于外部是无效的,函数执行完就消失了
*/
func sum2(var num1:Int,num2:Int)->Int{
num1 = num1 + num2
return num1
}
sum2(1, 2)
/**
* 输入输出参数
* 通过输入输出参数可以在函数内部修改函数外部的变量(注意调用时不能是常量或字面量)
* 注意:下面的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)")
|
和很多语言一样,Swift中的函数本身也可以看做一种类型,既可以作为参数又可以作为返回值。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
var sum3=sum
sum3(1,2)
func fn()->(Int,Int)->Int{
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)
caculate(1,2, minus)
|
闭包
Swift中的闭包其实就是一个函数代码块,它和ObjC中的Block及C#、Java中的lambda是类似的。闭包的特点就是可以捕获和存储上下文中的常量或者变量的引用,即使这些常量或者变量在原作用域已经被销毁了在代码块中仍然可以使用。事实上前面的全局函数和嵌套函数也是一种闭包,对于全局函数它不会捕获任何常量或者变量,而对于嵌套函数则可以捕获其所在函数的常量或者变量。通常我们说的闭包更多的指的是闭包表达式,也就是没有函数名称的代码块,因此也称为匿名闭包。
在Swift中闭包表达式的定义形式如下:
{ ( parameters ) -> returnType in
statements
}
下面通过一个例子看一下如何通过闭包表达式来简化一个函数类型的参数,在下面的例子中闭包的形式也是一而再再而三的被简化,充分说明了Swift语法的简洁性:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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)
caculate(a, b, minus)
caculate(a, b, {(num1:Int,num2:Int)->Int in
return num1 - num2
})
caculate(a, b, { num1,num2 in
num1 - num2
})
caculate(a, b, {
$0 - $1
})
|
考虑到闭包表达式的可读取性,Swift中如果一个函数的最后一个参数是一个函数类型的参数(或者说是闭包表达式),则可以将此参数写在函数括号之后,这种闭包称之为“尾随闭包”。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
func add()->()->Int{
var total=0
var step=1
func fn()->Int{
total+=step
return total
}
return fn
}
var a=add()
a()
a()
var b=add()
b()
var c=b
c()
|
Swift会自动决定捕获变量或者常量副本的拷贝类型(值拷贝或者引用拷贝)而不需要开发者关心,另外被捕获的变量或者常量的内存管理同样是由Swift来管理,例如当上面的函数a不再使用了那么fn捕获的两个变量也就释放了。
类
作为一门面向对象语言,类当然是Swift中的一等类型。首先通过下面的例子让大家对Swift的class有一个简单的印象,在下面的例子中可以看到Swift中的属性、方法(包括构造方法和析构方法):
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
class Person {
var name:String
var height=0.0
init(name:String){
self.name=name
}
func showMessage(){
println("name=\(name),height=\(height)")
}
deinit{
println("deinit...")
}
}
var p=Person(name: "Kenhin")
p.height=172.0
p.showMessage()
var p2 = p
p2.name = "Kaoru"
println(p.name)
if p === p2 {
println("p===p2")
}
|
从上面的例子不难看出:
- Swift中的类不必须继承一个基类(但是ObjC通常必须继承于NSObject),如果一个类没有继承于任何其他类则这个类也称为“基类”;
- Swift中的属性定义形式类似于其他语句中的成员变量(或称之为“实例变量”),尽管它有着成员变量没有的特性;
- Swift中如果开发者没有自己编写构造方法那么默认会提供一个无参数构造方法(否则不会自动生成构造方法);
- Swift中的析构方法没有括号和参数,并且不支持自行调用;
属性
Swift中的属性分为两种:存储属性(用于类、结构体)和计算属性(用于类、结构体、枚举),并且在Swift中并不强调成员变量的概念。 无论从概念上还是定义方式上来看存储属性更像其他语言中的成员变量,但是不同的是可以控制读写操作、通过属性监视器来属性的变化以及快速实现懒加载功能。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
class Account {
var balance:Double=0.0
}
class Person {
var firstName:String
var lastName:String
let age:Int
var fullName:String{
get{
return firstName + "." + lastName
}
set{
let array=split(newValue, maxSplit: Int.max, allowEmptySlices: false, isSeparator: {$0=="."})
if array.count == 2 {
firstName=array[0]
lastName=array[1]
}
}
}
lazy var account = Account()
init(firstName:String,lastName:String,age:Int){
self.firstName=firstName
self.lastName=lastName
self.age=age
}
func showMessage(){
println("name=\(self.fullName)")
}
}
var p=Person(firstName: "Kenshin", lastName: "Cui",age:29)
p.fullName="Kaoru.Sun"
p.account.balance=10
p.showMessage()
|
需要提醒大家的是:
- 计算属性并不直接存储一个值,而是提供getter来获取一个值,或者利用setter来间接设置其他属性;
- lazy属性必须有初始值,必须是变量不能是常量(因为常量在构造完成之前就已经确定了值);
- 在构造方法之前存储属性必须有值,无论是变量属性(var修饰)还是常量属性(let修饰)这个值既可以在属性创建时指定也可以在构造方法内指定;
从上面的例子中不难区分存储属性和计算属性,计算属性通常会有一个setter、getter方法,如果要监视一个计算属性的变化在setter方法中即可办到(因为在setter方法中可以newValue或者自定义参数名),但是如果是存储属性就无法通过监视属性的变化过程了,因为在存储属性中是无法定义setter方法的。不过Swift为我们提供了另外两个方法来监视属性的变化那就是willSet和didSet,通常称之为“属性监视器”或“属性观察器”。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
class Account {
var balance:Double=0.0{
willSet{
self.balance=2.0
println("Account.balance willSet,newValue=\(newValue),value=\(self.balance)")
}
didSet{
self.balance=3.0
println("Account.balance didSet,oldValue=\(oldValue),value=\(self.balance)")
}
}
}
class Person {
var firstName:String
var lastName:String
let age:Int
var fullName:String{
get{
return firstName + "." + lastName
}
set{
let array=split(newValue, maxSplit: Int.max, allowEmptySlices: false, isSeparator: { $0 == "." })
if array.count == 2 {
firstName=array[0]
lastName=array[1]
}
}
}
lazy var account = Account()
init(firstName:String,lastName:String,age:Int){
self.firstName=firstName
self.lastName=lastName
self.age=age
}
static var skin:Array<String>{
return ["yellow","white","black"];
}
}
var p=Person(firstName: "Kenshin", lastName: "Cui",age:29)
p.account.balance=1.0
println("p.account.balance=\(p.account.balance)")
for color in Person.skin {
println(color)
}
|
- 和setter方法中的newValue一样,默认情况下载willSet和didSet中会有一个newValue和oldValue参数表示要设置的新值和已经被修改过的旧值(当然参数名同样可以自定义);
- 存储属性的默认值设置不会引起属性监视器的调用(另外在构造方法中赋值也不会引起属性监视器调用),只有在外部设置存储属性才会引起属性监视器调用;
- 存储属性的属性监视器willSet、didSet内可以直接访问属性,但是在计算属性的get、set方法中不能直接访问计算属性,否则会引起循环调用;
- 在didSet中可以修改属性的值,这个值将作为最终值(在willSet中无法修改);
方法
方法就是与某个特定类关联的函数,其用法和前面介绍的函数并无二致,但是和ObjC相比,ObjC中的函数必须是C语言,而方法则必须是ObjC。此外其他语言中方法通常存在于类中,但是Swift中的方法除了在类中使用还可以在结构体、枚举中使用。关于普通的方法这里不做过多赘述,用法和前面的函数区别也不大,这里主要看一下构造方法。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
class Person {
var name:String
var height:Double
var age=0
init(name:String,height:Double,age:Int){
self.name=name
self.height=height
self.age=age
}
convenience init(name:String){
self.init(name:name,height:0.0,age:0)
}
func modifyInfoWithAge(age:Int,height:Double){
self.age=age
self.height=height
}
class func showClassName(){
println("Class name is \"Person\"")
}
deinit{
println("deinit...")
}
}
var p=Person(name: "kenshin")
|
- 除构造方法、析构方法外的其他方法的参数默认除了第一个参数是局部参数,从第二个参数开始既是局部参数又是外部参数(这种方式和ObjC的调用方式很类似,当然,可以使用“#”将第一个参数同时声明为外部参数名,也可以使用“_”将其他参数设置为非外部参数名)。但是,对于函数,默认情况下只有默认参数既是局部参数又是外部参数,其他参数都是局部参数。
- 构造方法的所有参数默认情况下既是外部参数又是局部参数;
- Swift中的构造方法分为“指定构造方法”和“便利构造方法(convenience)”,指定构造方法是主要的构造方法,负责初始化所有存储属性,而便利构造方法是辅助构造方法,它通过调用指定构造方法并指定默认值的方式来简化多个构造方法的定义,但是在一个类中至少有一个指定构造方法。
下标脚本
下标脚本是一种访问集合的快捷方式,例如:var a:[string],我们经常使用a[0]、a[1]这种方式访问a中的元素,0和1在这里就是一个索引,通过这种方式访问或者设置集合中的元素在Swift中称之为“下标脚本”(类似于C#中的索引器)。从定义形式上通过“subscript”关键字来定义一个下标脚本,很像方法的定义,但是在实现上通过getter、setter实现读写又类似于属性。假设用Record表示一条记录,其中有多列,下面示例中演示了如何使用下标脚本访问并设置某一列的值。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
class Record {
var store:[String:String]
init(data:[String:String]){
self.store=data
}
subscript(index:Int)->String{
get{
var key=sorted(Array(self.store.keys))[index]
return self.store[key]!
}
set{
var key=sorted(Array(self.store.keys))[index]
self.store[key]=newValue
}
}
subscript(key:String)->String{
get{
return store[key]!
}
set{
store[key]=newValue
}
}
}
var r=Record(data:["name":"kenshin","sex":"male"])
println("r[0]=\(r[0])")
r["sex"]="female"
println(r[1])
|
继承
和ObjC一样,Swift也是单继承的(可以实现多个协议,此时协议放在后面),子类可以调用父类的属性、方法,重写父类的方法,添加属性监视器,甚至可以将只读属性重写成读写属性。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
class Person {
var firstName:String,lastName:String
var age:Int=0
var fullName:String{
get{
return firstName+" "+lastName
}
}
init(firstName:String,lastName:String){
self.firstName=firstName
self.lastName=lastName
}
func showMessage(){
println("name=\(fullName),age=\(age)")
}
final func sayHello(){
println("hello world.")
}
}
class Student: Person {
override var firstName:String{
willSet{
println("oldValue=\(firstName)")
}
didSet{
println("newValue=\(firstName)")
}
}
var score:Double
init(firstName:String,lastName:String, score:Double){
self.score=score
super.init(firstName: firstName, lastName: lastName)
}
convenience init(){
self.init(firstName:"",lastName:"",score:0)
}
override var fullName:String{
get{
return super.fullName;
}
set{
let array=split(newValue, maxSplit: Int.max, allowEmptySlices: false, isSeparator: { $0 == "." })
if array.count == 2 {
firstName=array[0]
lastName=array[1]
}
}
}
override func showMessage() {
println("name=\(fullName),age=\(age),score=\(score)")
}
}
var p=Student()
p.firstName="kenshin"
|
在使用ObjC开发时init构造方法并不安全,首先无法保证init方法只调用一次,其次在init中不能访问属性。但是这些完全依靠文档约定,编译时并不能发现问题,出错检测是被动的。在Swift中构造方法(init)有了更为严格的规定:构造方法执行完之前必须保证所有存储属性都有值。这一点不仅在当前类中必须遵循,在整个继承关系中也必须保证,因此就有了如下的规定:
- 子类的指定构造方法必须调用父类构造方法,并确保调用发生在子类存储属性初始化之后。而且指定构造方法不能调用同一个类中的其他指定构造方法;
- 便利构造方法必须调用同一个类中的其他指定构造方法(可以是指定构造方法或者便利构造方法),不能直接调用父类构造方法(用以保证最终以指定构造方法结束);
- 如果父类仅有一个无参构造方法(不管是否包含便利构造方法),子类的构造方法默认就会自动调用父类的无参构造方法(这种情况下可以不用手动调用);
- 常量属性必须默认指定初始值或者在当前类的构造方法中初始化,不能在子类构造方法中初始化;
协议
协议是对实例行为的一种约束,和ObjC类似,在Swift中可以定义属性和方法(ObjC中之所以能定义属性是因为@property的本质就是setter、getter方法)。和其他语言不同的是Swift中的协议不仅限于类的实现,它同样可以应用于枚举、结构体(如果只想将一个协议应用于类,可以在定义协议时在后面添加class关键字来限制其应用范围)。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
protocol Named{
var name:String { get set }
static var className:String { get }
init(name:String)
func showName()
static func showClassName()
}
protocol Scored{
var score:Double { get set }
}
class Person:Named {
var name:String
var age:Int = 0
static var className:String{
return "Person"
}
required init(name:String){
self.name=name
}
func showName() {
println("name=\(name)")
}
static func showClassName() {
println("Class name is \"Person\"")
}
}
class Student: Person,Scored {
var score:Double=0.0
init(name:String, score:Double){
self.score = score
super.init(name: name)
}
required init(name: String) {
super.init(name: name)
}
func test(){
println("\(self.name) is testing.")
}
}
var p=Person(name: "Kenshin Cui")
p.showName()
println("className=\(Person.className)")
Person.showClassName()
p.age=28
var s:Named=Student(name: "Kaoru",score:100.0)
s.showName()
func showMessage(stu:protocol<Named,Scored>){
println("name=\(stu.name),score=\(stu.score)")
}
var s2=Student(name: "Tom",score:99.0)
showMessage(s2)
let b1 = s is Scored
if b1 {
println("s has score property.")
}
if let s3 = s as? Scored {
println("s3' score is \(s3.score)")
}
let s4 = s as! Scored
println("s4' score is \(s4.score)")
|
- 协议中虽然可以指定属性的读写,但即使协议中规定属性是只读的但在使用时也可以将其实现成可读写的;
- Swift的协议中可以约定属性是实例属性还是类型属性、是读写属性还是只读属性,但是不能约束其是存储属性还是计算属性;
- 协议中的类型属性和类型方法使用static修饰而不是class(尽管对于类的实现中类型属性、类型方法使用class修饰);
- 协议中约定的方法支持可变参数,但是不支持默认参数;
- 协议中约定的构造方法,在实现时如果不是final类则必须使用require修饰(以保证子类如果需要自定义构造方法则必须覆盖父类实现的协议构造方法,如果子类不需要自定义构造方法则不必);
- 一个协议可以继承于另外一个或多个协议,一个类只能继承于一个类但可以实现多个协议;
- 协议本身就是一种类型,这也体现除了面向对象的多态特征,可以使用多个协议的合成来约束一个实例参数必须实现某几个协议;
扩展
Swift中的扩展就类似于ObjC中的分类(事实上在其他高级语言中更多的称之为扩展而非分类),但是它要比分类强大的多,它不仅可以扩展类还可以扩展协议、枚举、结构体,另外扩展也不局限于扩展方法(实例方法或者类型方法),还可以扩展便利构造方法、计算属性、下标脚本、
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
class Person {
var firstName:String,lastName:String
var age:Int=0
var fullName:String{
get{
return firstName+" "+lastName
}
}
init(firstName:String,lastName:String){
self.firstName=firstName
self.lastName=lastName
}
func showMessage(){
println("name=\(fullName),age=\(age)")
}
}
extension Person{
convenience init(){
self.init(firstName:"",lastName:"")
}
var personInfo:String{
return "firstName=\(firstName),lastName=\(lastName),age=\(age)";
}
func sayHello(){
println("hello world.")
}
enum SkinColor{
case Yellow,White,Black
}
static func skin()->[SkinColor]{
return [.Yellow,.White,.Black]
}
}
var p=Person()
p.firstName="Kenshin"
p.lastName="Cui"
p.age=28
println(p.personInfo)
p.sayHello()
Person.skin()
|
枚举和结构体
结构体
结构体和类是构造复杂数据类型时常用的构造体,在其他高级语言中结构体相比于类要简单的多(在结构体内部仅仅能定义一些简单成员),但是在Swift中结构体和类的关系要紧密的多,这也是为什么将结构体放到后面来说的原因。Swift中的结构体可以定义属性、方法、下标脚本、构造方法,支持扩展,可以实现协议等等,很多类可以实现的功能结构体都能实现,但是结构体和类有着本质区别:类是引用类型,结构体是值类型。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
struct Person {
var firstName:String
var lastName:String
var fullName:String{
return firstName + " " + lastName
}
var age:Int=0
func showMessage(){
println("firstName=\(firstName),lastName=\(lastName),age=\(age)")
}
static func showStructName(){
println("Struct name is \"Person\"")
}
}
var p=Person(firstName: "Kenshin", lastName: "Cui", age: 28)
println(p.fullName)
p.showMessage()
Person.showStructName()
var p2 = p
p2.firstName = "Tom"
println(p2.fullName)
println(p.fullName)
|
- 默认情况下如果不自定义构造函数那么将自动生成一个无参构造函数和一个全员的逐一构造函数;
- 由于结构体是值类型,所以它虽然有构造函数但是没有析构函数,内存释放系统自动管理不需要开发人员过多关注;
- 类的类型方法使用class修饰(以便子类可以重写),而结构体、枚举的类型方法使用static修饰(补充:类方法也可以使用static修饰,但是不是类型方法而是静态方法;另外类的存储属性如果是类型属性使用static修饰,而类中的计算属性如果是类型属性使用class修饰以便可以被子类重写;换句话说class作为“类型范围作用域”来理解时只有在类中定义类型方法或者类型计算属性时使用,其他情况使用static修饰[包括结构体、枚举、协议和类型存储属性]);
枚举
在其他语言中枚举本质就是一个整形,只是将这组相关的值组织起来并指定一个有意义的名称。但是在Swift中枚举不强调一个枚举成员必须对应一个整形值(当然如果有必要仍然可以指定),并且枚举类型的可以是整形、浮点型、字符、字符串。首先看一下枚举的基本使用:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
enum Season{
case Spring
case Summer
case Autumn
case Winter
}
var s=Season.Spring
s = .Summer
switch s {
case .Spring:
println("spring")
case .Summer:
println("summer")
case .Autumn:
println("autumn")
default:
println("winter")
}
|
事实上Swift中也可以指定一个值和枚举成员对应,就像其他语言一样(通常其他语言的枚举默认就是整形),但是Swift又不局限于整形,它可以是整形、浮点型、字符串、字符,但是原始值必须是一种固定类型而不能存储多个不同的类型,同时如果原始值为整形则会像其他语言一样默认会自动递增。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
enum Season:Int{
case Spring=10
case Summer
case Autumn
case Winter
}
var summer=Season.Summer
println("summer=\(summer),rawValue=\(summer.rawValue)")
var autumn=Season(rawValue: 12)
if let newAutumn=autumn{
println("summer=\(newAutumn),rawValue=\(newAutumn.rawValue)")
}
|
如果一个枚举类型能够和一些其他类型的数据一起存储起来往往会很有用,因为这可以让你存储枚举类型之外的信息(类似于其他语言中对象的tag属性,但是又多了灵活性),这在其他语言几乎是不可能实现的,但是在Swift中却可以做到,这在Swift中称为枚举类型相关值。要注意的是相关值并不是原始值,原始值需要事先存储并且只能是同一种类型,但是相关值只有创建一个基于枚举的变量或者常量时才会指定,并且类型可以不同(原始值更像其他语言的枚举类型)。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
enum Color{
case RGB(String)
case CMYK(Float,Float,Float,Float)
case HSB(Int,Int,Int)
}
var red=Color.RGB("#FF0000")
var green=Color.CMYK(0.61, 0.0, 1.0, 0.0)
var blue=Color.HSB(240, 100, 100)
switch red {
case .RGB(let colorStr):
println("colorStr=\(colorStr)")
case let .CMYK(c,m,y,k):
println("c=\(c),m=\(m),y=\(y),k=\(k)")
case let .HSB(h,s,b):
println("h=\(h),s=\(s),b=\(b)")
}
|
上面提到其实枚举也有一些类型和结构体的特性,例如计算属性(包括类型属性,枚举只能定义计算属性不能定义存储属性,存储属性只能应用于类和结构体)、构造方法(其实上面使用原始值创建枚举的例子就是一个构造方法)、方法(实例方法、类型方法)、下标脚本 。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
enum Season:Int{
case Spring=0 ,Summer,Autumn,Winter
var tag:Int{
return self.rawValue
}
static var enumName:String{
return "Season"
}
init?(prefix:String){
switch prefix.lowercaseString {
case "sp":
self = .Spring
case "su":
self = .Summer
case "au":
self = .Autumn
case "wi":
self = .Winter
default:
return nil
}
}
func showMessage(){
println("rowValue=\(self.rawValue)")
}
static func showEnumName(){
println("Enum name is \"Season\"")
}
}
var summer=Season.Summer
println(summer.tag)
println(Season.enumName)
Season.showEnumName()
summer.showMessage()
if let spring = Season(prefix: "au") {
println(spring.tag)
}
|
泛型
泛型可以让你根据需求使用一种抽象类型来完成代码定义,在使用时才真正知道其具体类型。这样一来就好像在定义时使用一个占位符做一个模板,实际调用时再进行模板套用,所以在C++中也称为“模板”。泛型在Swift中被广泛应用,上面介绍的Array<>、Dictionary<>事实上都是泛型的应用。通过下面的例子简单看一下泛型参数和泛型类型的使用。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
func isEqual<T:Equatable>(a:T,b:T)->Bool{
return a == b
}
var a:Int=1,b:Int=2
println(isEqual(a,b))
var c:String="abc",d:String="abc"
println(isEqual(c,d))
struct Stack<T> {
var store:[T]=[]
mutating func push(item:T){
store.append(item)
}
mutating func pop()->T{
return store.removeLast()
}
}
var s = Stack<Int>()
s.push(1)
let t = s.pop()
println("t=\(t)")
extension Stack{
var top:T?{
return store.last
}
}
s.push(2)
println(s.top!)
|
上面演示了泛型结构体用法,其实类同样是类似的,这里就不在赘述了,但是如果遇到泛型协议怎么办呢?假设Stack必须遵循一个Stackable协议,此时就必须在协议中引入一个关联类型来解决。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
protocol Stackable{
typealias ItemType
mutating func push(item:ItemType)
mutating func pop()->ItemType;
}
struct Stack:Stackable{
var store:[T]=[]
mutating func push(item:T){
store.append(item)
}
mutating func pop()->T{
return store.removeLast()
}
}
var s = Stack()
s.push("hello")
s.push("world")
let t = s.pop()
println("t=\(t)")
|
来源:https://www.cnblogs.com/wangjiadashao/p/17376720.html |