Swift 高级
一.基础概述
1.基础的基础
- 值(value):不变的,永久的,可以是人为定义的,也可以是运行时候生成的。
- 变量:可以被刷新更改的,用var定义,更新的过程称为变量的改变(mutating)。
- 常量变量:用let定义,一旦被赋予一个值,就不能被更改。
- 值类型(value type):比如结构体(struct),枚举(enum),NSString等,当将一个值类型赋值给另一个,则这两个变量都会包含同样的值,约等于内容被复制了一遍,精确而言是被赋值的变量和另外一个变量包含的相同的值。
- 引用类型:比如类(class),它是一个指向另一个值的值。数个引用可能会指向同一个值,所以引用类型可能会因为程序中的几个不同部分的改变而改变。对于累来说,我们只能在变量中持有对他的引用,然后用这个引用去访问这个类。
-
引用类型具有同一性,可以用 === 来检查两个变量是否引用了同一个对象。
-
值类型不存在同一性,不能判定某个变量和其他变量一起持有了相同的值,只能检查是否都包含这个值。
-
有时候 == 被称为结构相等, 而 = = =被称为指针相等或者引用相等。
-
在c++中,值类型是公有的,引用类型是私密的,而在c#之后,他们都是私密的,主要区别就是:
-
- 存储空间:
-
-
- 1.创建引用类型时,runtime会为其分配两个空间,一块空间分配在堆上,存储引用类型本身的数据,另一个块空间分配在栈上,存储对堆上数据的引用,实际上存储的堆上的内存地址,也就是指针。
-
-
-
- 2.创建值类型时, runtime会为其分配一个空间,这个空间分配在变量创建的地方,如:
-
-
-
-
- 如果值类型是在方法内部创建,则跟随方法入栈,分配到栈上存储。
-
-
-
-
-
- 如果值类型是引用类型的成员变量,则跟随引用类型,存储在堆上。(对象的成员变量)
-
-
-
- 赋值方式:将值类型的变量赋值给另一个变量,会执行一次赋值,赋值变量包含的值;将引用类型的变量赋值给另一个引用类型变量,它复制的是引用对象的内存地址,在赋值后就会多个变量指向同一个引用对象实例。
-
- 值的传递:对于值类型(stu1),传递的是该值类型实例的一个副本,因此原本的值stu1并没有改变;对于引用类型(Student stu2),传递是变量stu2的引用地址(即stu2对象实例的内存地址)拷贝副本,因此他们操作都是同一个stu2对象实例。
-
可以用let来声明一个引用变量,这样的话会让引用变为常量,意味着会使变量不能改编为引用其他的东西,但是不表示这个变量所引用的对象本身不能被改变。这样做的话只有指向的关系被常量化了,但是引用对象本身还是会被改变的。所以当一个声明用let来表示的时候不一定表示这个东西是完全不可变的,必须知道这个变量引用的是值类型还是引用类型。
-
深复制:复制了值的内容。
-
浅复制:当引用类型被复制时候,其内容不会被复制,只有引用会被复制。
-
写时复制:
- 数组:数组中只有当其中的元素满足是一个值类型时候,数组本身才具有值类型。
- 加final的类被创建后就不提供任何方法来改变其内部状态,意味即使他们是类,但是具有值语义。
- swift的函数是一等值,可以在函数内部声明另一个函数,也可以在do的作用域或者其他作用域声明函数,但是被传出这个作用域的时候(retrun出去),这个函数可以捕获局部变量,这些局部变量将存于函数中,不会随局部作用域的结束而消亡,这样的函数叫做闭包。
- 用func定义的函数,如果包含了外部的变量,也是一个闭包。
- 函数是引用类型,被引用时候是状态被共享而不会导致当前状态的复制,当两个闭包持用同样的局部变量的时候,他们是共享这个变量以及他的状态的。
- 方法:定义在类或者协议中的函数,他们都有一个隐藏的self函数,如果一个函数不是接受多个参数,只接受部分参数,然后返回一个接受其余参数的函数的话,这个函数就是柯里化函数,不是方法的函数叫做自由函数。
- 静态派发:自由函数和结构体的调用方法,对于这些函数的调用,在编译的时候就以及确定了,静态派发就是编译器可能内联这些函数,就是完全不去做函数的调用,而是将函数调用替换为函数中需要执行的代码。
- 动态派发:类和协议的方法,编译器在编译时候不需要知道哪个函数将会被调用。
- 多态:一般使用子类型和重写,根据类型的不同,同样的方法会呈现不同的行为。另一种方法是函数重载,指为不同的类型多次写同一个函数的行为。第三种方法是通过泛性,就是一次性的编写能够接受任意类型的函数或者方法,与方法重写不同的是,泛型中的方法在编译期间就是静态已知的。
2.数组
- 数组已有序的方式存储一系列相同类型的元素,对于每个元素,可以用下标进行直接访问。
- Array是值类型的,对新数组的改变不会影响调用者所持有的数组。
- NSArray中没有更改方法,想要更改一个这样的数组,需要使用NSMutableArray,NSArray是引用类型的,如果需要改变新的引用而不改变原引用,需要先手动进行复制,x.copy() as!NSArray
- 数组的便利方法:非可选型方法
-
- 迭代数组:for x in array
-
- 迭代除了第一个元素以外的数组其余部分: for x in array.dropFirst()
-
- 迭代除了最后 5 个元素以外的数组: for x in array.dropLast(5)
-
- 列举数组中的元素和对应的下标:for (num, element) in collection.enumerated()
-
- 寻找一个指定元素的位置: if let idx = array.index { someMatchingLogic($0) }
-
- 对数组中的所有元素进行变形: array.map { someTransformation($0) }
-
- 筛选出符合某个标准的元素: array.filter { someCriteria($0) > }”
-
- “在 Swift 数组下标方法内部,存在对下标越界的安全特性的检查。当我们将优化器的优化级别设置为 -Ounchecked 时,这些检查将不会执行,这可以给我们带来更高的访问性能。但如果标准库中设计的是“可失败”的下标访问的话,这项优化将无法进行。另外,下标越界这样的错误应该归结于程序员的错误,通过运行时来“遮蔽”这种错误,往往会带来更大的问题。至于上面那些让使用者可以不进行索引计算的简便方法,更多的是结果而非原因。” ——摘录来自: Chris Eidhof. “Swift 进阶。” Apple Books.
Map
Map使用函数将行为参数化
-
- map 和 flatMap — 如何对元素进行变换
-
- filter — 元素是否应该被包含在结果中
-
- reduce — 如何将元素合并到一个总和的值中
-
- sequence — 序列中下一个元素应该是什么?
-
- forEach — 对于一个元素,应该执行怎样的操作
-
- sort(默认升序),lexicographicCompare 和 partition — 两个元素应该以怎样的顺序进行排列
-
- index,first 和 contains — 元素是否符合某个条件
-
- min 和 max — 两个元素中的最小/最大值是哪个
-
- elementsEqual 和 starts — 两个元素是否相等
-
- split — 这个元素是否是一个分割符
-
- prefix - 当判断为真的时候,将元素滤出到结果中。一旦不为真,就将剩余的抛弃。和 filter 类似,但是会提前退出。这个函数在处理无限序列或者是延迟计算 (lazily-computed) 的序列时会非常有用。
-
- drop - 当判断为真的时候,丢弃元素。一旦不为真,返回将其余的元素。和 prefix(while:) 类似,不过返回相反的集合。
Filter
filter可以将符合一定条件的元素过滤出来并且用它们创建一个新的数组,对数组进行循环并且根据条件过滤其中元素的模型。函数将数组中为true的数字输出。
可以使用Swift内建的用来代表参数的简写$0。
可以将map和filter组合使用,比如找出1到9的平方中的偶数:
Reduce
reduce可以把初始值以及中间值与序列中的元素进行合并的函数进行了抽象.
flatMap
map对数组操作的函数返回的是另一个数组。
flatMap的变化后返回的是同一个数组。
flatMap 的另一个常见使用情景是将不同数组里的元素进行合并。为了得到两个数组中元素的所有配对组合,我们可以对其中一个数组进行 flatMap,然后对另一个进行 map 操作:
forEach
forEach:它和 for 循环的作为非常类似:传入的函数对序列中的每个元素执行一次。和 map 不同,forEach 不返回任何值。
for element in [1,2,3] {
print(element)
}
[1,2,3].forEach { element in
print(element)
}
和for没有太大区别,如果你想要对集合中的每个元素都调用一个函数的话,使用 forEach 会比较合适。你只需要将函数或者方法直接通过参数的方式传递给 forEach 就行了,这可以改善代码的清晰度和准确性。比如在一个 view controller 里你想把一个数组中的视图都加到当前 view 上的话,只需要写 theViews.forEach(view.addSubview) 就足够了。
但是注意在 forEach 中的 return 并不能返回到外部函数的作用域之外,它仅仅只是返回到闭包本身之外。
数组类型
- 切片:如“let slice = fibs[1…]”,它将返回数组的一个切片 (slice),其中包含了原数组中从第二个元素开始的所有部分。得到的结果的类型是 ArraySlice,而不是 Array。切片类型只是数组的一种表示方式,它背后的数据仍然是原来的数组,只不过是用切片的方式来进行表示。这意味着原来的数组并不需要被复制。如果要将切片转换为数组的韩剧啊,可以通过将它传递给Array的构建方法来完成。
- 桥接:Swift的数组可以桥接到OC中,因为NSArray只能持有对象,所以在桥接时候,编译器和运行时会自动把不兼容的值(如enum)用一个不透明的box对象包装起来。
3.字典
- 字典包含键值对,每个字典中,每个键都只能出现一次。
- 字典查找将返回的是可选值,当特定键不存在时,下标查询返回nil。
- 字典是一个稀疏结构,一个键下是否存在某个值,对确定其他键下毫无帮助。
可变性
如果需要移除某个值,可以用下标将其对应的值设为nil,或者调用removeValue(forKey:),后一种方法在删除这个键后,还会将被删除的值返回
字典中的方法
- 合并两个字典:merge(_ :uniquingKeysWith:),他接受两个参数,第一个是要进行合并的键值对,第二个是定义如何合并相同键的两个值的函数。