学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

Swift花园之设计模式 上

init模式

初始化是准备要使用的类,结构体或枚举的实例的过程。

逐一成员构造器 (只有结构体有)

编译器会为结构体自动生成一个逐一成员构造器。生成的构造器将包含除拥有默认值的常量属性之外的所有属性,包括可选型;它的可访问性是 internal,所以对其他模块不可见。

只要结构体的任何一个存储属性是 private 的,那么结构体的默认逐一成员构造器也将是 private 的。同样,如果结构体的任何一个存储属性是 file private 的,那么逐一成员构造器也将是 file private 的。其他情况,这个构造器的访问级别是 internal。

失败构造器

有些情况下你发现事情不对,你不想因此创建出糟糕的或者不合理的对象。比如你想要滤掉一堆点中的原点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Point {
let x: Int
let y: Int

init?(x: Int, y: Int) { // ? 问号标记构造器可能失败
if x == 0 && y == 0 {
return nil
}
self.x = x
self.y = y
}
}
let p1 = Point(x: 0, y: 0) // nil
let p2 = Point(x: 1, y: 1) // valid point

枚举遵循 RawRepresentable 协议,它们可以通过 rawValue 来构造,这也是一个失败构造器模式的例子。

1
2
3
4
5
6
7
8
enum Color: String {
case red
case blue
case yellow
}

let c1 = Color(rawValue: "orange") // nil, 没有这个case
let c2 = Color(rawValue: "red") // .red

类初始化

类并没有逐一成员构造器。它们是引用类型,加上继承逻辑,为类自动生成逐一成员构造器这件事要复杂很多。

默认构造器

如果你为包括可选型在内的所有存储属性提供了默认值,你会获得一个自动生成的 internal 访问级别的默认构造器。

1
2
3
4
5
class Point {
let x: Int = 1
let y: Int = 1
}
let p1 = Point()

或者像这样:

1
2
3
4
5
6
7
class Point {
let x: Int = 0
let y: Int = 0
var key: Int!
let label: String? = "zero"
}
let p1 = Point()

指定构造器

指定构造器是一个类的主构造器

指定构造器是没有用 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
26
27
28
29

class Point {
let x: Int
let y: Int

init(x: Int, y: Int) { // 这是指定构造器
self.x = x
self.y = y
}
}

class NamedPoint: Point {

let label: String?

init(x: Int, y: Int, label: String?) { // 指定构造器
self.label = label

super.init(x: x, y: y)
}

init(point: Point, label: String?) { // 也是指定构造器
self.label = label
super.init(x: point.x, y: point.y) // 向上代理
}
}

let p1 = NamedPoint(x: 1, y: 1, label: "first")
let p2 = NamedPoint(point: Point(x: 1, y: 1), label: "second")

指定构造器必须调用它的直接父类的某个指定构造器,所以你需要向上代理构造链。但在这之前,根据初始化的第一条规则,我们必须先确保当前类的所有存储属性被初始化。

便利构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
class Point {
let x: Int
let y: Int

init(x: Int, y: Int) {
self.x = x
self.y = y
}
convenience init(z: Int) {
self.init(x: z, y: z) // 其实是调用指定构造器
}
}
let p1 = Point(z: 1)

便利构造器必须调用同一个类层级里的其他构造器,最终调用到指定构造器。

结构体也可以有“便利”构造器,但你不需要写出 “convenience” 关键字。实际上它们的 init 方法略有不同,因为你可以在一个 init 方法里调用其他的 init 方法,外部看起来跟类的那套机制很相似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Point {
let x: Int
let y: Int

init(x: Int, y: Int) {
self.x = x
self.y = y
}

init(z: Int) {
self.init(x: z, y: z)
}
}

var p1 = Point(z: 1)

必需构造器

如果你在一个类里面用 required 标记构造器。那你将不得不在类继承体系里每个层级都用上它。因为子类也必须实现这种构造器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Point {
let x: Int
let y: Int

required init(x: Int, y: Int) {
self.x = x
self.y = y
}
}

class NamedPoint: Point {
let label: String?

required init(x: Int, y: Int) {
self.label = nil

super.init(x: x, y: y)
}
}

let p1 = NamedPoint(x: 1, y: 1)

重写构造器

在 Swift 中,构造器默认是不被子类继承的。如果你想要为子类提供一个父类已经存在的构造器,你需要用到 override 关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Point {
let x: Int
let y: Int

init(x: Int, y: Int) {
self.x = x
self.y = y
}
}

class NamedPoint: Point {
let label: String?

override init(x: Int, y: Int) {
self.label = nil

super.init(x: x, y: y)
}
}

let p1 = NamedPoint(x: 1, y: 1)

构造器继承有两条规则:

  1. 如果你的子类没有定义任何指定构造器,它将自动继承其父类的所有指定构造器。

  2. 如果你的子类提供其父类的指定构造器的实现——无论是通过规则1继承得到,或者自定义实现的,那么它都会自动继承父类的所有便利构造器。

懒加载

在计算机编程中,懒加载是一种在创建对象,或者计算某个值,或者其他某些开销昂贵的处理过程中延迟执行的策略。这些过程只在第一次被需要的时候才会真正执行。

当一个属性只有在某些时刻才被需要,你可以用 lazy 关键字放在它前头。这样它就被排除出初始化过程。它的默认值一经请求,就会赋值。这个特性对于创建开销昂贵或者创建过程耗时的类型很有用。

用懒加载变量来优化代码十分方便,但它们只能用于结构体和类,也不能用于计算属性。

下面是一个睡美人的传说。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class SleepingBeauty {

init() {
print("zzz...沉睡中...")
sleep(2)
print("睡美人准备好了!")
}
}

class Castle {

var princess = SleepingBeauty()

init() {
print("城堡准备好了!")
}
}

print("一座新的城堡即将出现...")
let castle = Castle()

这段代码的输入如下,你可以看到公主睡了很久,还导致城堡被阻塞。

1
2
3
4
一座新的城堡即将出现...
zzz...沉睡中...
睡美人准备好了!
城堡准备好了!

现在,让我们通过添加 lazy 关键字来把事情提速吧,以便我们的英雄有时间去屠龙,同时我们的继续沉睡,直到剧情需要她醒来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class SleepingBeauty {

init() {
print("zzz...沉睡中...")
sleep(2)
print("睡美人准备好了!")
}
}

class Castle {

lazy var princess = SleepingBeauty()

init() {
print("城堡准备好了!")
}
}

print("一座新的城堡即将出现...")
let castle = Castle()
castle.princess

这下好多了!城堡会被立即创建,以便战斗立即开始,打败恶龙后王子唤醒公主。他们幸福地生活在一起,happy ending。

1
2
3
4
一座新的城堡即将出现...
城堡准备好了!
zzz...沉睡中...
睡美人准备好了!

用懒加载来避免可选型

还可以用懒加载来抹除对象中的可选型。

当你在处理 UIView 的派生类时这会很有用。举个例子,如果你的视图层级需要用到一个UILabel,通常你会需要声明这个视图的可选型属性或者隐式解包可选型属性。这种情况下,我们就可以利用 lazy 关键字消灭邪恶的可选型需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ViewController: UIViewController {

lazy var label: UILabel = UILabel(frame: .zero)

override func loadView() {
super.loadView()

self.view.addSubview(self.label)
}

override func viewDidLoad() {
super.viewDidLoad()

self.label.textColor = .black
self.label.font = UIFont.systemFont(ofSize: 16, weight: .bold)
}
}

使用懒加载闭包

基于存储属性做懒加载的主要好处是你的语句块只有那个属性发生读取操作时才会执行。看一下实践代码:

1
2
3
4
5
6
7
8
9
10
class ViewController: UIViewController {

lazy var label: UILabel = {
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 16, weight: .bold)
return label
}()
}

使用工厂进行懒加载初始化

工厂方法

如果你不喜欢上面那种闭包,你还可以把你的代码放进工厂方法,再通过懒加载变量来使用。就像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
class ViewController: UIViewController {

lazy var label: UILabel = self.createCustomLabel()

private func createCustomLabel() -> UILabel {
print("被调用")
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 16, weight: .bold)
return label
}
}

这里的工厂方法看起来就懒加载属性的私有构造器,还有进一步提升可复用性的空间。

静态工厂

如果你希望在应用的多个部分复用代码,那么把懒加载构造器的代码外包给静态工厂会是一种好的实践。举个例子,自定义 view 的初始化过程就很适合采用这个方式。况且,严格说来创建自定义视图不应该是 view controller 的任务,在这个例子中责任可以分担出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ViewController: UIViewController {

lazy var label: UILabel = UILabel.createCustomLabel()
}

extension UILabel {

static func createCustomLabel() -> UILabel {
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 16, weight: .bold)
return label
}
}

使用静态工厂属性或者方法你还能额外收获一些好处,比如实现缓存或者返回特定的子类型。

迭代器

什么是迭代器设计模式?

让你能够迭代一个合集里的元素。

提供一种方法,访问一个容器对象中的各个元素,而又不暴露该对象的内部细节。

简单来说,迭代器为你提供一个接口,让你可以遍历合集,不用关心这个合集是怎么实现的。下面是一个用字符串迭代器演示上面定义的粒子。

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
import Foundation

protocol StringIterator {
func next() -> String?
}

class ArrayStringIterator: StringIterator {

private let values: [String]
private var index: Int?

init(_ values: [String]) {
self.values = values
}

private func nextIndex(for index: Int?) -> Int? {
if let index = index, index < self.values.count - 1 {
return index + 1
}
if index == nil, !self.values.isEmpty {
return 0
}
return nil
}

func next() -> String? {
if let index = self.nextIndex(for: self.index) {
self.index = index
return self.values[index]
}
return nil
}
}


protocol Iterable {
func makeIterator() -> StringIterator
}

class DataArray: Iterable {

private var dataSource: [String]

init() {
self.dataSource = [" ", " ", " ", " ", " ", " ", " ", " ", " "]
}

func makeIterator() -> StringIterator {
return ArrayStringIterator(self.dataSource)
}
}

let data = DataArray()
let iterator = data.makeIterator()

while let next = iterator.next() {
print(next)
}

Swift中的自定义序列

Swift有一个内建的序列协议,可以帮助你创建迭代器。实现你自己的序列其实就是做一件事:通过创建自定义的迭代器隐藏内部的数据结构体。你只需要存储当前的索引,然后在每一次 next 函数被调用时返回下一个元素。

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
import Foundation

struct Emojis: Sequence {
let animals: [String]

func makeIterator() -> EmojiIterator {
return EmojiIterator(self.animals)
}
}

struct EmojiIterator: IteratorProtocol {

private let values: [String]
private var index: Int?

init(_ values: [String]) {
self.values = values
}

private func nextIndex(for index: Int?) -> Int? {
if let index = index, index < self.values.count - 1 {
return index + 1
}
if index == nil, !self.values.isEmpty {
return 0
}
return nil
}

mutating func next() -> String? {
if let index = self.nextIndex(for: self.index) {
self.index = index
return self.values[index]
}
return nil
}
}

let emojis = Emojis(animals: [" ", " ", " ", " ", " ", " ", " ", " ", " "])
for emoji in emojis {
print(emoji)
}

因此,Sequence 协议相当于前一个例子中的那个 Iterable 协议,而 IteratorProtocol 则类似于对应的 StringIterator,不过更像 Swift 风格,并且更通用。

Swift 中的自定义 Collection

Collection 是比序列更进一步的东西,里面的元素可以通过下标访问。

举个例子,如果你想消除可选型。想象一个分类收藏的机制,对于每个分类都有一组收藏。你需要处理空的情况和不存在的情况。利用自定义 Collection,你可以在你的自定义数据结构里隐藏额外的代码,只暴露一个干净的接口。

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class Favorites {

typealias FavoriteType = [String: [String]]

private(set) var list: FavoriteType

public static let shared = Favorites()

private init() {
self.list = FavoriteType()
}
}


extension Favorites: Collection {

typealias Index = FavoriteType.Index
typealias Element = FavoriteType.Element

var startIndex: Index {
return self.list.startIndex
}
var endIndex: Index {
return self.list.endIndex
}

subscript(index: Index) -> Iterator.Element {
return self.list[index]
}

func index(after i: Index) -> Index {
return self.list.index(after: i)
}
}

extension Favorites {

subscript(index: String) -> [String] {
return self.list[index] ?? []
}

func add(_ value: String, category: String) {
if var values = self.list[category] {
guard !values.contains(value) else {
return
}
values.append(value)
self.list[category] = values
}
else {
self.list[category] = [value]
}
}

func remove(_ value: String, category: String) {
guard var values = self.list[category] else {
return
}
values = values.filter { $0 == value }

if values.isEmpty {
self.list.removeValue(forKey: category)
}
else {
self.list[category] = values
}
}
}


Favorites.shared.add("apple", category: "fruits")
Favorites.shared.add("pear", category: "fruits")
Favorites.shared.add("apple", category: "fruits")

Favorites.shared["fruits"]

Favorites.shared.remove("apple", category: "fruits")
Favorites.shared.remove("pear", category: "fruits")
Favorites.shared.list

单例

单例之所以名声不好是因为他们共享全局可变状态。可以从程序的任何地方访问全局变量,所以当你的类使用这些变量时,它们是有状态的,不安全的,紧耦合并且难以调试的。

1
2
3
4
5
6
7
8
9
10
11
12
var global = 0

// 这里是别人写的代码
func square(_ x: Int) -> Int {
global = x
return x * x
}

global = 1;
var result = square(5)
result += global // 我们本以为global是1
print(result) //wtf 30,不应该是26吗?

利用单例来管理那些会持续整个应用生命周期的状态。Swift 默认是非线程安全的

何时使用单例类?

举个例子,UIApplication 最有可能是单例,因为应用本来就只能有一个实例,并且它需要存续到应用被关闭。这是一个绝佳的单例范例。
另外一个用例是 Logger 类。它也很适合用单例,因为开或者不开 logger 都不会影响到应用状态。没有别人会持有或者管理这个logger,只有你自己传递信息给它,因此状态不会被搞乱。 结论:控制台和 logger 类是很适合单例的场景。

1
Console.default.notice("Hello,我是一个单例!")

Apple的框架中有大量的单例场景,下面这份清单或许能给你一些灵感:

1
2
3
4
5
6
7
8
9
10
HTTPCookieStorage.shared
URLCredentialStorage.shared
URLSessionConfiguration.defaultURLSession.shared
FileManager.defaultBundle.mainUserDefaults.standard
NotificationCenter.defaultUIScreen.mainUIDevice.current
UIApplication.sharedMPMusicPlayerController.systemMusicPlayer
GKLocalPlayer.localPlayer()
SKPaymentQueue.default()
WCSession.default
CKContainer.default()

如果你想要把某样东西变成单例,先问自己几个问题:

  1. 有没有其他人可能持有,管理或者对这个东西负责?
  2. 是真的只有一个实例吗?
  3. 可以用全局状态变量代替吗?
  4. 是否存续在整个应用生命周期?
  5. 有没有其他选项?

Swift中如何创建单例?

1
2
3
4
5
6
7
8
9
class Singleton {

static let shared = Singleton()

private init() {
// 不要忘记把构造器变成私有
}
}
let singleton = Singleton.shared

如何消除单例?

单例最常用的替代选项是依赖注入。首先,你应该将单例方法抽象为协议,然后用单例作为这个协议的默认实现。现在,你可以将单例或者重构的对象注入合适的地方。

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
typealias DataCompletionBlock = (Data?) -> Void

// 1. 抽象要求的函数
protocol Session {
func make(request: URLRequest, completionHandler: @escaping DataCompletionBlock)
}

// 2. 使你的单例遵循协议
extension URLSession: Session {

func make(request: URLRequest, completionHandler: @escaping DataCompletionBlock) {
let task = self.dataTask(with: request) { data, _, _ in
completionHandler(data)
}
task.resume()
}
}

class ApiService {

var session: Session

// 3. 注入单例对象
init(session: Session = URLSession.shared) {
self.session = session
}

func load(_ request: URLRequest, completionHandler: @escaping DataCompletionBlock) {
self.session.make(request: request, completionHandler: completionHandler)
}
}

// 4. 创建mock对象

class MockedSession: Session {

func make(request: URLRequest, completionHandler: @escaping DataCompletionBlock) {
completionHandler("Mocked data response".data(using: .utf8))
}
}

// 5. 编写测试
func test() {
let api = ApiService(session: MockedSession())
let request = URLRequest(url: URL(string: "https://localhost/")!)
api.load(request) { data in
print(String(data: data!, encoding: .utf8)!)
}
}
test()

简单工厂

用 switch-case 来实现简单工厂

简单工厂模式的目标是封装一些经常变化的事情。

想象一个调色板应用,你需要根据设计师的日常习惯,比如最近的常用的颜色来改变调色板的默认颜色。如果要手工搜索和替换每一处创建的颜色实例会很麻烦。让我们在 Swift 中创建一个简单工厂,它基于给定的样式返回不同的颜色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ColorFactory {

enum Style {
case text
case background
}

func create(_ style: Style) -> UIColor {
switch style {
case .text:
return .black
case .background:
return .white
}
}
}

let factory = ColorFactory()
let textColor = factory.create(.text)
let backgroundColor = factory.create(.background)

简单工厂十分有用,特别是在对象的构造过程很复杂的情况下。你也可以定义一个协议,在它的实现中通过 switch-case 块返回各种不同的实例类型。

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
protocol Environment {
var identifier: String { get }
}

class DevEnvironment: Environment {
var identifier: String { return "dev" }
}

class LiveEnvironment: Environment {
var identifier: String { return "live" }
}

class EnvironmentFactory {

enum EnvType {
case dev
case live
}

func create(_ type: EnvType) -> Environment {
switch type {
case .dev:
return DevEnvironment()
case .live:
return LiveEnvironment()
}
}
}

let factory = EnvironmentFactory()
let dev = factory.create(.dev)
print(dev.identifier)
  • 几条关于简单工厂需要记住的东西
  1. 它通过分离构造和使用逻辑实现松散耦合。
  2. 它只是一种对于经常变化的事情的包装器。 ‍♂️
  3. Swift 里的简单工厂可以采用 enum 和 switch-case 来实现。
  4. 如果你想返回不同的对象,可以使用协议。(POP )
  5. 保持简单

这个设计模式将创建过程和实际使用分离开,将创建的责任移给特定的角色。如果创建过程有变化,只需要改动工厂即可,其他代码可以完全不变。