语言篇swift
原学习网站:
https://www.jianshu.com/p/69e257a29587
https://www.runoob.com/swift/swift-tutorial.html
与其他语言大体相同的部分不再赘述,这里只提出几点swift特性
语言基础
每接触一个新语言没有什么比看见"Hello World!"更让人开心的了
print("Hello World!")
变量、常量、可选类型
对于常量和变量的命名,区分大小写,Swift几乎可以使用任意的Unicode字符,中文、希腊字母甚至Emoji字符都可以做变量名,当然,空格、数学符号、箭头、特殊用途或无效的Unicode字符除外,如果命名中有数字,数字不可放在首位。
类型别名
typealias newname = type
变量
var x, y, z: Double;
var greeting:String = "Hello"
print(greeting)
greeting = "Nice to meet you!"
print(greeting)
greeting = "Goodbye"
print(greeting)
也可以直接
var greeting = "Hello"
print(greeting)
编译器会根据你赋给变量的值进行类型推断,建议省略类型标记尽可能使用类型推断来让代码更简短并获得更好的可读性。
常量
关键字let
可选类型
Swift 的可选(Optional)类型,用于处理值缺失的情况,表示"那儿有一个值,并且它等于 x "或者"那儿没有值"。
Swfit语言定义后缀?作为命名类型Optional的简写
即在类型标记的后面加一个问号,就可以将变量声明为可选类型,注意要确保用括号给 ? 操作符一个合适的范围。例如,声明可选整数数组,应该写成 (Int[])?,
默认值为nil
以下两种声明是相等的:
var optionalInteger: Int?
var optionalInteger: Optional<Int>
强制解析
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。这个感叹号表示"我知道这个可选有值,请使用它。"这被称为可选值的强制解析(forced unwrapping)。
var str: String?
print(str)
str = "Hello, world"
print(str?.uppercaseString)
print(str!.uppercaseString)
运行结果:
字符串
字符串插值
var a = 123
var b = 321
print("\(a) + \(b) = \(a+b)")
遍历字符串中的字符
Swift 3 中的 String 需要通过 characters 去调用的属性方法,在 Swift 4 中可以通过 String 对象本身直接调用
以Swift 4 为例:
for ch in "Runoob" {
print(ch)
}
字符串连接字符
append() 方法来实现字符串连接字符:
var varA:String = "Hello "
let varB:Character = "G"
varA.append( varB )
print("varC = \(varA)")
关于分号
Swift对此做了一个折衷:
如果一行只书写一条语句则可以不使用分号来表示语句的结束;
如果一行有多于一条语句,则语句间需要用分号隔开
数值
Int 和 Double 能满足绝大多数要求,这里只提swift的一些特性
在Swift中,可以在书写数值型的字面量时使用下划线来增强数值的可读性
var million = 1_234_567
变量赋值时允许使用运算符计算出数值,在Swift中,所有的运算符本质都是函数(Tips:按住Command再点击Swift中的类型可以查看头文件中对类型、函数、协议等的定义),需要参与运算的各个部分类型保持一致,如下所示:
var radius = 4
let PI = 3.1416
var area = Double(radius) * Double(radius) * PI
布尔型
swift中有布尔型:true和false,有一点要注意的是,在Swift中,循环和分支结构中的条件只能使用布尔类型的值
元组
元组是由多个值组成的单一类型,与类和结构体不同,你不需要定义元组类型就能使用它。
元组只适用于快速的构造简单的复合数据类型,更多的时候我们需要的还是类和结构体
var address = (201306, "SHOU")
var fruit: (Int, String) = (5, "Apple")
var drink = (price: 5, name: "Coca Cole")
print(address.0); print(address.1)
let (zipcode, school) = address
print(zipcode); print(scholl)
print(drink.name); print(drink.price)
基本流程
Tips:
1)Swift省略了对设置条件的圆括号的使用且条件只能是布尔型
2)switch-case-default每个case后面不需要写break,只有一个case或default会执行,default必须写
3)如果希望执行完一个case后继续执行后续的case,可以在case之后写上fallthrough
4)每个case可以写多个常量或范围与switch中的表达式进行等值匹配
容器
数组
下标从0开始
创建数组
我们可以使用构造语法来创建一个由特定数据类型构成的空数组:
var names: [String] = []
var lookup: [String: Int] = [:]
以下是创建一个初始化大小数组的语法:
var someArray = [SomeType](repeating: InitialValue, count: NumbeOfElements)
实例
var a = [Int](repeating: 0, count: 3) //类型为 Int ,数量为 3,初始值为 0 的空数组:
var b:[Int] = [10, 20, 30] //含3个数
访问数组
var array = [12, 7, 38, 65]
print(array[2]) // 38
print(array) // [12, 7, 38, 65]
修改数组
var array = [12, 7, 38, 65]
array.append(93) //append() 方法在末尾添加元素
print(array) // [12, 7, 38, 65, 93]
array.appendContentsOf(100...103)
print(array) // [12, 7, 38, 65, 93, 100, 101, 102, 103]
array += [49] //用 += 在末尾添加元素
print(array) // [12, 7, 38, 65, 93, 100,101,102,103,49]
array[0] = 9 //下标修改
print(array) // [9, 7, 38, 65, 93, 100,101,102,103,49]
array.removeAtIndex(0)
print(array) // [7, 38, 65 93, 100, 101, 102, 103]
遍历
for-in遍历
如果我们同时需要每个数据项的值和索引值,可以使用 String 的 enumerate() 方法来进行数组遍历
var someStrs = [String]()
someStrs.append("Apple")
someStrs.append("Amazon")
someStrs.append("Runoob")
someStrs += ["Google"]
for (index, item) in someStrs.enumerated() {
print("在 index = \(index) 位置上的值为 \(item)")
}
字典
Swift 字典用来存储无序的相同类型数据的集合,Swift 字典会强制检测元素的类型,如果类型不同则会报错。
Swift 字典每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。
https://www.runoob.com/swift/swift-dictionaries.html
函数
Swift 定义函数使用关键字 func
语法
func funcname(形参) -> returntype {
Statement1
Statement2
……
Statement N
return parameters
}
实例
unc runoob(name: String, site: String) -> String {
return name + site
}
print(runoob(name: "菜鸟教程:", site: "www.runoob.com"))
print(runoob(name: "Google:", site: "www.google.com"))
由于swift中元组的存在,可以用元组让多个值作为一个复合值从函数中返回。
如果不确定返回的元组一定不为nil,那么你可以返回一个可选的元组类型——在元右括号后放置一个问号来定义一个可选元组,例如(Int, Int)?或(String, Int, Bool)?但是请注意,可选元组类型如(Int, Int)?与元组包含可选类型如(Int?, Int?)是不同的.可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。
func minMax(array: [Int]) -> (min: Int, max: Int)? {
if array.isEmpty { return nil }
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
print("最小值为 \(bounds.min),最大值为 \(bounds.max)")
}
外部参数名
外部参数名
可以在局部参数名前指定外部参数名,中间以空格分隔,外部参数名用于在函数调用时传递给函数的参数。
如下你可以定义以下两个函数参数名并调用它:
func pow(firstArg a: Int, secondArg b: Int) -> Int {
var res = a
for _ in 1..<b {
res = res * a
}
print(res)
return res
}
pow(firstArg:5, secondArg:3)
可变参数
可变参数可以接受零个或多个值。函数调用时,可以用可变参数来指定函数参数,其数量是不确定的。
可变参数通过在变量类型名后面加入(...)的方式来定义。
func vari<N>(members: N...){
for i in members {
print(i)
}
}
vari(members: 4,3,5)
vari(members: 4.5, 3.1, 5.6)
vari(members: "Google", "Baidu", "Runoob")
常量,变量及 I/O 参数
一般默认在函数中定义的参数都是常量参数
如果想要声明一个变量参数,可以在参数定义前加 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)")
闭包
闭包(Closures)是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。
闭包一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)
Swift优化:
1)类型推断
2)从单行表达式闭包中隐式返回(也就是闭包体只有一行代码,可以省略return)
3)可以使用简化参数名,如$0, $1(从0开始,表示第i个参数...),即直接通过$0,$1,$2来顺序调用闭包的参数
4)提供了尾随闭包语法(Trailing closure syntax)
例如
var animals = ["fish", "cat", "panda", "dog"]
func compare(one: String, two: String) -> Bool {
return one < two;
}
animals.sortInPlace(compare)
print(animals)
闭包形式简化
var animals = ["fish", "cat", "panda", "dog"]
animals.sortInPlace({ (one: String, two: String) -> Bool in return one < two })
print(animals)
类型推断简化
var animals = ["fish", "cat", "panda", "dog"]
animals.sortInPlace({ (one, two) in one < two })
print(animals)
参数名简化
var animals = ["fish", "cat", "panda", "dog"]
animals.sortInPlace({ $0 < $1 })
print(animals)
由于我们只需要<,其运算符函数正好需要的函数类型相符合 -> 运算符函数简化
var animals = ["fish", "cat", "panda", "dog"]
animals.sortInPlace(<)
print(animals)
尾随闭包
尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
var animals = ["fish", "cat", "panda", "dog"]
animals.sortInPlace() { $0 < $1 }
print(animals)
捕获值
闭包可以在其定义的上下文中捕获常量或变量。
即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值,即延长其所在的代码块中的常量或变量的生命周期
这样可以免除了对全局变量的使用(因为全局变量总是让你的代码变得糟糕,因为你不知道这个变量什么时候会被哪段代码意外的修改),但是它通过延长局部变量生命周期的方式让你以可控制的方式使用这些值。
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement: 10)
// 返回的值为10
print(incrementByTen())
// 返回的值为20
print(incrementByTen())
// 返回的值为30
print(incrementByTen())
由于没有修改amount变量,incrementor实际上捕获并存储了该变量的一个副本,而该副本随着incrementor一同被存储。所以我们调用这个函数时会累加
闭包是引用类型
无论将函数/闭包赋值给一个常量还是变量,实际上都是将常量/变量的值设置为对应函数/闭包的引用。 上面的例子中,incrementByTen指向闭包的引用是一个常量,而并非闭包内容本身。
类和结构体
不同点
结构体和类有许多不同点,这里只提其最大的不同————结构体是值类型(value type)而类是引用类型(reference type)
class Cat {
var name = "Kitty"
}
struct Dog {
var name = "Wangcai"
}
var cat1 = Cat()
var dog1 = Dog()
var cat2 = cat1
var dog2 = dog1
cat2.name = "Mimi"
dog2.name = "Xiaoqiang"
print(cat1.name) // Mimi
print(dog1.name) // Wangcai
类的恒等运算符
因为类是引用类型,有可能有多个常量和变量在后台同时引用某一个类实例。
为了能够判定两个常量或者变量是否引用同一个类实例,Swift 内建了两个恒等运算符:
恒等运算符 :=== 如果两个常量或者变量引用同一个类实例则返回 true
不恒等运算符:!== 如果两个常量或者变量引用不同一个类实例则返回 true
类型转换
可以判断实例的类型/用于检测实例类型是否属于其父类或者子类的实例/检查一个类是否实现了某个协议
操作符
is 来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 true,否则返回 false。
向下转型用类型转换操作符(as? 或 as!)
当不确定向下转型可以成功时,用类型转换的条件形式(as?)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 nil
当确定向下转型一定会成功时,才使用强制形式(as!)。当向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。
注意:switch语句的case中使用强制形式的类型转换操作符(as, 而不是 as?)来检查和转换到一个明确的类型
同时,Swift为不确定类型提供了两种特殊类型别名:
AnyObject 可以代表任何class类型的实例
Any 可以表示任何类型,包括方法类型(function types)
class Subjects {
var physics: String
init(physics: String) {
self.physics = physics
}
}
class Chemistry: Subjects {
var equations: String
init(physics: String, equations: String) {
self.equations = equations
super.init(physics: physics)
}
}
class Maths: Subjects {
var formulae: String
init(physics: String, formulae: String) {
self.formulae = formulae
super.init(physics: physics)
}
}
// [AnyObject] 类型的数组
let saprint: [AnyObject] = [
Chemistry(physics: "固体物理", equations: "赫兹"),
Maths(physics: "流体动力学", formulae: "千兆赫"),
Chemistry(physics: "热物理学", equations: "分贝"),
Maths(physics: "天体物理学", formulae: "兆赫"),
Maths(physics: "微分方程", formulae: "余弦级数")]
let samplechem = Chemistry(physics: "固体物理", equations: "赫兹")
print("实例物理学是: \(samplechem.physics)")
print("实例方程式: \(samplechem.equations)")
let samplemaths = Maths(physics: "流体动力学", formulae: "千兆赫")
print("实例物理学是: \(samplemaths.physics)")
print("实例公式是: \(samplemaths.formulae)")
var chemCount = 0
var mathsCount = 0
for item in saprint {
// 类型转换的条件形式
if let show = item as? Chemistry {
print("化学主题是: '\(show.physics)', \(show.equations)")
// 强制形式
} else if let example = item as? Maths {
print("数学主题是: '\(example.physics)', \(example.formulae)")
}
}
var exampleany = [Any]()
exampleany.append(12)
exampleany.append(3.14159)
exampleany.append("Any 实例")
exampleany.append(Chemistry(physics: "固体物理", equations: "兆赫"))
for item2 in exampleany {
switch item2 {
case let someInt as Int:
print("整型值为 \(someInt)")
case let someDouble as Double where someDouble > 0:
print("Pi 值为 \(someDouble)")
case let someString as String:
print("\(someString)")
case let phy as Chemistry:
print("主题 '\(phy.physics)', \(phy.equations)")
default:
print("None")
}
}
构造函数
关键字init
https://www.runoob.com/swift/swift-initialization.html
析构函数
关键字deinit
Swift 会自动释放不再需要的实例以释放资源。
Swift 通过自动引用计数(ARC)处理实例的内存管理。
类的继承
https://www.runoob.com/swift/swift-inheritance.html
访问控制
元组的访问级别与元组中访问级别最低的类型一致
各种情况的访问控制详见:https://www.runoob.com/swift/swift-access-control.html
泛型
类同c++里的模板
例如
// 定义一个交换两个变量的函数
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
var numb1 = 100
var numb2 = 200
print("交换前数据: \(numb1) 和 \(numb2)")
swapTwoValues(&numb1, &numb2)
print("交换后数据: \(numb1) 和 \(numb2)")
var str1 = "A"
var str2 = "B"
print("交换前数据: \(str1) 和 \(str2)")
swapTwoValues(&str1, &str2)
print("交换后数据: \(str1) 和 \(str2)")
关联类
关联类
关键字:associatedtype
下面例子定义了一个 Container 协议,该协议定义了一个关联类型 ItemType。
Container 协议只指定了三个任何遵从 Container 协议的类型必须提供的功能。遵从协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。
// Container 协议
protocol Container {
associatedtype ItemType
// 添加一个新元素到容器里
mutating func append(_ item: ItemType)
// 获取容器中元素的数
var count: Int { get }
// 通过索引值类型为 Int 的下标检索到容器中的每一个元素
subscript(i: Int) -> ItemType { get }
}
// Stack 结构体遵从 Container 协议
struct Stack<Element>: Container {
// Stack<Element> 的原始实现部分
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 协议的实现部分
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
var tos = Stack<String>()
tos.push("google")
tos.push("runoob")
tos.push("taobao")
// 元素列表
print(tos.items)
// 元素个数
print( tos.count)
where
Where 语句可以用来定义参数的约束,即紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。
例如
泛型
// Container 协议
protocol Container {
associatedtype ItemType
// 添加一个新元素到容器里
mutating func append(_ item: ItemType)
// 获取容器中元素的数
var count: Int { get }
// 通过索引值类型为 Int 的下标检索到容器中的每一个元素
subscript(i: Int) -> ItemType { get }
}
// // 遵循Container协议的泛型TOS类型
struct Stack<Element>: Container {
// Stack<Element> 的原始实现部分
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 协议的实现部分
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
// 扩展,将 Array 当作 Container 来使用
extension Array: Container {}
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
// 检查两个容器含有相同数量的元素
if someContainer.count != anotherContainer.count {
return false
}
// 检查每一对元素是否相等
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// 所有元素都匹配,返回 true
return true
}
var tos = Stack<String>()
tos.push("google")
tos.push("runoob")
tos.push("taobao")
var aos = ["google", "runoob", "taobao"]
if allItemsMatch(tos, aos) {
print("匹配所有元素")
} else {
print("元素不匹配")
}
属性
属性可分为存储属性和计算属性
类型属性是通过类型本身来获取和设置,而不是通过实例
延迟存储属性
在属性声明前使用 lazy 来标示一个延迟存储属性。
必须将延迟存储属性声明成变量(使用var关键字),因为属性的值在实例构造完成之前可能无法得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。
延迟存储属性一般用于:延迟对象的创建/值依赖于其他未知类
例如
import Cocoa
class sample {
lazy var no = number() // `var` 关键字是必须的
}
class number {
var name = "Runoob Swift 教程"
}
var firstsample = sample()
print(firstsample.no.name)
计算属性
计算属性不直接存储值,而是提供一个 getter 来获取值,一个可选的 setter 来间接设置其他属性或变量的值
必须使用var关键字定义计算属性
class sample {
var no1 = 0.0, no2 = 0.0
var length = 300.0, breadth = 150.0
var middle: (Double, Double) {
get{
return (length / 2, breadth / 2)
}
set(axis){
no1 = axis.0 - (length / 2)
no2 = axis.1 - (breadth / 2)
}
}
}
var result = sample()
print(result.middle)
result.middle = (0.0, 10.0)
print(result.no1)
print(result.no2)
枚举
关键词enum
Swift 的枚举成员在被创建时不会被赋予一个默认的整型值,这些枚举成员本身就有完备的值,这些值是已经明确定义好的
enum DaysofaWeek {
case Sunday
case Monday
case TUESDAY
case WEDNESDAY
case THURSDAY
case FRIDAY
case Saturday
}
var weekDay = DaysofaWeek.THURSDAY
weekDay = .THURSDAY //一旦weekDay被声明为一个DaysofaWeek,你可以使用一个缩写语法(.)即省略枚举名
switch weekDay
{
case .Sunday:
print("星期天")
case .Monday:
print("星期一")
case .TUESDAY:
print("星期二")
case .WEDNESDAY:
print("星期三")
case .THURSDAY:
print("星期四")
case .FRIDAY:
print("星期五")
case .Saturday:
print("星期六")
}
相关值与原始值
相关值
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)。")
}
原始值
每个原始值在它的枚举声明中必须是唯一的。
在原始值为整数的枚举时,不需要显式的为每一个成员赋值,Swift会自动为你赋值。
enum Month: Int {
case January = 1, February, March, April, May, June, July, August, September, October, November, December
} //当使用整数作为原始值时,隐式赋值的值依次递增1。如果第一个值没有被赋初值,将会被自动置为0。
let yearMonth = Month.May.rawValue
print("数字月份为: \(yearMonth)。")
扩展
关键字 extension
扩展就是向一个已有的类、结构体或枚举类型添加新功能,但是不能重写已有的功能
https://www.runoob.com/swift/swift-extensions.html
可选链
https://www.runoob.com/swift/swift-optional-chaining.html