swift充分利用了语言设计的悠久历史,拥有非常多的设计特性,使得软件开发变得更容易,更简单,更安全。
- 安全
swift设计的初衷就是一门安全的语言,c语言中有许多缺陷,比如意外使用null指针,这些很难在swift中遇到。swift非常重视强类型化,除了一些极为特殊的情况下,它是不允许为空的。
- 现代
swift包含了大量的现代语言的特性,可以轻松的表达代码逻辑。这些特性包括:模式匹配switch语句,闭包,所有值都是对象的概念
- 强大
swift可以访问整个Object-c运行时,而且可以无缝的连接到Object-c的类,意味着我们可以马上用swift编写出完整的ios和OSX App,不用等着别人从Oc向swift移植任何功能
基础语法
变量和常量
在swift中,let用来定义常量,如果一个值定义之后不希望再被改变,可以用let来声明
var用来定义变量,如果一个值一直再被赋值和变化,则可以用var来声明
swift中的常量必须拥有值,如果定义了一个常量,但是没有给定值会报错的。
1 2 |
|
变量可以不包含值,只要不尝试访问它就行。换句话说,如果创建了一个变量,但没有为它设置值,那唯一能做的就是为它指定一个值。之后就可以使用它了。
1 2 3 4 5 6 7 |
|
类型
我们不需要定义一个变量是什么类型,Swift可以根据它的初始值做出判断。这意味着在你定义一个变量并为其设定数值2时,这个变量会是Int类型:
//隐式指定整数类型
var anInteger = 2
大多数类型都不能合并,因为编译器不知道结果会是什么类型。例如:我们不能将一个string值加到一个int值上。因为其结果毫无意义。
在Object-c中,nil实际上被定义为一个指向0的void指针。严格来说,它是一个数字,这就意味着我们可以进行类似下面的操作:
int i=(int)(nil)+2
//等于2(因为0+2=2)
这在swift中是不允许的,因为nil和Int是不同类型
swift中的所有变量都是需要有取值的。如果希望允许一个变量在某些时候为nil,那就使它成为一个可选变量。可选变量的定义是在其类型中包含一个问号(?)
//可选整数,允许为nil
var anOptionalInteger : Int? = nil
anOptionalInteger = 22
只有可选变量才允许被设置为nil.如果一个变量没有被定义为nil,那就不允许将它设定为nil值:
//非可选,不允许为nil
var aNonOptionalInteger = 32
aNonOptionalInteger = nil
//错误,只有可选值才能为nil
可以使用if语句来查看一个可选变量是否拥有值
if anOptionalInteger !=nil {
println("It has a value")
}else{
println("It has no value");
}
对于可选变量,可以进行拆包操作,获得其取值。这一工作用 感叹号 !实现。
注意
如果对一个可选变量进行拆包,而它并没有值,程序将会抛出一个运行时错误,并会崩溃
//可选类型必须使用!拆包
anOptionalInteger=2
1+anOptionalIntger! //3
anOptionalInteger = nil
1+anOptionalInteger!
//崩溃:anOptionalInteger=nil,不能使用nil数据
如果不希望每次用到可选变量都要对其进行拆包,可以将它声明为已拆包的
var unwrappedOptionalInteger:int!
unwarappedOptionalInteger=1
1+ unwarappedOptionalInteger //2
这样就可以直接使用它们的值,但可能会不安全(因为它让你逃避了在需要时对其进行拆包的操作,可能会让你忘了它们有时会是nil).谨慎使用
在swift中,可以在不同类型之间进行转换。例如:要将一个Int转换为一个string,可以这样做
let aString=String(anInteger)
//"2"
元组
元组是数据的一个简单集合。利用元组,可以将多个值一起捆绑到单个值中:
let aTuple = (1,"YES")
有了元组,就可以从中提取到值:
let theNumber = aTuple.0 //=1
除了用数字提取元组的值之外,还可以为元组中的值添加标签:
let anotherTuple = (aNumber:1,aString: "YES")
let theOhterNumber = anotherTuple.aNumber //1
数组
swift中的数组很容易使用。要创建一个数组,可以直接用 []:
//整数数组
let arrayOfInteger : [Int] = [1,2,3]
Swift还可以推断出数组的类型:
//暗含了数组类型
let implicitArrayofIntegers=[1,2,3]
还可以创建空数组,不过,这种情况需要人工指定其类型
let anotherArray=[Int]()
用let关键字定义的数组,其内容是不可变的,也就是说,不允许改变其内容
有了数组之后,就可以使用其内容了。例如,可以使用append函数向数组的末尾追加对象
var myArray =[1,2,3,4]
myArray.append(4)
除了在数组的末尾追加对象之外,还可以插入对象
myArray.insert(5,atIndex:0)
字典
字典是一种将键映射到值的类型。当希望表示一组相关信息时,字典是很有用的。 声明一个字典
var crew=["caption":"张三",
"first officer":"jack",
"second officer":"david"]
有了字典,就可以通过下标来访问其内容。下标就是在变量名之后用方括号 []的地方。
crew["Caption"] //=张三
控制流
在swift中,所有的if语句以及所有的循环的主体都需要放在两个大括号。
if(something){
//这对大括号是必须的
}
当拥有一个集合时,比如一个数组,可以使用for-in循环来迭代每一项:
let loopingArray=[1,2,3,4,5]
var loopSum=0
for number in loopingArray{
loopSum += number
}
还可以使用for-in循环来迭代一个数值范围。例如
var firstCount = 0
for index in 1..<10{
firstCounter++
}
//循环9次
注意第一行中的..<运算符,这是一个范围运算符,swift用它描述一个值到另一个值的数值范围。实际上有两个范围的运算符:两个句点加一个做尖括号(..<),表示从第一个值开始知道最后一个值的一个范围(最后一个值不包含在内),例如5..<9 包含了数字 5,6,7,8. 如果希望创建一个包含最后数值的范围,可以使用三个句点(…),这里没有尖括号,范围5…9包含的数值 5,6,7,8,9
Switch
switch是一种根据变量值运行代码的强大方式。
switch可以判断整数,还可以判断字符串
let stringSwitch="Hello"
switch stringSwitch{
case "Hello":
println("A greeting");
case "Goodbye":
println("A farewell")
default:
println("something");
}
还可以对元组进行判断
var str = "Hello, playground"
let tupleSwith=("YES",123)
switch tupleSwith{
case ("YES",123):
print("Tuple contains 'YES' and 123");
break;
case ("YES", 1):
print("Tuple contains 'YES' and else")
break;
default:
print("tuple something")
break;
}
switch的工作方式与C和Object-C中有点不同,在swift中,switch语句中的某一部分执行完毕后,不会自动“落入”下一部分,也就是说,不需要使用break关键字。
函数和闭包
函数可以向调用它们的代码返回一个值。在定义一个具有返回值的函数时,必须使用箭头(->)指明所返回数据的类型
func sayHello()->Int{
return 123
}
sayHello()
我们也可以向函数中传送参数,让其能够利用它们完成任务,在为函数定义参数时,还需要定义这些参数的类型:
func thirdFunction(firstValue:Int, secondValue:Int)->Int{
return firstValue+secondValue;
}
thirdFunction(1,2)
一个函数只可以返回一个值,我们前面已经看到这种情况,但也可以通过元组的方式返回多个值。另外,可以为元组中的值附加名字,以便能够更轻松的处理返回值:
func fourthFunction(firstValue:Int,secondValue:Int)->(doubled:Int,test:Int){
return (firstValue+1,secondValue*4)
}
fourthFunction(2,3)
在调用一个会返回元组的函数时,可以用数字访问它的值,如果有名字的话,也可以用名字来访问:
//用数字访问
fourthFunction(2,4).1 //16
//其他相同,只是使用了名字:
fourthFunction(2,4).test //16
在定义函数时,可以为参数指定名字,当无法马上明白每个参数的用途时,这一功能会非常有用。可以像下面这样来定义参数的名字
func addNumbers(firstNumber num1:Int,toSecondNumber num2:Int)->Int{
return num1+num2;
}
addNumbers(firstNumber: 2, toSecondNumber: 3) //5
在为参数创建名字时,就是为参数创建一个内部名字和一个外部名字。内部名字供函数引用该参数,而外部名字工调用该函数的外部代码使用。如果函数没有命名参数,那每个参数就只有一个内部名字。
在创建参数时,还可以为其参数指定默认值,这就意味着在调用这些函数时可以省略特定的参数;
func multiplyNumber(firstNumber:Int,multiplier:Int=2)->Int{
return firstNumber+multiplier;
}
//可以省略具有默认值的参数
multiplyNumber(2);//4
有时,我们希望使用参数个数可变的函数,一个取值数目可变的参数成为可变参数,在这写情况下,我们希望一个函数能够处理任意数目的参,从0到一个无限数,为此可以使用三个句点(…)表示一个参数的取值是可变的。在函数的主体内部,可变参数变成一个数组,我们可以像使用其他数组一样使用它。
1 2 3 4 5 6 7 8 9 10 |
|
通常,函数以参数为输入时是按值传递的,输出返回的也是值,但是,如果有用inout关键字定义一个参数,那就可以按引用传送改参数,直接改变这个变量中存储的值。采用这种方式,可以用一个函数交换两个变量,如下所示:
1 2 3 4 5 6 7 8 9 10 |
|
在调用函数时,这个变量的值可能会发生变化。
将函数作为变量
函数可以存储在变量在中,为此,首先声明一个变量,它能够存储一个接受特定参数,返回一个值的函数。声明之后,只要一个函数的参数与返回值类型都与声明中的函数相同,可以将它存储在这个变量中:
1 2 3 4 5 6 7 |
|
函数还可以接受其他函数作为参数并使用它们。这意味着可以将函数合并到一起。
1 2 3 4 5 6 7 8 9 10 11 |
|
函数还可以返回其他函数。这意味着可以用函数创建新函数,并在自己的代码中使用这个新函数:
1 2 3 4 5 6 7 8 9 10 |
|
闭包
swift的另一个特性是闭包-一些小的匿名代码块,可以像函数一样使用。可以非常方便的将闭包传送给其它函数,告诉他们应当如何执行某一任务。
1 2 3 4 5 6 7 8 9 10 11 |
|
为了举例说明闭包如何工作,请考虑内置的sorted函数。这个函数接受一个数组和一个闭包作为参数,并用这个闭包来确定应当如何对数组的各个元素进行排序。
sorted函数接受一个数组,并返回同一数组的一个有序版本。除了sorted函数之外,有一个sort函数,它接受一个数组,并将其修改为有序版本:
var sortingInline=[2,3,5,19,1,10]
sort(&sortingInline)
sortingInline //
要对一个数组进行排序,使小的数字出现在大数字之前,可以这样做:
var numbers=[2,3,5,19,1,10]
var numbersSorted=sorted(numbers,{(n1:Int,n2:Int) - >Bool in
//进行排序,使得小数字出现在大数字之前
return n2>n1;
})
和函数一样,闭包可以接受参数。在上面的例子中,闭包指定了它所处理参数的名字和类型。但是,并不需要写的特别详细,编译器可以替我们推断参数的类型,非常类似于推断变量类型的方式。
var numbersSortedReverse=sorted(numbers,{n1,n2 in
return n1>n2;
})
如果不是特别在意参数拥有什么样的名字,可以让它更简便一些。如果省略了参数名,可以直接根据数字来引用每个参数(第一个参数名称为$0,第二个为$1)
另外,如果闭包只包含一行代码,可以省略return 关键字
var numbersSortedAgain = sorted(numbers,{
$1>$0
})
如果一个闭包是函数调用中的最后一个参数,可以将它放在括号外面。这纯粹是为了提高可读性,不会改变闭包的工作方式
var numbersSortedReversedAgain=sorted(numbers){
$0>$1
}
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 |
|
对象
在swift中类看起来是这样的
class Vechicle {
var color:String?
var maxSpeed=80
func description()->String{
return "A\(self.color) vehicle";
}
func travel(){
print("Traveling at \(maxSpeed) kph")
}
}
类中既包含了属性也包含方法。属性和方法都是类的组成部分,属性是变量,方法是函数。
例如,要定义Vehicle类的一个实例,我们定义一个变量
var redVehicle=Vehicle()
redVehicle.color="Red";
redVehicle.maxSpeed=100;
redVehicle.travel();
redVehicle.description()
初始化与反初始化
在swift中创建对象时,会调用一个被称为其初始化器的特殊方法。初始化器是用来为对象设定初始状态的方法。
除了初始化器之外,还有一个反初始化器,可以在对象消失时运行其中的代码。此方法在对象的retaincount数降到0时运行,就在要将该对象从内存中清除时调动。要想在对象永远消失之前进行一些必要的清理工作,这是最后一个机会。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
初始化器还可以返回nil.当初始化其不能成功地构造一个函数时,这一点很有用。例如,NSURL类有一个初始化器,它接受一个字符串,并将它转化为URL;如果这个字符串不是一个有效的URL,则初始化器返回nil.
要创建一个可以返回nil的初始化器,就在init关键字后面加一个问号,并在初始化器确定它不能成功地构造该对象时,return nil;
convenience init?(value:Int){
self.init();
if value > 5{
//不能初始化这个对象;返回nil,表示初始化失败
return nil
}
}
属性
类将其数据存储在属性中。在前面的例子中,属性是存储在对象中的一个简单值。在swift中,它称为存储属性。但是利用属性可以做很多事情,包括创建一些属性,利用代码来计算它们的值。这些属性称为计算属性,可以用它们提供一个更简单的接口,用来访问类中存储的信息。
例如,考虑一个代表矩形的类,它有一个width属性和一个height属性。再增加一个包含面积值的属性应该是很有用的,但你不想再有第三个属性,而是使用一个计算属性。从外部看来,这就是一个普通属性,但是从内部来说,它实际上是一个函数,可以在需要的时候计算其取值。
要定义一个计算属性,可以像声明存储属性一样声明一个变量,但在后面增加大括号({和}),在这些大括号内部,提供一个get部分,还可以提供有一个set部分;
class Retangle {
var width:Double=0.0
var height:Double=0.0
var area:Double{
//计算getter
get{
return width*height;
}
//计算setter
set{
width=sqrt(newValue)
height=sqrt(newValue)
}
}
}
在上面的例子中,面积是通过计算长和宽的乘积而得到的,这个属性也是可设定的–如果设定了矩形的面积,代码就假定你希望创建一个正方形,并更新宽度和长度值。
1 2 3 4 5 |
|
在使用属性时,经常希望在一个属性发生变化时运行某些代码。为支持这一个功能,Swift属性允许向属性添加观察期,也就是一些小的代码块,可以在一个属性值即将发生变化之前运行。要创建一个属性观察期,需在属性后面添加一对大括号,并包含willSet和didSet代码块。这些块会分别获得一个参数-willSet在属性值发生变化之前被调用,它获得的是一个将要设定的值,而didSet获取的是一个旧值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
我们还可以让属性变成惰性的。惰性属性就是知道首次访问时才会设定的属性。类的某些设置工作需要耗费大量的时间,利用惰性属性可以将这些工作推迟到将来真正需要时完成。为将一个属性定义为惰性的,可以在它的前面放一个lazy关键字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
在这个例子中,当创建lazyExample变量时,它立即创建SomeExpensiveClass的第一个实例。但是,第二个实例将一直等到代码中实际用到它时才会创建。
协议
协议可以看做是一个类的需求清单。定义协议,就是创建一个属性和方法清单,类可以声明他们拥有这些属性和方法。
协议看起来与类非常相似,只是我们没有提供任何实际的代码–只是定义了存在哪些种类的属性和函数,以及如何访问它们。
例如:
protocol Blinking{
//这个属性必须(至少是)可获取的
var isBlining:Bool{get}
//这个属性必须是可获取的和可设置的
var blinkSpeed:Double{get set}
//这个函数必须存在,但是它做些什么由是实现者决定
func startBlinking(blinkSpeed:Double)->Void
}
有了协议,就可以创建遵守协议的类。当一个类遵守了协议时,就是向编译器做出了承诺:它实现了这个协议中列出的所有属性和方法,除此之外,它还可以有其他很多属性和方法,也可以遵守多个协议。
class Light:Blinking {
var isBlining:Bool=false;
var blinkSpeed:Double=0.0
func startBlinking(blinkSpeed: Double) {
print("I am now blikning")
isBlining=true
//我们说这里的self.blinkSpeed是为了帮助编译器
//判断参数'blinkSpeed'和属性之间的区别
self.blinkSpeed=blinkSpeed;
}
}
调用如下:
1 2 3 4 5 6 7 8 9 |
|
泛型
Swift是一种静态类型化语言,这就是说,Swift编译器需要确切的了解你的代码正在处理什么类型的信息。这意味着你不能将字符串传送给打算用来出来日期的代码,而在Objective-c中是可能发生这种情况的。
利用泛型,在编写代码时,不需要准确地知道这些信息的类型。数组就是泛型的一个应用实例:数组实际上并没有对自己存储的数据进行任何操作,只是将他们存储为一个有序集合,事实上,数组就是泛型。
要创建一个泛型类型,可以像通常一样为对象命名,然后在两个尖括号之间指定任务泛型类型。传统上使用的术语是T.
class Tree<T> {
var value:T
var children:[Tree<T>]=[]
init(value:T){
self.value=value;
}
func addChild(value:T)->Tree<T>{
let newChild=Tree<T>(value: value)
children.append(newChild)
return newChild;
}
}
一旦定义了一个泛型,就可以由它创建一个具体的非泛型类型。例如,可以使用刚刚创建的Tree类型,创建一个用于处理Int的版本和一个处理String的版本。
1 2 3 4 5 6 7 8 9 10 |
|
序列化与反序列化
我们还可以将对象转化为数据。为此,首先要使对象遵守NSObject和NSCoding协议,然后添加两个方法:encodeWithCoder,一个以NScoder为参数的初始化函数:
class SerializableObject:NSObject,NSCoding {
var name:String?
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(name!,forKey: "name")
}
override init(){
self.name="My object"
}
required init(coder aDecoder:NSCoder){
self.name=aDecoder.decodeObjectForKey("name") as? String
}
}
这些对象与数据之间的相互转换非常简单
let anObject=SerializableObject()
anObject.name="My thing that I'm saving"
//将它转换为数据
let objectConvertedToData=NSKeyedArchiver.archivedDataWithRootObject(anObject);
//将其转换回来,
//注意,此转换可能会失败,所以'unarchiveObjectWithData'返回一个可选值
//因此,使用‘as?’来查看它是否成功
let loadedObject=NSKeyedUnarchiver.unarchiveObjectWithData(objectConvertedToData) as? SerializableObject
loadedObject?.name