Swifter 读书笔记(一)

Currying 柯里化

Swift 里可以将方法进行柯里化,把接受多个参数的方法转变成接受第一个参数的方法,并且返回 一个接受其余参数并且返回结果的新方法。

curry 是 Swift 中一个很灵活的特性,curry 就是一个用方法生成方法的机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func add(a: Int, b:Int) -> Int {
return a + b
}
add(1, b: 2)

let numbers = 1...10
let added = numbers.map {
return add($0, b: 2)
}
func add(a: Int) -> (Int -> Int) {
return {
b in return a + b
}
}

let added1 = numbers.map(add(2))

向上面的代码那样,给 add 函数传入不同参数,它就会依照这个参数值生成一个新的函数,这个就会对它接受的参数与之前 add 指定的的增量进行加法操作。

1
2
3
4
5
6
func add(a: Int)(b: Int) -> Int {
return a + b
}

// 先传入2 则 返回一个 2 + b -> Int 的函数 b 为传入参数。
let addTwo = add(2)

上面我们对自定义方法进行 curry 处理,那么如果系统内置的方法,或者别的第三方库中定义的方法,如何对他们应用 curry

例子:

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
43
44
45
46
47
48
49
50
51
52
53
54
// 自定义方法进行 curry 处理,那么如果系统内置的方法,或者别的第三方库中定义的方法,如果使用对他们应用 curry

// 给 NSNumber 定义一个 multiple 方法
extension NSNumber {
class func multiple(left: Int, right: Int) -> Int {
return left * right
}

class func add(left: Double, right: Double) -> Double {
return left + right
}
}

func add(a: Int, b:Int) -> Int {
return a + b
}

func add(a: Int) -> (Int -> Int) {
return {
b in return a + b
}
}

// 不改变这个方法的定义,怎样让它支持 curry 特性
func curry<A, B, C>(function: (A, B) -> C) -> (A -> (B -> C)) {
return {
a in {
b in
return function(a, b)
}
}
}

add(1, b: 2)
let numbers = 1...10
let added = numbers.map {
return add($0, b: 2)
}
let added1 = numbers.map(add(2))

/**
* 首先,curry 接受的参数类型为(Int, Int) -> Int 这个表示的是接受两个 Int 类型的参数,
* 并返回一个 Int 类型。也就是我们在 NSNumber 扩展中定义的 multiple 函数类型。我们可以
* 将 Number.multiple 作为参数传给这个方法
*/

// curry 这个函数,会把传给它的函数转换成另一个带有 currying 特性的函数

let curriedMultiple = curry(NSNumber.multiple)

let a = curriedMultiple(3)(2)
let b = curry(NSNumber.add)(3.0)(3.0)
print(b)
print(a)

Struct Mutable 的方法

在 Swift 中我们基本都是用 struct 去定义一个纯数据类型。

1
2
3
4
5
struct User {
var age: Int
var weight: Int
var height: Int
}

在变量里添加一些简单的改变变量里内容的方法

1
2
3
4
// Struct 出来的变量是 Immutable 的,不可改变的。想要用一个方法去改变变量里面的值,就需要加上一个关键字 mutating,User 的 Weight 是 Immutable 的,所以 += 无法再这两个 Int 上使用
mutating func gainWeight(newWeight: Int) {
weight += newWeight
}

将 protocol 的方法声明为 mutating

Swift 的 protocol 不仅可以被 class 类型实现,也适用于 struct 和 enum。

在写给别人用的接口时,需要考虑是否使用 mutating 来修饰方法,Swift 的 mutating 关键字修饰方法,是为了能在该方法中修改 struct 或者 enum 的变量。比如下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
protocol Vehicle {
var numberOfWheels: Int{get}
var color: UIColor {get set}
mutating func changeColor()
}

struct MyCar: Vehicle {
let numberOfWheels = 4
var color = UIColor.blueColor()
mutating func changeColor() {
color = UIColor.redColor()
}
}

在使用 class 来实现带有 mutating 的方法的接口时,具体实现的前面是不需要加 mutating 修饰的,因为 class 可以随意更改自己的成员变量。所以在接口里用 mutating 修饰方法,对于 class 的实现是完全透明,可以当作不存在的。

Sequence … 关联

Swift 的 for…in 可以用在所有实现了 SequenceType 的类型上。

首先需要实现 GeneratorType。比如一个实现了反向的 generator 和 sequence 可以这么写:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 首先定义一个实现了 GeneratorType protocol 的类型
// GeneratorType 需要制定一个 typealias Element
// 以及提供一个返回 Element? 的方法 next()

// reverse 反转
class ReverseGenerator: GeneratorType {
typealias Element = Int

var counter: Element

init<T>(array: [T]) {
self.counter = array.count - 1
}

init(start: Int) {
self.counter = start
}

func next() -> Element? {
if counter < 0 {
return nil
}
counter -= 1
return counter + 1
}
}

// 然后我们来定义 SequenceType
// 和 GeneratorType 很类似,不过换成指定一个 typealias Generator
// 以及提供一个返回 Generator?的方法 generate()

struct ReverseSequence<T>: SequenceType {
var array: [T]

init(array: [T]) {
self.array = array
}

typealias Generator = ReverseGenerator
func generate() -> Generator {
return ReverseGenerator(array: array)
}
}

let arr = [0, 1, 2, 3, 4]

// 对 SequenceType 可以使用 for...in来循环访问

//for i in ReverseSequence(array: arr) {
// print("Index\(i) is \(arr[i])")
//}

// for in 这样的方法到底做了什么
var ar = ReverseSequence(array: arr)
var g = ReverseSequence(array: arr).generate()

while let obj = g.next() {
print(obj)
}

// 顺便你可以使用 map, filter,和 reduce 这些方法,接口扩展实现了他们

多元祖 Tuple

使用多元组,会让程序轻松不少,比如交换输入:

1
2
3
4
5
func swapMe<T>(inout a: T, inout b: T){
let temp = a
a = b
b = temp
}

使用多元祖:

1
2
3
func swapMe<T>(inout a: T, inout b: T){
(a,b) = (b,a)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// OC 中 CGRect 有一个辅助方法叫做 CGRectDivide 用来将一个 CGRect 在一定位置切分成两个区域。

// CGRectDivide(CGRect rect, CGRect *slice, CGRect *remainder, CGFloat amount, CGRectEdge dge
/*
CGRect rect = CGRectMake(0, 0, 100, 100)

CGRect small
CGRect large
CGRectDivide(rect, &small, &large, 20, CGRectMinXEdge)
*/

// 在 Swift 中,使用了多元组

// func rectsByDividing(atDistance: CGFloat, fromEdge: CGRectEdge)
// -> (slice: CGRect, remainder: CGRect)
let rect = CGRectMake(0, 0, 100, 100)

let (small, large) = rect.divide(20, fromEdge: .MinXEdge)

@autoclosure 和 ??

@autoclosure 做的事情就是把一句表达式自动地封装成一个闭包。

例如:

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
func logIfTrue(predicate: () -> Bool) {
if predicate() {
print("True")
}
}

// 执行:
logIfTrue({return 2 > 1})

// 在 swift 中对闭包的用法可以进行一些简化
logIfTrue({2 > 1})

// 还可以更近一步简化,闭包是最后一个参数
logIfTrue{
2 > 1
}

func logIfTrue(@autoclosure predicate:() -> Bool) {
if predicate() {
print("True")
}
}

// 这时候我们就可以直接写:
logIfTrue(2 > 1)

Swift 会把 2 > 1这个表达式自动转换为 () -> Bool。

在 Swift 中,有一个非常有用的操作符,可以快速的对 nil 进行判断,??

这个操作符可以判断输入并在当左侧的值是非 nil 的 Optional 值时返回其 value,当左侧是 nil 时返回右侧的值,比如:

1
2
3
4
var level: Int?
var startLevel = 1

var currentLevel = level ?? startLevel

因此最后 一个 startLevel 被赋值给了 currentLevel ,??的定义:

1
2
func ??<T>(optional: T?, @autoclosure defaultValue:() -> T?) -> T?
func ??<T>(optional: T?, @autoclosure defaultValue:() -> T) -> T

这里我们输入满足的是后者,虽然表面上看,startLevel 只是一个 Int,其实它被自动封装成了一个() -> Int,实现:

1
2
3
4
5
6
7
8
9
10

func ??<T>(optional: T?, @autoclosure defaultValue:() -> T) -> T
{
switch optional {
case .Some(let value):
return value
case .None:
return defaultValue()
}
}

如果我们直接使用 T,那么久以为这 在 ?? 操作符真正取值之前,我们就必须准备好一个默认值。这个默认值的准备和计算是会消耗性能的。但是其实要是 optional 不是 nil 的话,我们是完全不需要这个默认值的,而会直接返回 optional 解包后的值。这样一来,默认值就拜拜准备了。这样的开销是完全可以避免的,方法就是讲默认值的计算推迟到了 optional 判定为 nil 之后。

@autoclosure 并不支持带有输入参数的写法,只有形如() -> T 的参数才能使用这个特性进行简化,另外因为调用者往往很容易忽视 @autoclosure 这个特性,需要小心使用,还是写完整的闭包写法比较好。

练习:其实 && 和 || 这两个操作符里也用到了 @autoclosure,试试看如何实现这两个操作符

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
// false && true -> false true


func &&(@autoclosure predicate1: () -> Bool, @autoclosure predicate2: () -> Bool) -> Bool {
guard predicate1() else {
return false
}

guard predicate2() else {
return false
}

return true
}

func ||(@autoclosure predicate1: () -> Bool, @autoclosure predicate2: () -> Bool) -> Bool {
if predicate1() {
return true
}

if predicate2() {
return true
}

return false
}

Optional Chaining(可选链)

使用 Optional Chaining 可以让我们摆脱很多不必要的判断和取值,但是需要注意一些陷阱

因为 Optional Chaining 是随时都可能体检返回 nil 的,所以使用 Optional Chaining 所得到的东西其实都是 Optional 的,比如有下面的一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Toy {
let name: String
init(name: String) {
self.name = name
}
}

class Pet {
var toy: Toy?
}

class Child {
var pet: Pet?
}

如果我们想通过小明,知道小明的宠物的玩具的名字,则:

1
2
let xiaoming = Child()
let toyName = xiaoming.pet?.toy?.name

在这个 Optional Chaining 中,我们在任何一个 ?.时,都可能遇到 nil 而提前返回。

实际使用中,大多情况下我们更希望使用可选绑定来直接取值:

1
2
3
if let toyname = xiaoming.pet?.toy?.name{
// 太好了,小明既有个宠物,而且宠物还正好有个玩具
}

接下来,稍微和其他特性结合,事情就会变得复杂起来:

如果为 Toy 定义一个扩展,增加一个玩玩具的 play()方法

1
2
3
4
5
extension Toy {
func play() {
// ...
}
}

这时,处于对代码的扩展性,也许会有其他人有一个宠物,宠物会有玩具,玩具有 play()方法,则做一个闭包方便调用

1
let playClosure = {(child: Child) -> () in child.pet?.toy?.play()}

这样的代码没有意义,在对于 play()的调用上,定义时我们没有写 play()的返回,表示该方法返回 Void,经过 Optional Chaining 我们得到的是一个 Optional 的结果,就是说,最后应该是这样一个 closure:

1
let playClosure = {(child: Child) -> ()? in child.pet?.toy?.play()}

这样调用的返回将是一个() ? 这时候就可以通过可选绑定来判断方法是否被调用成功了:

1
2
3
4
5
if let result: () = playClosure(xiaoming) {
print("好开心~")
} else {
print("没有玩具可以玩")
}

操作符

与 Objective-C 不同,Swift 支持重载操作符这样的特性,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Vector2D {
var x = 0.0
var y = 0.0
}

// 两个 Vector2D 相加:
let v1 = Vector2D(x: 2.0, y: 3.0)
let v2 = Vector2D(x: 1.0, y: 4.0)

// 重载加号操作符
func +(left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}

let v4 = v1 + v2

上面定义的加号,减号,负号等都是存在于 Swift 中的运算符了,我们所做的只是变换它参数进行重载。
如果我们要定义一个全新的运算符,需要做的事情会多一件,根据定义选取+*运算符:

1
2
3
func +*(left: Vector2D, right: Vector2D) -> Double {
return left.x * right.x + left.y * right.y
}

但是编译器会报出错误:Operator implementation without matching operator declaration

这时因为,我们没有对这个操作符进行申明。之前可以直接重载 + - * % 是因为 Swift 中早已经有定义了。

这时,我们需要告诉编译器这个符号是一个操作符:

1
2
3
4
infix operator +* {
associativity none
precedence 160
}

infix

表示要定义一个中位操作符,即前后都是输入,其他的修饰符还包括,prefix 和 postfix

associativity

定义了结合律,即如果多个同类的操作符顺序出现的计算顺序,常见的+ - 都是 left,多个加法同时出现,按照从左往右顺序计算。点乘的结果是一个 Double 不会再和其他点乘结合使用,所以这里写 none

precedence(优先权)

运算的优先级,越高则优先进行计算。 乘除150 加减140 这里我们定义的是160

1
2
let result = v1 +* v2
// 输出14.0

注意点:Swift 的操作符是不能定义在局部域中的,因为至少会希望在全局范围使用你的操作符,否则操作符就失去了意义。不同 module 的操作符是有可能冲突的,这对于库开发者来说是需要特别注意的地方。 如果库中的操作符冲突的话,使用者是无法像解决类型名冲突那样通过指定库名字来进行调用的。因此在重载或者自定义操作符时,应当尽量将其作为其他某个方法的“简便写法”,我们不应该滥用这个特性。

func 的参数修饰

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
func incrementor(variable: Int) -> Int {
return variable + 1
}

// 使用以前 C 的写法

//func incrementor(variable: Int) -> Int {
// return ++variable
//}

// 错误的,由于 Swift 默认该参数是 let,而++则说明这是个变量,

func incrementor(var variable: Int) -> Int {
return ++variable
}

var luckyNumber = 7
let newNumber = incrementor(luckyNumber)

// newNumber = 8 luckyNumber = 7

// 正如上面的例子,将参数写作 var 后,var 只在方法内部起作用,不直接影响输入的值,如果希望在方法内部直接修改输入的值,使用 inout

func incrementor(inout variable: Int) {
++variable
}

let newNumber1 = incrementor(&luckyNumber)

print(luckyNumber)

最后,要注意参数的修饰是具有传递限制的,对于跨越层级的调用,需要保证同一参数的修饰是统一的。例子:

1
2
3
4
5
6
func makeIncrementor(addNumber: Int) -> ((inout Int) -> ()) {
func incrementor(inout variable: Int) -> () {
variable += addNumber
}
return incrementor
}

外层的 makeIncrementor 的返回里也需要在参数类型前面明确指出修饰词,以符合内部的定义,否则将无法编译通过

字面转换量

Swift 提供了一组非常有意思的接口,用来将字面量转换为特定类型

开发中,经常可能用到的有:

ArrayLiteralConvertible
BooleanLiteralConvertible
DictionaryLiteralConvertible
FloatLiteralConvertible
NilLiteralConvertible
IntegerLiteralConvertible
StringLiteralConvertible

所有的字面量转换接口都定义了一个 typealias 和对应的 init 方法。拿 BooleanLiteralConvertible 举个例子

1
2
3
4
5
6
protocol BooleanLiteralConvertible {

associatedtype BooleanLiteralType

init(booleanLiteral value: BooleanLiteralType)
}

于是在我们需要自己实现一个字面量转换的时候,可以简单地只实现定义的 init 方法就行了。举个不太有实际意义的例子,我们想实现自己的 Bool 类型,可以这么做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum MyBool: Int {
case myTrue, myFalse
}

extension MyBool: BooleanLiteralConvertible {
init(booleanLiteral value: Bool) {
self = value ? myTrue : myFalse
}
}


let myTrue: MyBool = true
let myFalse: MyBool = false

myTrue.rawValue
myFalse.rawValue

BooleanLiteralType 大概是最简单的形式,如果我们深入一点,就会发现像是 StringLiteralConvertible 这样的接口会复杂一些,这个接口不仅类似上面布尔的情况,定义了 StringLiteralType 及接受其的初始化方法,这个接口还要求实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 如果通过 String 赋值生成 Person 对象的话,可以改写这个类:


class Person1: StringLiteralConvertible {
let name: String
init(name value: String) {
self.name = value
}

required init(stringLiteral value: String) {
self.name = value
}

required init(extendedGraphemeClusterLiteral value: String) {
self.name = value
}

required init(unicodeScalarLiteral value: String) {
self.name = value
}
}

在所有的接口定义的 init 前面都加上了 required 关键字,这个类的子类都需要保证能够做类似的字面量转换,确保类型安全,但是会有很多重复代码,需要做一个修改加入,convenience

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
class Person2: StringLiteralConvertible {
let name: String
init(name value: String) {
self.name = value
}

required convenience init(stringLiteral value: String) {
self.init(name: value)
}

required convenience init(extendedGraphemeClusterLiteral value: String) {
self.init(name: value)
}

required convenience init(unicodeScalarLiteral value: String) {
self.init(name: value)
}
}

let p1: Person1 = "xiaoMing"
p1.name


let p: Person2 = "xiaoMing"
print(p.name)

在上面的 Person 的例子中,我们没有像 MyBool 中做的那样,使用一个 extension 的方式来扩展类,使其可以使用字面量赋值,这是因为,在 extension 中,我们是不能定义 required 的初始化方法。 我们无法为现有的非 final 的 class 添加字面量转换

文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2016/06/23/Swifter%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0(%E4%B8%80)/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论