目录
一.Swift概述
1.语言层次:
- 既是一门高层级语言:可以用map或者reduce来写出类似python的代码,易于创建高阶函数。
- 又是一门低层级语言:可以直接编译为二进制可执行文件,性能上佳。
- 兼顾高低层级:通过闭包表达式映射到编译得到的汇编代码,与直接对内存计算的结果是一致的。
2.语言范式:
- 面向对象。
- 函数式:拥有范型,协议,值类型,闭包等特性。
- 可使用指针运算。
- 从下而上的编程方式:可定制化一些可重用的组件,许多swift的基本组件,如可选值和基本的运算符,不是在语言本身实现的,实在其标准库中实现的。
3.安全性:
- 除非有意为之,swift在实践中总是安全的,但不同于java的过于安全。
- 终止方法:
- C#中的终止方法,C++中的析构函数,他们的终止方法是不确定的,其受命与垃圾回收器,且运行在垃圾回收器的线程上,且有时候终止方式是完全不会被调用的。
- swift中的终止方法是引用计数器(ARC)deinit方法的调用是可以确定和预测的。
二.基础介绍
1.类型
- 值(value)是不变的,永久的,可以是被定义的,也可以是计算获得的。
- 变量(variable),以var来声明,内部可能是在某段内存新增,删除,或改变,但是逻辑上来说这个值是全新的,这个过程叫做变量的改变(mutaing)
- 常量(constant variables),以let声明,一旦被首次赋予一个值,就不能被再次赋予一个新的值了。
- 值类型(value type),如结构体(struct)和枚举(enum),当将一个结构体赋值给另一个,被赋值的变量和原始变量包含了同样的值。
- 引用(reference type),它是指向另一个值的值,两个引用可能指向同一个值,就是一个值可能被程序两个不同的部分改变。
- 类(class)是引用类型,无法在一个变量中直接持有一个类的实例。对于一个类的实例,只可以在变量中持有对他的引用,然后从这个引用中来访问它。
- 引用类型具有同一性,可以使用===来检查两个变量是否引用了同一个对象。
- 值类型不存在同一型的问题,比如无法对某个变量判定它是否和另一个变量持有一个相同的数字,只能检查他们是不是都包含同一个数字。
- 在程序语言中,==有时被称为结构相等,而= = =被称为指针相等或引用相等。
2.引用类型和值类型
- swift中,类引用不是唯一的引用类型,swift中依然有指针,不过类是最简单的应用类型。
- 一个引用变量也可以用let声明,这样话会使变量不能被改变为引用其他东西,但是它不以为这被引用的对象本身无法被改变,这个过程只有指向关系被常量化了,而对象本身还是可变的,所以在以let声明的东西不是完全不可变的,需要知道这个变量持有的是值类型还是引用类型。
- 深复制:可以通过值类型是否执行深复制来对它们分类,判断他们是否具有值语义(value semantics),这种复制可能是在赋值新变量时就发生的,也可能会延迟到变量内容发生改变的时候再发生。
- 浅复制: 如果结构体中含有一个引用类型,将结构体赋值给一个新变量时所发生的复制行为中,这些引用类型的内容是不会被自动复制一份的,只有引用本身会被复制,这种复制行为被称为浅复制。
- 写时复制:用来保证操作的高效,具体在此不细说。
- 数组:在swift中,数组这样的集合类型也是对引用类型的封装,同时使用了写时复制的方式来提供值语义的同时保证高效,在数组其中的值都满足值语义时,数组本身才具有值语义。
- 值语义的类:标记了final的类能够保证具有值语义且不被子类化,也不会被添加可变状态。
3.各种函数
- 高等函数:在swift中,函数也是值,可以将一个函数赋值给一个变量,也可以创建一个包含函数的数组,或者调用变量所持有的函数。如果一个函数的返回值是函数,那么这样的函数就叫做高等函数。
- 闭包:函数不需要声明在最高层级,可以在一个函数内部声明另一个函数,也可以在一个do的作用域或者其他作用域中声明函数。如果一个函数被定义在外层作用域中,但是被传递出这个作用域(将这个函数作为其他函数的返回值返回时),它将能够捕获局部变量,这些局部变量将存于函数中,不会随着局部作用域的结束而消亡,函数也将持有他们的状态,这种行为的变量被称为“闭合变量”,将这样的函数叫做闭包。
- 函数的定义:可以通过func关键字定义,也可以通过 {} 这样简短的闭包表达式来定义,如果使用func定义的函数,它包含了外部的变量,那他也是一个闭包。
- 函数的类型:函数是引用类型,将一个捕获了状态的函数赋值给另一个变量,并不会导致这些状态被复制。和对象引用类似,这些状态会被共享。就是当两个闭包持有同样的局部变量是,他们是在共享这个变量以及它的状态的。
- 方法:定义在类或者协议中的函数就是方法,它们有一个隐式的self函数。
- 柯里化函数:当一个函数不是接受全部参数,而是先接受部分参数,又返回一个接受其他参数的函数的话,这个函数就是一个柯里化函数。
- 自由函数:不是方法的函数。
- 完整的函数:一个完整的函数不止包括函数的基本名,也包括它的参数标签。
- 静态派发:自由函数和结构体上调用的方法是静态派发的。对于这些函数的调用是在编译的时候就已经确定了的。对于静态派发的调用,编译器可能可以内联这些,就是在完全不去做函数调用,而是将函数调用替换成函数中需要执行的代码。优化器还可以帮助丢弃或者简化那些在编译时就确定不会被实际执行的代码。
- 动态派发:类或者协议上的方法的动态派发的,编译器在编译时不需要知道哪个函数将被调用,在swift中,这种动态特性要么由vtable来完成要么通过selector和objc_msgSend来完成,前者的处理方法和c++或者java中类似,而后者只针对@objc的类和协议上的方法。
- 多态:子类型和方法重写是实现多态特性的手段,会根据类型的不同,同样的方法给出不同的行为,另一种方式是函数重载,特使指为不同类型多次写同一个函数的行为。第三种方法是通过泛型,也就是一次性地编写能够接受任意类型的函数或者方法,不过这些方法的实现会各有不同。与方法重写不同的是,泛型中的方法在编译期间就是静态已知的。
三.集合的数据类型
一.数组
1.基础
创建一个数组:
var mutableNums = [0,1,2,3,4]
在结尾添加元素:
mutableNums.append(5)
mutableNums.append(contentsOf:6...8)
mutableNums.append(contentsOf:[9,10,11])
//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
- 创建数组也可以用let来创建一个具有不变性的数组,编译器保证其不会被改变,但是如果是使用let定义的类实例对象,它是没有值语义的,此时let只能保证这个引用不能被改变,但是引用所指向的对象是可以被改变的。
- 在Fundation框架中的NSArray中没有更改方法,想要更改一个数组,可以使用NSMutableArray,但是,即使在使用一个不可变的NSArray中,它的引用特性也不能保证这个数组不会被改变。
let a = NSMutableArray(array: [1,2,3])
let b:NSArray = a
a.insert(4, at: 3) //[1,2,3,4]
b //[1,2,3,4]
let c = NSMutableArray(array: [1,2,3])
let d = c.copy() as! NSArray //赋值前先手动复制
c.insert(4, at: 3) //[1,2,3,4]
d //[1,2,3]
2.非索引操作数组
var testArray = [5,2,6,1,8,6]
for x in testArray{print(x)} //for遍历
//5,2,6,1,8,6
testArray.forEach{print($0)} //forEach遍历
//5,2,6,1,8,6
for x in testArray.dropFirst(){print(x)} //除了首字符,遍历数组
//2,6,1,8,6
for x in testArray.dropFirst(4){print(x)} //除了前4个,遍历数组
//8,6
for (num,element) in testArray.enumerated(){print(num,element)} //遍历数组与他的下标
//0 5,1 2,2 6,3 1,4 8,5 6
if let idx = testArray.firstIndex(of:2){print(idx)} //寻找特定字符的下标
//1
let mapTest = testArray.map{$0 + 1} //对数组每一个值进行一些操作
//6,3,7,2,9,7
let sortArray = testArray.sorted{$0 < $1} //排序
//1,2,5,6,6,8
let deleterSix = testArray.filter{($0) == 6} //筛选出符合条件的字符
//[6,6]
3.数组中的函数
map
let mapArray = [1,2,3,4,5]
let arraySquares = mapArray.map{$0 * $0}
//[1, 4, 9, 16, 25]
- 当map出现时,意味着即将有一个函数被作用在数组的每个元素上,并返回另一个数组,新数组中包含了所有被函数转换后的结果。
- 因为新数组是由map的结果得到的,所以不会再改变他的值,就不需要用var来声明了。
map的实现
- Element是数组中原来元素类型的占位符。
- T是元素经过函数变化后类型的占位符。
- T的具体类型有调用者传入map的transform方法的返回值类型来决定。
- transform就是闭包传入的方法,比如例子中的{$0 * $0}
extension Array{
func mapTest<T>(_ transform:(Element)throws -> T)rethrows ->[T]{
var result:[T] = []
result.reserveCapacity(count)
for x in self{
result.append(try transform(x))
}
return result
}
}
let map = mapArray.mapTest{$0 * $0}
print(map) // [1, 4, 9, 16, 25]
- map将模板代码分离出来,如何变化的逻辑代码通过调用者提供的变化函数作为参数进入map中进行使用。优化了代码风格,减少了输入的操作。
函数式套娃
extension Array{
func forCalculation<Result>(_ initialResult:Result,_ nextPartialResult:(Result,Element) -> Result) -> [Result] {
var runing = initialResult
return map{
next in runing = nextPartialResult(runing,next)
return runing
}
}
}
mapArray.forCalculation(0, +) //[1, 3, 6, 10, 15]
mapArray.forCalculation(1, *) //[1, 2, 6, 24, 120]
- 创建了一个中间变量来存储每一步的值,然后使用map来从这个中间值中逐步计算结果。
Filter
Filter用来检查一个数组,然后将这个数组中符合一定条件的元素过滤并形成一个新的数组。
let nums = [1,2,3,4,5,6,7,8,9,10]
print(nums.filter{$0 % 2 == 0}) // [2, 4, 6, 8, 10]
Filter的实现
extension Array{
func filterTest(_ isincluded:(Element) -> Bool) -> [Element]{
var result:[Element] = []
for x in self where isincluded(x){
result.append(x)
}
return result
}
}
print(nums.filterTest{$0 % 2 == 0}) //[2, 4, 6, 8, 10]
Reduce
reduce方法将一个初始值,以及一个将中间值与序列中的元素进行合并的函数进行了抽象。
let nums = [1,2,3,4,5,6,7,8,9,10]
extension Array{
func reduceTest<Result>(_ initialResult:Result,_ nextPartialResult:(Result,Element) -> Result) -> Result{
var result = initialResult
for x in self{
result = nextPartialResult(result,x)
}
return result
}
}
print(nums.reduceTest(0, +)) // 55
2.数组类型
1.切片
let fibs = [1,2,3,4,5]
let slice = fibs[1...] //[2,3,4,5]
type(of:slice) //ArraySlice<Int>
返回的是一个数组的切片,切片类型只是数组的一种表示方式,它背后的数据仍然是原来的数组,意味着原来的数组不需要被复制,如果需要把切片转换为数组,可以通过将其传给数组的构建方法来完成。
let newArray = Array(slice) //type:Array
2.桥接
Swift的数组可以桥接到OC中,因为NSArray只能持有对象,所以对Swift数组进行桥接转换是,编译器和运行时会自动把不兼容的值(比如swift的枚举)用一个不透明的box对象包装起来。一些值类型,比Int,Bool,String,Dictionary,Set将被自动桥接到OC中对应的类型。
二.字典
- 字典包含键以及它们所对应的值,在一个字典中,每个键都只能出现一次。
- 通过键来获取值所花费的平均时间是常熟量级的。
- 字典是无序的,使用for循环来枚举字典中的键值对时,顺序是不确定的。
- 字典查找将返回的是可选值,当特定键不存在时,下标查询返回nil。