一、基础语法
swift语句结束不需要分号(写了也没有问题),有一种情况需要分号,如果一行代码中有多条语句,这时候就必须要分号隔开
swift字符串,数组,字典不需要@标示
swift是类型安全的语言,所有的类型都不会自动转换(如:Int和UInt类型不能直接运算),同时swift具有强大的类型推测,所以很多时候我们不需要声明类型
swift的==布尔值使用小写true和false,而不用YES/NO==,++判断语句只能使用==Bool==类型++
二、数据类型
与objc一样,swift支持以前(objc)使用的所有数据类型,swift的类型名字首字母大写,如Int, Float, NSInteger
swift支持可选类型(Optionals)类型,标识变量可能为空,基础数据类型也可为空,可选类型不能直接赋非可选类型
var a: Int? = 10
var b: Int = a // 报错,不同类型不能赋值
- swift支持使用_来分割数值来增强可读性而不影响值,如一亿可以表示为下面形式
let oneMillion = 1_000_000
- swift数值类型进行运算符计算的时候不会自动进行类型转换,通常可以通过类型的构造方法进行类型转换
var a: Int = 12
var b: Float = 23
var c = a + b // 报错
var d = Float(a) + b // 正确
三、常量变量
与C/Obj-C不同,swift的常量更为广义,支持任意类型,==常量只能赋值一次==
swift的变量和常量在声明的时候类型就已经确定(由编译器自动识别或开发者指定)
使用let声明的集合为可变集合,使用var声明的集合为不可变集合
// 常量:使用let声明,赋值后就不能再修改
let a = NSMutableArray()
let b = 12
let c: Float = 12 // 类型标注(type annotation)
let d = b + 12
a.addObject(11) // str == [11]
let e = a // str == [11], d == [11]
a.addObject(12) // str == [11, 12], d == [11, 12]
// 变量:使用var声明
var f: Double? = 12
var g = "hello world"
四、序列和集合
1. 字符串String
swift字符串是由Character字符组成的集合,==支持+操作符==,字符串与值类型(与Int, Float)一样,是值类型,在传值的时候都会进行拷贝,当然这回带来一定的性能损耗,swift编译器在编译的时候会进行优化,保证只在必要的情况下才进行拷贝
// 1. 与NSString不同,声明不需要@前缀,支持转移字符
let name1 = "bomo\n"
// 2. 空串(下面两种方式等价)
let name2 = ""
let name3 = String()
// 3. 字符串由字符Character组成,定义字符
let character1: Character = "!"
// 4. 常见属性,方法
name1.isEmpty // 判空
name1.characters.count // 获取字符串的字符数
name1.uppercased() // 转大写
name1.lowercased() // 转小写
name1.hasPrefix("bo") //前缀
name1.hasSuffix("mo") //后缀
// 5. 加法运算
let hello = "hello " + name1 // hello bomo\n
// 6. 比较(比较值,而不是地址)
let name4 = "b" + "omo\n"
name4 == name1 // True
// 7. 字符串插值(使用反斜杠和括号站位)
let city = "广州"
let hello2 = "I'm \(name1) from \(city)"
// 8. 格式化字符串
let f = 123.3233
var s = String(format: "%.2f", f) //123.32
1. 数组Array
swift的数组可以是有类型的(泛型),存放同类型的数据,如果添加一个错误的类型会报编译错误,默认情况下编译器会自动识别
//1. 数组的写法为:Array<Int>,也可以简写成[Int]
//2. 数组初始化与NSArray类似,直接用中括号括起来,里面值用逗号隔开
var array0 = [Int]()
var array1: [Int] = [1, 3, 5, 7, 9]
var array2: Array<Int> = array1
array1.append(11) // [1, 3, 5, 7, 9, 11]
array1.insert(0, atIndex: 0) // [0, 1, 3, 5, 7, 9, 11]
array1.isEmpty // False
array1.count // 7
// 3. 如果初始化时不指定类型,而编译器也不能识别出类型,这时候,会被当成NSArray处理
var array3 = [] // array3 为 NSArray类型的空数组
// 4. 如果声明的时候使用不同的类型,编译器会把数组识别为NSObject类型
var array4 = ["fdsa", 121] // array4 为 Array<NSObject> 类型
// 5. 集合支持加法运算,相当于NSMutableArray的addObjectsFromArray
array1 += [2, 4, 6, 8, 10] // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
// 6. 使用let声明的数组不可变,不能修改数组array3
let array5: [Int] = [1, 3, 5, 7, 9]
//array5.append(2) // 报编译错误
// 7. 集合使用下标索引,支持区间索引,区间不可越界
var array6: [Int] = [1, 3, 5, 7, 9]
array6[1] = 4 // [1, 3, 5, 7, 9]
array6[1...3] = [2, 3, 4] // [1, 2, 3, 4, 9]
array6[0...2] = array6[1...3] // [2, 3, 4, 4, 9]
// 8. 迭代数组的时候,如果需要索引,可以用enumerate方法
for (index, value) in array4.enumerated() {
//do something
}
2. 字典Dictionary
与数组类型一样,字典也支持泛型,其键值类型都可以指定或有编译器识别,其中Key的类型,==必须遵循Hashable协议(哈希表==),swift中基础数据类型都是可hash的(String、Int、Double和Bool)
// 1. 用法与oc类似,初始化不需要@
var dict1 = ["key1": 1, "key2": 2, "key3": 3]
// 2. 声明方式
var dict2: Dictionary<String, Int> = dict1 //dict2与dict1不是一个对象
var dict3: [String: Int] = dict1 //通常采用这种方式声明类型
// 3. 不声明类型,编译器又无法识别,则为NSDictionary
var dict4 = [:]
var dict5: [Int: String] = [:]
// 4. 修改或添加键值对
dict1["key3"] = 4
// 5. 删除键
dict1["key3"] = nil
// 6. key不存在不报错,返回可空类型nil
let value4 = dict1["key4"]
// 7. 字典迭代返回key/value元组,类似python
for (key, value) in dict1 {
print("\(key) = \(value)")
}
3. Set集合
Set集合用于存放无序不重复的对象,用法与数组类似,重复的项会被忽略
var s: Set<Int> = [1, 3, 5, 6, 7, 4, 3, 7] // [1, 3, 4, 5, 6, 7]
s.count
s.isEmpty
s.insert(3)
s.remove(3)
s.contains(3)
集合操作
使用“是否相等”运算符( == )来判断两个 合是否包含全部相同的值。
使用 isSubset(of:) 方法来判断一个 合中的值是否也被包含在另外一个 合中。
使用 isSuperset(of:) 方法来判断一个 合中包含另一个 合中所有的值。
使用 isStrictSubset(of:) 或者 isStrictSuperset(of:) 方法来判断一个 合是否是另外一个 合的子 合或 者父 合并且两个 合并不相等。
使用 isDisjoint(with:) 方法来判断两个 合是否不含有相同的值(是否没有交 )
4. 元组Tuple
swif也支持元组,可以很方便的使用元组包装多个值,也使得函数返回多个值变得更加方便,特别是临时组建值得时候
支持任意类型
支持同时赋值
支持自定义key,支持索引
==元组不是对象,不是AnyObject类型,由于swift是强类型的,所以元组有时不能当做普通的对象使用,例如不能把元组加到数组里面,元组内的所有类型必须是明确的==
// 1. 声明一个元组,元组支持任意类型
let httpError1 = (404, "Not Found")
let point = (100, 50)
// 2. 可以分别赋值
let (x, y) = point
print(x) // 100
print(y) // 50
// 3. 使用下标取元组元素,下标从0开始
print(httpError1.0) // 404
print(httpError1.1) // Not Found
// 4. 可以给数组元素取名
let httpError2 = (code: 404, errorMessage: "Not Found")
print(httpError2.code) // 404
print(httpError2.errorMessage) // Not Found
// 5. 可以用下划线表示忽略部分值
let (a, _) = point
==6. 集合的赋值和拷贝行为==
swift的集合通常有Array和Dictionary,他们在赋值或传递的时候,行为上有所不同,==字典类型Dictionary或数组类型Array在赋值给变量或常量的时候,只要有做修改,就会进行值拷贝==,并且不会作用到原来变量上
var dict1 = ["a": 1, "b": 2]
var dict2 = dict1
print(dict1 == dict2) // true
dict2["a"] = 3 // 修改dict2
print(dict1 == dict2) // false
var arr1 = ["a", "b"]
var arr2 = arr1
print(arr1 == arr2) // true
arr1[0] = "c" // 修改arr1
// arr1.append("c")
print(arr1 == arr2) // false
五、可选类型(可空类型)
swift加入了可空类型让我们使用数据的时候更为安全,我们需要在可空的地方使用可选类型声明该变量可为空,==不能给非可选类型设值nil值==,在使用的时候可以明确的知道对象是否可能为nil,有点像ObjC的对象,对象可以为nil,也可以不为nil,而swift得可选类型范围更广可以作用于任何类型(基础类型,类,结构体,枚举)
1. 声明
// 1. 声明可选类型,在类型后面加上?
var obj1: NSObject?
obj1 = NSObject()
obj1 = nil
// 2. 不能给一个可选类型赋nil,下面会报错,
var obj = NSObject()
obj = nil
// 3. 如果声明可选变量时没有赋值,则默认为nil
var i: Int?
// 4. 一个函数返回一个可选类型
func getdog() -> String? {
return "ErHa"
}
// 5. 不能把可选类型赋值给非可选类型,下面会报错
let cat: String = dog
2. 强制解析
可选类型不能直接使用,需要通过取值操作符!取得变量的值,才能使用,如果变量有值,则返回该值,如果变量为空,则会运行时错误
var b: Int?
var a: Int
a = 12
b = 13
let c = a + b! // 先对b取值,再运算
var b: Bool? = nil
if b! { // b为空,编译不报错,运行时报错
print("true")
} else {
print("false")
}
3. 可选绑定
使用可选绑定可以判断一个可选类型是否有值,如果有值,则绑定到变量上,如果没有值,返回false,使用if-let组合实现
var i: Int? = nil
if let number = i {
print("\(number)")
} else {
print("nil")
}
可选绑定还支持绑定条件
var i: Int? = nil
if let number = i where i > 10 {
print("i不为空且大于10 \(number)")
} else {
print("nil")
}
4. 隐式解析
声明类型的时候可以使用隐式解析,即在使用可选变量的时候自动取值,不需要调用!操作符,
// 一个函数返回一个可选类型
func getdog() -> String? {
return "wangcai"
}
//假定我们通过getdog方法返回的值一定不为空
var dog: String? = getdog()
let cat: String = dog! // 使用前需要通过!强制取值
使用dog的时候都需要取值我们觉得太麻烦了,可以声明成隐式可选类型,使用的时候自动取值
var dog: String! = getdog() // 实际上dog还是可选类型,只是使用的时候回自动取值
let cat: String = dog // 在使用dog的时候会自动进行取值,不需要取值操作符
5. 可选类型自判断链接
在使用可选类型之前,需要进行判断其是否有值,才能使用,通过!操作符取值后使用(保证有值的情况下),或通过if-let可选绑定的方式,swift提供了一种类似C#语言的语法糖可以让代码更为简洁,可以自动判断值,如果有值,则操作,无值则不操作,并返回nil,在使用前加上?
class Person {
var favDog: Dog?
}
class Dog {
var name: String?
}
var p = Person()
var d = Dog()
// p.favDog = d
p.favDog?.name = "tobi" // 如果p.favDog为空,不设置name
if let name = p.favDog?.name {
// p.favDog不为空且p.favDog.name不为空
} else {
// p.favDog为空或p.favDog.name为空
}
自判断链接还支持多连接如
let identifier = john.residence?.address?.buildingIdentifier
6. 可选关联运算符
可选关联运算符可对可选类型进行拆包,如果可选类型对象为nil,返回第二个操作数,第二个操作数类型必须和第一个操作数同类型(可选或不可选)
let defaultColorName = "red"
var userDefinedColorName: String? // defaults to nil
var colorNameToUse = userDefinedColorName ?? defaultColorName
defaultColorName和userDefinedColorName必须是同类型(String或String?)
如果userDefinedColorName不为空,返回其值,如果userDefinedColorName为空,返回defaultColorName
返回值colorNameToUse的类型同??的第二个操作数的类型,为String
六、运算符
swift运算符在原有的基础上做了一些改进,还添加了一下更高级的用法,还有新的运算符
=运算符不返回值
符合运算符+=, -=等不返回值
比较运算符可以用于元组的比较(逐个比较,如果遇到不等的元素,则返回,默认最多只能比较7个元素的元组,超过则需要自定义)
(1, "zebra") < (2, "apple") // true,因为 1 小于 2
字符串String,字符Character支持+运算符
浮点数支持%求余运算
++/--运算在swift3被抛弃,用+=/-=代替
支持溢出运算符(&+, &-, &*),可以在溢出时进行(高位)截断
支持位运算符(>>, <<)
支持三目运算符(a ? b : c)
支持逻辑运算符(&&, ||, !)
恒等于/不恒等于
===:这两个操作符用于引用类型,用于判断两个对象是否指向同一地址
!==:与===相反,表示两个变量/常量指向的的地址不同
==:表示两个对象逻辑相等,可以通过重载运算符实现相等的逻辑,两个值相等的对象可以是不同地址的对象
!=:与==相反,表示两个对象逻辑不等
- 区间运算符
可以使用a...b表示一个范围,
a...b: 从a到b并包含a和b
a..<b: 包含a不包含b
范围运算符也可以作用于字符串
let az = "a"..."z" // 返回的是CloseInteval或HalfOpenInterval
az.contains("e") // True
- 空合运算符??
//可选类型取值,如果不为空则返回该值,如果为空则去第二个操作数
let result = a ?? b
七.控制流
swift使用三种语句控制流程:for-in、for、switch-case、while和repeat-while,且判断条件的括号可以省略
guard-else
翻译为保镖模式,在执行操作前,进行检查,如果不符合,则拦截,使用方式与if有些类似,如果与let结合使用,可以对可选类型解包
guard let i = i where i > 0 else {
// 在这里拦截,处理不符合条件的情况
return
}
// 符合条件的处理,这个时候已经对i进行了拆包,i是非可选类型,可以直接使用
switch-case
itch语句支持更多数据类型(String,Int, Float, 元组, 枚举),理论上switch支持任意类型的对象(需要实现~=方法或Equatable协议,详情参见这里)
se可以带多个值,用逗号隔开
se可以支持区间(a...b),支持元组,区间可以嵌套在元组内使用
se多条语句不需要用大括号包起来
se语句不需要break,除了空语句,如果需要执行下面的case,可以使用fallthrough
case不能命中所有的情况,必须要default,如Int,String类型,否则编译会失败
用fallthrough关键字声明接着执行下一条case语句,注意,如果case语句有赋值语句(let),则fallthrough无效
带标签的语句
如果有多层嵌套的情况下,有时候我们需要在某处直接退出多层循环,在objc下并没有比较好的方式实现,需要添加退出标识,然后一层一层退出,而在swift可以很方便的退出多层循环,首先需要使用标签标识不通的循环体,形式如下
labelName : while condition { statements }
看下面例子
outerLoop1 : for i in 1...10 {
outerLoop2 : for j in 1...10 {
outerLoop3 : for k in 1...10 {
if j > 5 {
// 1. 跳出一层循环(默认)继续outerLoop2的循环
break
// 2. 跳出两层循环,继续outerLoop1的循环
// break outerLoop2
// 3. 跳出三层循环,退出整个循环,继续后面的语句
// break outerLoop1
}
}
}
}
八、函数
1. 基本形式
//有返回值
func 函数名(参数名1:参数类型1, 参数名2:参数类型2) -> 返回值类型 {
// 函数体
}
//多个返回值(元组)
func getPoint() -> (x: Int, y: Int) {
return (1, 3)
}
var p = getPoint()
p.x
//无参数无返回值
func sayHello() {
// 函数体
}
//egg
func add(a: Int, b: Int) -> Int {
return a + b
}
// 调用
add(12, b: 232)
2. 可变参数
可变参数只能作为最后一个参数,一个方法最多只有一个可变参数,通过在变量类型名后面加入(...)的方式来定义可变参数。
func sum(numbers: Int...) -> Int {
var sum = 0
for number in numbers {
sum += number
}
return sum
}
3. 外部参数名
默认情况下,如果不指定外部参数名,swift编译器会自动为函数参数声明与内部参数名同名的外部参数名(格式为:外部参数名 内部参数名: 类型名)
//默认情况下,外部参数名与内部参数名一样
func add(first a: Int, second b: Int) -> Int {
return a + b
}
// 调用
add(first: 10, second: 20)
如果函数在第一个参数定义外部参数名,必须显示指定,当然我们还可以通过下划线_让函数忽略参数名
func add(a: Int, _ b: Int) -> Int {
return a + b
}
add(1, 2)
4. 函数默认值
函数还支持声明默认值,(格式为:外部参数名 内部参数名: 类型名 = 默认值)
func log(msg: String, isDebug: Bool = true) {
if isDebug {
print(msg)
}
}
log("fail")
log("success", isDebug: false)
5. 闭包
闭包作为变量
闭包作为函数参数
闭包作为函数返回值
闭包函数声明
func add(a: Int, b: Int) -> Int {
return a + b
}
//函数作为变量,函数hello赋给somefunc,并调用
let somefunc: (Int, Int) -> Int = add
somefunc(10, 20) // 30
//函数作为参数
func logAdd(a:Int, b:Int, function: (Int, Int) -> Int) {
// 函数内容
print("begin")
function(a, b)
print("end")
}
logAdd(12, b: 23, function: add)
//函数作为返回值(包装一个函数,在执行前后输出信息),函数作为参数又作为返回值
func addWrapper(addFunc: (Int, Int) -> Int) -> ((Int, Int) -> Int) {
// 函数内容
func wrapper(a: Int, b: Int) -> Int {
print("begin")
let res = addFunc(a, b)
print("end")
return res
}
return wrapper
}
var newAdd = addWrapper(add)
newAdd(12, 32)
//闭包函数声明形式
{ (parameters) -> returnType in
statements // 可以有多行
}
闭包函数
//定义一个函数变量
var addfunc: (Int, Int) -> Int
//闭包的写法
// 1. 完整写法
addfunc = {(a: Int, b: Int) -> (Int) in
//var c = a + 1 //函数体可以有多条语句,如果在同一行,需要用分号隔开,函数体不需要大括号
return a + b
}
// 2. 前面的addfunc变量可以推断出后面函数的参数类型和返回值类型,故可以省略
addfunc = {(a, b) in return a + b}
// 3. 参数列表括号可以省去,函数只有一条语句时,return可以省略
addfunc = {a, b in a + b}
// 4. 参数和in可以省去,通过$和索引取得参数
addfunc = {$0 + $1}
// 操作符需要的参数与函数参数一致,可以省去参数,并使用括号括起来,作为参数时,可不用括号
addfunc = (+)
6. Trailing(尾行)闭包
如果函数作为另一个函数的参数,并且是最后一个参数时,可以通过Trainling闭包来增强函数的可读性
func someFunctionThatTakesAClosure(a: Int, closure: () -> ()) {
// 函数体部分
}
// 1. 一般形式
someFunctionThatTakesAClosure(10, closure: {
// 闭包主体部分
})
// 2. Trainling闭包的方式
someFunctionThatTakesAClosure(10) {
// 闭包主体部分
}
// 3. 如果没有其他参数时,可以省略括号
someFunctionThatTakesAClosure {
// 闭包主体部分
}
7. Escaping(逃逸)闭包
如果一个闭包/函数作为参数传给另外一个函数,但这个闭包在传入函数返回之后才会执行,就称该闭包在函数中"逃逸",需要在函数参数添加@escaping声明,来声明该闭包/函数允许从函数中"逃逸",如下
var completionHandlers: [() -> Void] = []
// 传入的闭包/函数并没有在函数内执行,需要在函数类型钱添加@escaping声明
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
九、枚举
swift的枚举比C语言的枚举更为强大,支持更多特性,swift的枚举更像类和结构体,支持类和结构体的一些特性,与ObjC不同,如果不声明枚举的值,编译器不会给枚举设置默认值
1. 声明和使用
// 1. 定义枚举
enum CompassPoint {
case North
case South
case East
case West
}
// 2. 可以把枚举值定义在一行,用逗号隔开
enum CompassPoint2 {
case North, South, East, West
}
// 3. 像对象一样使用枚举,代码结构更为清晰,枚举更为简短
let direction = CompassPoint.East
// 4. 如果编译器可以识别出枚举的类型,可以省略枚举名
let direction2: CompassPoint
direction2 = .East
// 5. 如果编译器能确定case命中所有的情况,可以不需要default
switch direction {
case .East:
print("east")
case .West:
print("west")
case .South:
print("south")
case .North:
print("north")
//所有值都被枚举,则不需要default
}
2. 嵌套枚举
swift的枚举定义支持嵌套,在使用的时候一层一层引用
enum Character {
enum Weapon {
case Bow
case Sword
case Lance
case Dagger
}
enum Helmet {
case Wooden
case Iron
case Diamond
}
case Thief
case Warrior
case Knight
}
let character = Character.Thief
let weapon = Character.Weapon.Bow
let helmet = Character.Helmet.Iron
3. 递归枚举
枚举的关联值的类型可以设为枚举自身,这样的枚举称为递归枚举
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
带递归类型的枚举需要在case前面添加关键字声明indirect,也可以在enum前面加上声明,表示所有的成员是可以递归的
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
其实感觉这种嵌套多层的用法可读性并不是特别好,而且在取值的时候还需要递归,通常来说,嵌套一层就够了
4. 原始值
与C语言一样,可以为每个枚举指定值,并且可以支持更多类型(Int, Float, Character, String)
// 定义枚举,并初始化原始值
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
// 2. 通过两个属性获得原始值
var ch = ASCIIControlCharacter.Tab
ch.hashValue // 获取是否有原始值
ch.rawValue // 获得原始值
// 3. 通过原始值构造枚举,如果不存在,则返回nil
var tab = ASCIIControlCharacter.init(rawValue: "\t")
// 4. 如果是原始值是整形值,后面的值默认自增1,如果不指定,则默认为空,而不是从0开始
enum Planet: Int {
case Mercury = 1, Venus // Venus = 2
case Neptune // Neptune = 3
}
// 5. 如果没有指定枚举原始值的类型,则默认为空,而不是整型
enum CompassPoint {
case North
case South
case East
case West
}
//swift 不会为North, South, East, West设置为0,1,2,3,并且CompassPoint没有原始值(rawValue)
// 6. 有原始值的枚举可以通过原始值构造(构造器返回可选类型)
let lineFeed = ASCIIControlCharacter(rawValue: "\n")
5. 关联值
swift的枚举可以给不同的枚举值绑定关联值,如下
enum Barcode {
case UPCA(Int, Int, Int) //条形码,关联一个元组
case QRCode(String) //二维码,关联一个字符串
}
var productBarcode = Barcode.UPCA(8, 85909_51226, 3)
// var productBarcode = .QRCode("http://www.baidu.com")
switch productBarcode {
case .UPCA(let a, let b, let c): //在枚举的时候可以取得关联值
print("barcode: \(a)\(b)\(c)")
case let .QRCode(value):
print("qrcode: \(value)")
}