cell分割线置顶
1 | tableView.separatorInset = UIEdgeInsets.zero |
Swift中lazy作惰性求值
函数式编程中有惰性求值的概念,即一次计算在真正需要时才执行,尽可能推迟求解表达式。
假如我们有一个数组,我们对每个元素作 element*2
的 map 操作,获取其中某一个元素,我们会如下代码处理:1
2
3let array = [1, 2, 4, 5, 3, 7]
let element = array.map{ $0 * 2 }[3]
print(element)
这个计算对每个元素都*
2,最后我们只取了其中一个值,也就是说在这个场景中另外5次计算是无意义的。
这时使用惰性求值就可以避免这算浪费。我们知道Swift中有个lazy关键字,如果用来修饰属性之类的,可以实现属性的惰性求值。同样,Swift扩展了LazySequenceProtocol协议,提供了一个lazy属性,用于处理map,filter等操作的惰性求值,定义如下代码所示:1
2
3
4extension LazySequenceProtocol {
/// Identical to `self`.
public var lazy: Self { get }
}
所以,上面这个例子如果要实现惰性求值,则可以如下代码处理:1
2
3let array = [1, 2, 4, 5, 3, 7]
let element = array.lazy.map{ $0 * 2 }[3]
print(element)
Swift的stride操作
创建一个一定步长的数组, Swift提供了一个便捷函数:stride,可以在某个区间内创建一个任意可变步长的序列,这个步长可以是任意值。1
2
3let array = stride(from: 0, to: 3, by: 0.3).map {
$0
}
stride有两个变种:
- stride(from:to:by),开区间处理,最后一个值严格小于最大值;
- stride(from:through:by),闭区间处理,最后一个值小于或等于最大值
static方法与class方法的区别
static和class这两个关键字都可以修饰类的方法,以表明这个方法是一个类方法。不过这两者稍微有一些区别:class修饰的类方法可以被子类重写,而static修饰的类方法则不能。
Swift中的函数值
在Swift中,函数是一等公民,即函数也是一种类型,可充当参数、返回值等角色,当然也可以定义函数类型的常量/变量。利用这个特性,在某些场景下可以简化我们的代码,如下代码所示:1
2
3
4
5let setInt: (Int, String) -> Void = UserDefaults.standard.set
let getInt: (String) -> Int = UserDefaults.standard.integer
setInt(10, "key")
print(getInt("key"))
Array.contains操作
Swift中判断一个Array中是否包含某个元素,我们可以使用contains方法,如下代码所示:1
2let array = [2, 5, 6, 7, 19, 40]
array.contains(10) // false
不过这个方法要求数组中的元素类型实现了Equatable协议,否则无法使用,如下代码所示:1
2
3
4
5
6
7enum Animal {
case dog
case cat(Int)
}
let animals: [Animal] = [.dog, .dog]
let hasCat = animals.contains(.cat(100)) // 编译器错误
还好Swift为我们提供了另一个contains方法,可以自定义谓词条件作为判断依据,其定义如下:1
public func contains(where predicate: (Element) throws -> Bool) rethrows -> Bool
这个方法会查看数组是否包含满足给定的谓词条件的元素。可以看到这个方法是一个高阶函数,其参数是一个尾随闭包,在闭包内我们可以根据实际需要来实现我们自己的判断。所以上面的判断可以如下代码实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14enum Animal {
case dog
case cat(Int)
}
let animals: [Animal] = [.dog, .dog]
let hasCat = animals.contains { animal in
if case .cat = animal {
return true
} else {
return false
}
}
当然,对于元素类型实现了Equatable协议的数组,也可以使用这个方法。可以自定义谓词条件,查看数组是否有满足此条件的元素,如下代码所示:1
2
3
4
5let array = [2, 5, 6, 7, 19, 40]
array.contains { (element) -> Bool in
element % 7 == 0
}
filter与flatMap过滤nil
使用高阶函数过滤一个数组中的nil可以有两种方法:filter和flatMap。
filter方法如下代码所示:1
2
3
4
5
6
7let array: [Int?] = [1, 2, 3, 5, nil, 9]
let result = array.filter { element in
element != nil
}
print(result) // [Optional(1), Optional(2), Optional(3), Optional(5), Optional(9)]
flatMap方法如下代码所示:1
2
3
4
5
6let array: [Int?] = [1, 2, 3, 5, nil, 9]
let result: [Int] = array.flatMap {
$0
}
print(result) // [1, 2, 3, 5, 9]
从输出可以看出,filter返回的仍然是一个Optional数组,而flatMap返回的是一个非Optional数组。一般推荐使用第二种方法。
Extension-Selector
在Swift中,我们可以使用#selector设置target-action模式中的action操作,如下代码所示:1
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
如果你有代码洁癖,想把#selector集中起来管理,则可以定义一个结构体,来统一归集这样的代码,如下图所示。这样看着是不是会更整洁一些?1
2
3
4fileprivate struct Action {
static let buttonTapped = #selector(ViewController.buttonTapped)
}
button.addTarget(self, action: Action.buttonTapped, for: .touchUpInside)
不过,还有种更好的方式,就是直接扩展Selector结构体,如下代码所示,这样可以在使用时直接用.buttonTapped这种方式来引用,就像我们使用.red这样的UIColor属性一样简洁。1
2
3
4extension Selector {
static let buttonTapped = #selector(ViewController.buttonTapped)
}
button.addTarget(self, action: .buttonTapped, for: .touchUpInside)
typealias泛型闭包
在 Swift 中我们可以用 typealias 来为已经存在的类型重新定义名字的,通过命名可以使代码变得更加清晰。当然也可以给闭包类型定义一个新名字,给带有泛型的闭包重新定义名字的方式如下代码所示:1
2
3
4
5
6
7
8
9typealias Block<U> = (U, U) -> Bool
func compare<T: Comparable>(number1: T, number2: T, algorithm: Block<T>) -> Bool {
return algorithm(number1, number2)
}
compare(number1: 10, number2: 20) {
$0 < $1
}
Swift自定义操作符
在Swift中,自定义操作符就是简单的二步:首先在全局使用operator关键字来声明操作符,同时用prefix、infix或postfix来声明操作符的位置;然后在所需要的类/结构体中实现操作符。如下代码所示:1
2
3
4
5
6
7
8
9
10
11
12postfix operator >?
postfix operator >!
extension MIType {
public static postfix func >?(type: MIType) -> MIType {
return MIType("Optional<\(type.name)>")
}
public static postfix func >!(type: MIType) -> MIType {
return MIType("ImplicitlyUnwrappedOptional<\(type.name)>")
}
}
自定义操作符需要以两类字符开头:
- ASCII字符中的
/, =, -, +, !, *, %, <, >, &, |, ^, ?, ~
- Unicode 中的 Mathematical Operators, Miscellaneous Symbols和Dingbats Unicode blocks这些字符中的字符
然后后面允许使用组合的Unicode字符。如下代码是以一个Miscellaneous Symbols开头的实现向量加法的操作符。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20infix operator ★+
struct Vector2D {
var x: CGFloat
var y: CGFloat
}
extension Vector2D {
static func ★+ (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
}
let vector1 = Vector2D(x: 10, y: 20)
let vector2 = Vector2D(x: 30, y: 10)
let vector = vector1 ★+ vector2
vector.x // 40.0
vector.y // 30.0
自定义操作符中的.
在自定义操作符时,可以以dot(.)开头,这种情况下,操作符后面还可以包含其它的dot(.),如下代码所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20infix operator .+.
struct Vector2D {
var x: CGFloat
var y: CGFloat
}
extension Vector2D {
static func .+.(left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
}
let vector1 = Vector2D(x: 10, y: 20)
let vector2 = Vector2D(x: 30, y: 10)
let vector = vector1 .+. vector2
vector.x
vector.y
但如果操作符不是以dot开头,则后面不能再包含dot,如operator +.+这个声明会被看成是”+”操作符后面跟了个”.+”操作符。编译器会给出如下错误提示:1
infix operator +.+ // error: operator with postfix spacing cannot start a subexpression
Swift中操作符优先级
Swift 3中改进了操作符的优先级及结合性的声明方式。
在Swift 3之前,是使用magic numbers(魔数)的方式来声明操作符的优先级,如下代码所示:1
2
3
4infix operator <~ {
associativity left
precedence 125
}
magic numbers总归是一个不好的东西,所以Swift 3改用precedence groups(优先级组)的方式来声明操作符的优先级,如下代码所示:1
2
3
4
5
6
7precedencegroup Equivalence {
associativity: left
higherThan: LogicalConjunctionPrecedence
lowerThan: ComparisonPrecedence
}
infix operator ~ : Equivalence
系统为我们提供了一些默认的precedence groups,如下代码所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43precedencegroup AssignmentPrecedence {
assignment: true
associativity: right
}
precedencegroup TernaryPrecedence {
associativity: right
higherThan: AssignmentPrecedence
}
precedencegroup DefaultPrecedence {
higherThan: TernaryPrecedence
}
precedencegroup LogicalDisjunctionPrecedence {
associativity: left
higherThan: TernaryPrecedence
}
precedencegroup LogicalConjunctionPrecedence {
associativity: left
higherThan: LogicalDisjunctionPrecedence
}
precedencegroup ComparisonPrecedence {
higherThan: LogicalConjunctionPrecedence
}
precedencegroup NilCoalescingPrecedence {
associativity: right
higherThan: ComparisonPrecedence
}
precedencegroup CastingPrecedence {
higherThan: NilCoalescingPrecedence
}
precedencegroup RangeFormationPrecedence {
higherThan: CastingPrecedence
}
precedencegroup AdditionPrecedence {
associativity: left
higherThan: RangeFormationPrecedence
}
precedencegroup MultiplicationPrecedence {
associativity: left
higherThan: AdditionPrecedence
}
precedencegroup BitwiseShiftPrecedence {
higherThan: MultiplicationPrecedence
}
wift打印对象的地址
在Swift中,我们可以使用 withUnsafePointer(to:_:)
函数来获取一个变量的指针,如下代码所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var x = 42
var y = 3.14
var z = "foo"
var obj = NSObject()
withUnsafePointer(to: &x) {ptr in print(ptr)}
withUnsafePointer(to: &y) {ptr in print(ptr)}
withUnsafePointer(to: &z) {ptr in print(ptr)}
withUnsafePointer(to: &obj) {ptr in print(ptr)}
// 输出
// 0x000000011a145660
// 0x000000011a145668
// 0x000000011a145670
// 0x000000011a145688
withUnsafePointer(to:_:)
将第一个参数转换为指针,然后将这个指针作为参数去调用第二个参数指定的闭包。如果闭包有返回值,它将作为函数的返回值。
需要注意的是,�生成的指针的生命周期限定于闭包内部,不能将其指定给外部的变量。
第二种打印变量的指针的方式如下代码所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19var x = 42
var y = 3.14
var z = "foo"
var obj = NSObject()
func printPointer<T>(ptr: UnsafePointer<T>) {
print(ptr)
}
printPointer(ptr: &x)
printPointer(ptr: &y)
printPointer(ptr: &z)
printPointer(ptr: &obj)
// 输出
// 0x000000011a145660
// 0x000000011a145668
// 0x000000011a145670
// 0x000000011a145688
ArraySlice的用途
Swift提供了ArraySlice来执行数组的切片操作。类似于其它语言中的切片(如Python),ArraySlice对象复用了原始数组的存储结构,而不是新开辟一块内存区域来将数组片断的元素拷贝过来。因此,它能让我们快速高效地对大数组的片段执行操作。如下代码所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14var array: [Int] = []
for i in 0..<1000 {
array.append(i)
}
let slice = array[100..<300]
let result = slice.map {
$0 * 2
}.reduce(0) {
$0 + $1
}
print(result) // 79800
ArraySlice与Array有相同的接口,所以通常可以在切片数组上执行与原始数组相同的操作。
注意
- ArraySlice会维持对原始数组的一个强引用,而不仅仅是它所表示的片断。这样即使原始数组对象的生命周期结束了,也可能无法释放。所以不建议长期存储ArraySlice对象,仅用于临时操作。
- 与Array不同的是,ArraySlice起始索引不一定是0,而是取决于其创建方式。一般是采用共享索引的方式,即ArraySlice对象的起始索引就是切片的开始位置,如代码清单8-2-2所示,切片是从100开始,所以slice[100]是OK的,而slice[99]会报越界错误。通常建议使用startIndex和endIndex来取代指定的索引值.