该系列主要是记录Swift中与OC差异较大,较容易忘记的知识点。
该篇主要是关于基础数据层面的知识点(比如数组,字典,闭包,字符串等)。
下篇主要记录关于类方面的(初始化,属性,协议等)。
1. 元祖
元组(tuples)把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。
// http404Error 的类型是 (Int, String),值是 (404, "Not Found")”
let http404Error = (404, "Not Found")
// 输出(通过下标)
print("The status code is \(http404Error.0)")
print("The status message is \(http404Error.1)")
// 也可以通过赋值输出对应值,可以把要忽略的部分用下划线_
let (statusCode, statusMessage) = http404Error
作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 (Int, String) 元组来描述是否获取成功。
元组在临时组织值的时候很有用,但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用,请使用类或者结构体而不是元组。
2. 可选类型
import UIKit
// String? 为可选类型,可以为nil,可以有值
// 如果单单是String,则myString不是可选类型,不能为nil
// 自动被设置为 nil
var myString:String?
myString = "Hello, Swift!"
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int”
//“因为该构造器可能会失败,所以它返回一个可选类型(optional)Int,而不是一个 Int。”
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// 如果是String!为可选类型,输出结果会自动解析 → 隐式解析可选类型
// 可以把隐式解析可选类型当做一个可以自动解析的可选类型,其他应用和?的可选类型一致
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感叹号来获取值
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要感叹号”
// 可选类型的输出有两种,一种是判断是否为nil,一种是可选绑定(判断赋值)
// ① 判断是否为nil
if myString != nil {
// 加上!为强制解析,如果前面已经是String!,就不需要加上!
// 在可选类型情况下,没有解析会输出 Optional("Hello, Swift!")
print(myString!)
}else{
print("myString 值为 nil")
}
// ② 可选绑定
if let yourString = myString {
print("你的字符串值为 - \(yourString)")
}else{
print("你的字符串没有值")
}
Swift 的 nil 和 Objective-C 中的 nil 并不一样。在 Objective-C 中,nil 是一个指向不存在对象的指针。在 Swift 中,nil 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 nil,不只是对象类型。
3.错误处理和断言
相对于可选中运用值的存在与缺失来表达函数的成功与失败,错误处理可以推断失败的原因,并传播至程序的其他部分。
一个函数可以通过在声明中添加throws关键词来抛出错误消息。当你的函数能抛出错误消息时, 你应该在表达式中前置try关键词。
func canThrowAnError() throws {
// 这个函数有可能抛出错误
}
do {
// 可能会抛出错误的函数
try canThrowAnError()
// 没有错误消息抛出,继续执行
do someThing()
} catch {
// 有一个错误消息抛出
}
使用断言进行调试
let age = -3
// 因为 age < 0,所以断言会触发(后面的提示可以省略)
assert(age >= 0, "A person's age cannot be less than zero")
4.控制流( 循环类型,条件语句)
Swift在3.0废弃了for循环,目前有for-in,while循环,repeat..while循环。在循环中常用到的区间运算符有:
①闭区间运算符:1...5 (区间值为 1, 2, 3, 4 和 5)
②半开区间运算符:1..< 5( 区间值为 1, 2, 3, 和 4)
另外,说到运算符,还有一个特别的:空合运算符
空合运算符(a ?? b)将对可选类型 a 进行空判断,如果 a 包含一个值就进行解封,否则就返回一个默认值 b。相当于:
a != nil ? a! : b
以下是循环的例子:
// for-in循环
for index in 1...5 {
print("\(index) 乘于 5 为:\(index * 5)")
}
// while循环
var index = 10
while index < 20
{
print( "index 的值为 \(index)")
index = index + 1
}
// repeat..while循环(类似do..while)
var index = 15
repeat{
print( "index 的值为 \(index)")
index = index + 1
}while index < 20
if语句和OC的差别不多,这边我就带过。Swift 的switch语句比 C 语言中更加强大。在 C 语言中,如果某个 case 不小心漏写了break,这个 case 就会贯穿至下一个 case,Swift 无需写break,所以不会发生这种贯穿的情况。case 还可以匹配很多不同的模式,包括:间隔匹配(条件是区间),元组(条件是元祖),复合匹配(多个条件一起)和转换到特定类型(这个就?)。switch语句的 case 中匹配的值可以绑定成临时常量或变量,在case体内使用,也可以用where来描述更复杂的匹配条件。
其中,有个比较特别的控制转移语句用于switch语句中:fallthrough(可以实现贯穿)。
5. 字符串的常见操作
拼接和判断相等相较于OC都方便很多,还可以获取里面的字符,操作像数组一样方便。Swift 的String类型是值类型。 如果您创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。
// 使用字符串字面量创建空字符串
var stringA = ""
// 实例化 String 类来创建空字符串
var stringB = String()
// 判断是否为空
if stringA.isEmpty {
print( "stringA 是空的" )
}
// 字符串的拼接
stringA = stringB + "ABC"
// 字符串的长度(也可以通过characters遍历所有的字符)
print( "长度为\(stringA.characters.count)" )
// 字符串的比价
if stringA == stringB {
print( "\(stringA) 与 \(stringB) 是相等的" )
}
// 字符串的修改
// 每一个String值都有一个关联的索引(index)类型(不过不是之前用的那些整数来表示),String.Index,它对应着字符串中的每一个Character的位置。
// 所以可以用来增删改查,这边只举了查的例子
“let greeting = "Guten Tag!"
greeting[greeting.startIndex]// G
greeting[greeting.index(before: greeting.endIndex)]// !
greeting[greeting.index(after: greeting.startIndex)]// u
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index]// a
6. 集合类型
数组的常见操作
和OC的区别就是更加灵活,尤其是添加元素和修改元素。
// 创建数组
// 创建了一个类型为 Int ,数量为 3,初始值为 0 的空数组
// 也可以这样创建:var someInts = [Int]()
// 创建有值的情况:var someInts:[Int] = [10, 20, 30]
var someInts = [Int](repeating: 0, count: 3)
// 添加元素
someInts.append(20)
someInts.append(30)
someInts += [40]
// 获取元素
var someVar = someInts[0]
// 修改元素
someInts[2] = 50
// 遍历元素,也可以直接使用for-in
for (index, item) in someInts.enumerated() {
print("在 index = \(index) 位置上的值为 \(item)")
}
// 合并数组
var intsB = someInts + someInts
// 判断数组是否为空
var intsC = [Int]()
let isEmpty:Bool = intsC.isEmpty
字典的常见操作
Swift 字典的key没有类型限制可以是整型或字符串,但必须是唯一的。注:count和isEmpty的判断也是同数组。
// 创建字典
// 创建空的字典:var someDict = [Int: String]()
var someDict:[Int:String] = [1:"One", 2:"Two", 3:"Three"]
// 输出字典(也是通过key),这边要注意的是someDict[2]是可选类型
print( "key = 2 的值为 \(someDict[2])" )
print( "key = 3 的值为 \(someDict[3])" )
// 修改字典
// 或者:someDict[1] = "One 新的值"
var oldVal = someDict.updateValue("One 新的值", forKey: 1)
// 移除key-value对
// 或者直接置为nil:someDict[2] = nil
var removedValue = someDict.removeValue(forKey: 2)
// 遍历字典
for (key, value) in someDict {
print("字典 key \(key) - 字典 value \(value)")
}
for (key, value) in someDict.enumerated() {
print("字典 key \(key) - 字典 (key, value) 对 \(value)")
}
// 字典keys和values转成数组
let dictKeys = [Int](someDict.keys)
let dictValues = [String](someDict.values)
7. 函数的格式和使用
Swift函数的格式比较不一样,不过也简单。相较于OC,参数和返回值的变化更多,意味着能做的更多。另外,函数还可以做到像其他类型一样赋值使用。
// func 函数名(形参) -> 返回值类型
// 也可以不带参数,没有返回值
func runoob(name: String, site: String) -> String {
return name + site
}
print(runoob(name: "菜鸟教程:", site: "www.runoob.com"))
print(runoob(name: "Google:", site: "www.google.com"))
① 函数返回值的不同
函数返回值类型不仅可以是字符串,整型,浮点型等,也可以是元祖。元组与数组类似,不同的是,元组中的元素可以是任意类型,使用的是圆括号。你可以用元组(tuple)类型让多个值作为一个复合值从函数中返回。
func minMax(array: [Int]) -> (min: Int, max: Int) {
var currentMin = array[0]
var currentMax = array[0]
for value in array[1.. currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("最小值为 \(bounds.min) ,最大值为 \(bounds.max)")
② 函数参数的不同
函数参数都有一个外部参数名和一个局部参数名。
// number是局部参数,只能在函数体内使用
func sample(number: Int) {
println(number)
}
sample(number: 1)
//外部参数名在局部参数前,用于在函数调用时传递给函数的参数
func power(firstArg a: Int, secondArg b: Int) -> Int {
var res = a
for _ in 1..
可变参数可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数,其数量是不确定的。可变参数通过在变量类型名后面加入(...)的方式来定义。
一般默认在函数中定义的参数都是常量参数,也就是这个参数你只可以查询使用,不能改变它的值。如果想要声明一个变量参数,可以在参数定义前加 inout 关键字,这样就可以改变这个参数的值了。一般默认的参数传递都是传值调用的,而不是传引用。所以传入的参数在函数内改变,并不影响原来的那个参数。传入的只是这个参数的副本。当传入的参数作为输入输出参数时,需要在参数名前加 & 符,表示这个值可以被函数修改。
// N表示任意类型
func vari(members: N...){
for i in members {
print(i)
}
}
vari(members: 4,3,5)
// inout表示该值可以被改变
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
var x = 1
var y = 5
swapTwoInts(&x, &y)
print("x 现在的值 \(x), y 现在的值 \(y)")
③ 函数类型的应用
在 Swift 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将适当的函数赋值给它,也可以作为参数类型和返回类型。函数还可以嵌套使用,函数内定义一个新的函数,外部的函数可以调用函数内定义的函数。
func sum(a: Int, b: Int) -> Int {
return a + b
}
// 现在addition变成了sum函数
var addition: (Int, Int) -> Int = sum
print("输出结果: \(addition(40, 89))")
// 函数类型也可以作为参数类型
func another(addition: (Int, Int) -> Int, a: Int, b: Int) {
print("输出结果: \(addition(a, b))")
}
another(addition: sum, a: 10, b: 20)
// 函数还可以嵌套使用(类似于返回值)
// 函数内定义一个新的函数,外部的函数可以调用函数内定义的函数。
func calcDecrement(forDecrement total: Int) -> () -> Int {
var overallDecrement = 0
func decrementer() -> Int {
overallDecrement -= total
return overallDecrement
}
return decrementer
}
let decrem = calcDecrement(forDecrement: 30)
print(decrem())
6. 闭包的使用
闭包(Closures)是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。Swift 中的闭包与 C 和 Objective-C 中的代码(blocks)以及其他一些编程语言中的 匿名函数比较相似。全局函数和嵌套函数其实就是特殊的闭包。
//闭包表达式如下(当成func来记)
/*
{(参数) -> 返回值类型 in
return 返回值
}
*/
let divide = {(val1: Int, val2: Int) -> Int in
return val1 / val2
}
let result = divide(200, 20)
print (result)
闭包格式上还有很多可以精简的地方(比如在作为数组排序条件时):
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backward(_ str1: String, _ str2: String) -> Bool {
return str1 > str2
}
// sorted函数的参数是一个闭包,下面传了一个方法名,由此说明:嵌套函数是一个有名字但并可以捕获其封闭函数域内值的闭包
var reversedNames = names.sorted(by: backward)
// 普通闭包格式:(参数: 参数类型,...) -> 返回值类型 in ...
reversedNames = names.sorted(by: {(_ str1: String, _ str2: String) -> Bool in return str1 > str2})
// 根据Swift的类型推断,参数类型及参数括号可以去掉,返回值类型可以去掉
reversedNames = names.sorted(by: {str1, str2 in return str1 > str2})
// 单行表达式:可以去掉return
reversedNames = names.sorted(by: {str1, str2 in str1 > str2})
// 使用参数名缩写:参数和in也可以去掉
reversedNames = names.sorted(by: {$0 > $1})
// 使用运算符:因为Swift中为字符串重载了大于号小于号
reversedNames = names.sorted(by: >)
另外,我们常听到尾随闭包和逃逸闭包。
尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
// 该类函数在调用时有两种写法,如下
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure({
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
// 原本是应该上面那样写,尾随闭包只是把闭包拿出来,放在后面
// 如果函数只需要闭包表达式一个参数,甚至可以把()省略掉。
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
逃逸闭包:当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行(比如等下载完成在完成回调,也就是我们常看到的CompleteHandle,函数已经return,CompleteHandle再回调。反之函数还没return,闭包就执行,这就不是逃逸闭包),我们称该闭包从函数中逃逸。逃逸闭包多用来做函数回调。
// 逃逸闭包(使用@escaping,不加默认为非逃逸)
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
//下载完成再回调completionHandler
}
// 非逃逸闭包
func someFunctionWithNoneEscapingClosure(closure: () -> Void) {
// 直接就去调用,起到的是调用,而不是回调。
closure()
}
闭包可以在其定义的上下文中捕获常量或变量(跟OC的Block一样)。
Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。另外,闭包是引用类型,像OC的对象一样,指向同一个对象。
// 闭包作为嵌套函数(就是函数里面有函数(闭包))
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
// incrementor是一个闭包
// 它会截获值runningTotal和amount
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement: 10)
// 返回的值为10
print(incrementByTen())
// 返回的值为20
print(incrementByTen())
// 返回的值为30
print(incrementByTen())
// 闭包是引用类型,意味着alsoIncrementByTen也是指向上面那个闭包
// 所以runningTotal还是截获的值所加上来的结果
let alsoIncrementByTen = incrementByTen
// 返回的值为40
print(alsoIncrementByTen())
8. 枚举的使用
枚举简单的说也是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合。
Swift 的枚举类似于 Objective C 和 C 的结构,但是功能多了很多,Swift中的枚举是一等类型, 它可以像类和结构体一样增加属性和方法。
- 它声明在类中,可以通过实例化类来访问它的值。
- 枚举也可以定义构造函数(initializers)来提供一个初始成员值;可以在原始的实现基础上扩展它们的功能。
- 可以遵守协议(protocols)来提供标准的功能。
Swift 的枚举的枚举需要理解三个概念:成员值,相关值,原始值。
成员值
和 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在例子中,Sunday,Monday,……和Saturday不会隐式地赋值为0,1,……和6。相反,这些枚举成员本身就有完备的值,这些值是已经明确定义好的DaysofaWeek类型,这些就是成员值。
// 定义枚举(也可以case Sunday,Monday...)
// 枚举中定义的值(如 Sunday)是这个枚举的成员值(或成员)
enum DaysofaWeek {
case Sunday
case Monday
case TUESDAY
case WEDNESDAY
case THURSDAY
case FRIDAY
case Saturday
}
var weekDay = DaysofaWeek.THURSDAY
weekDay = .THURSDAY//如果推断出类型,就可以直接用.
switch weekDay
{
case .Sunday:
print("星期天")
case .Monday:
print("星期一")
case .TUESDAY:
print("星期二")
case .WEDNESDAY:
print("星期三")
case .THURSDAY:
print("星期四")
case .FRIDAY:
print("星期五")
case .Saturday:
print("星期六")
}
原始值
原始值就跟OC的差不多,不过Swift的原始值可以是字符串,字符,或者任何整型值或浮点型值。
每个原始值在它的枚举声明中必须是唯一的。在原始值为整数的枚举时,不需要显式的为每一个成员赋值,Swift会自动为你赋值。例如,当使用整数作为原始值时,隐式赋值的值依次递增1。如果第一个值没有被赋初值,将会被自动置为0。
enum Month: Int {
case January = 1, February, March, April, May, June, July, August, September, October, November, December
}
// 通过rawValue获取值
let yearMonth = Month.May.rawValue
// 通过rawValue创建一个枚举值
let some = Month(rawValue: 3)
相关值(关联值)
相关值可以看做是原始值的进化,它可以存不同数据类型,并且当你在创建一个基于枚举成员的新常量或变量时才会被设置,所以它的值可以是不同的。因此可以用来判断多种不同类型的数据。
enum Student{
case Name(String)
case Mark(Int,Int,Int)
}
var studDetails = Student.Name("Runoob")
var studMarks = Student.Mark(98,97,95)
switch studMarks {
case .Name(let studName):
print("学生的名字是: \(studName)。")
case .Mark(let Mark1, let Mark2, let Mark3):
print("学生的成绩是: \(Mark1),\(Mark2),\(Mark3)。")
}