我们知道delegate在OC中是比较经常使用的代理模式。主要作用是将某对象的行为属性传递到另一个对象。以及绑定代理都用用weak弱引用。

1
2
3
4
5
6
7
8
9
10
11
protocol MyClassDelegate: class {
func doFoo()
}

class MyClass {
weak var delegate: MyClassDelegate?

func foo() {
delegate?.doFoo()
}
}

问题:假如我不用埋点方法替换foo等,在用户调用触发doFoo()方法,可以怎样做日志行为分析?前提是delegate又不能强制破坏或替换。
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
class MulticastDelegate<T> {

private let delegates: NSHashTable<AnyObject> = NSHashTable.weakObjects()

func add(_ delegate: T) {
delegates.add(delegate as AnyObject)
}

func remove(_ delegateToRemove: T) {
for delegate in delegates.allObjects.reversed() {
if delegate === delegateToRemove as AnyObject {
delegates.remove(delegate)
}
}
}

func invoke(_ invocation: (T) -> Void) {
for delegate in delegates.allObjects.reversed() {
invocation(delegate as! T)
}
}
}

class MyClassMulticastDelegate: MyClassDelegate {

private let multicast = MulticastDelegate<MyClassDelegate>()

init(_ delegates: [MyClassDelegate]) {
delegates.forEach(multicast.add)
}

func doFoo() {
multicast.invoke { $0.doFoo() } //调用代理对象的doFoo方法
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class AnalyticsEngine {}

extension AnalyticsEngine: MyClassDelegate {
func doFoo() {
print("Track foo event")
}
}

let logger = Logger()
let analyticsEngine = AnalyticsEngine()
let delegate = MyClassMulticastDelegate([logger, analyticsEngine])

let myClass = MyClass()
myClass.delegate = delegate

myClass.foo()

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。
在MVC 框架中,其中C(控制器)就是 M(模型)和 V(视图)的中介者。我们接下来用一个搜索视图来展示中介者模式:

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
protocol HistoryView {
var isHidden: Bool { get set }
func setHistory(_ history: [String])
}

protocol HistoryRepository {
var history: [String] { get }
func addSearchTerm(_ term: String)
}

class SearchHistoryMediator: NSObject {
private let searchBar: UISearchBar
private var historyView: HistoryView
private var observasion: NSKeyValueObservation?
private let historyRepository: HistoryRepository

init(searchBar: UISearchBar, historyView: HistoryView, historyRepository: HistoryRepository) {
self.searchBar = searchBar
self.historyView = historyView
self.historyRepository = historyRepository
super.init()

self.historyView.isHidden = true
self.historyView.setHistory(historyRepository.history)

searchBar.delegate = self
observasion = searchBar.observe(\.text) { [weak self] (searchBar, _) in
self?.historyView.isHidden = searchBar.text?.isEmpty ?? false
}
}
}

extension SearchHistoryMediator: UISearchBarDelegate {

func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
historyView.isHidden = false
}

func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
historyView.isHidden = true
}

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
historyView.isHidden = true
if let text = searchBar.text, !text.isEmpty {
historyRepository.addSearchTerm(text)
historyView.setHistory(historyRepository.history)
searchBar.text = nil
}
searchBar.resignFirstResponder()
}

func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
historyView.isHidden = true
searchBar.resignFirstResponder()
}
}

//在控制器中使用中介者
private(set) lazy var mediator: SearchHistoryMediator = {
return SearchHistoryMediator(searchBar: searchBar, historyView: historyView, historyRepository: historyRepository)
}()

override func viewDidLoad() {
super.viewDidLoad()
_ = mediator
}

优点:降低了类的复杂度,将一对多转化成了一对一。各个类之间的解耦,减少对象之间的关联性,让每一个对象都能够独立

有限状态自动机(FSM “finite state machine” 或者FSA “finite state automaton” )是为研究有限内存的计算过程和某些语言类而抽象出的一种计算模型。有限状态自动机拥有有限数量的状态,每个状态可以迁移到零个或多个状态,输入字串决定执行哪个状态的迁移。有限状态自动机可以表示为一个有向图。有限状态自动机是自动机理论的研究对象。
我们来看以下片段代码:

1
2
3
4
5
6
7
class ViewController {
@IBOutlet var tableView: UITableView!
@IBOutlet var errorLabel: UILabel!
@IBOutlet var emptyStateLabel: UILabel!
@IBOutlet var activityIndicator: UIActivityIndicatorView!
// Some implementation
}

我们得出4种状态,且每种独立存在:

  • 显示数据
  • 是否在加载数据中
  • 是否显示错误
  • 空状态
    我们在控制器中定义该枚举:
    1
    2
    3
    4
    5
    6
    7
    8
    extension ViewController {
    enum State {
    case loading
    case showingData([Item])
    case empty
    case error(Error)
    }
    }

Swift能实现多继承吗?

虽然多继承在C++中可以实现,但在Swift中,是不能够实现多继承的。但一个class可以遵循多个协议,只能继承一个父类。但值类型比如 struct , enum 就不存在继承这么一说,但还是可以遵循多个协议的。
Swift只支持协议多继承
如:

1
2
3
4
5
6
7
protocol ChildAB:ChildA,ChildB {

}
class MyClass: ChildAB {
func method() {
}
}

struct或class可遵循多个协议实现假“多继承”
比如以下片段给UIView扩展实现多个协议的方法:

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
protocol Blinkable {
func blink()
}

extension Blinkable where Self: UIView {
func blink() {
alpha = 1

UIView.animate(
withDuration: 0.5,
delay: 0.25,
options: [.repeat, .autoreverse],
animations: {
self.alpha = 0
})
}
}

// MARK: - Scalable

protocol Scalable {
func scale()
}

extension Scalable where Self: UIView {
func scale() {
transform = .identity

UIView.animate(
withDuration: 0.5,
delay: 0.25,
options: [.repeat, .autoreverse],
animations: {
self.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
})
}
}

这样,我们申明一个View的实例,那么可以调用blink,scale 等方法。
1
2
3
extension UIView: Scalable, Blinkable {}
aView.blink()
aView.scale()

Swift与OC最大的区别就是新增了值类型Struct, let变量修饰后无法再次被赋值实现真正的immutable,也就是说一旦赋值不能再次修改。虽然OC中定义NSArray *arr 也是一个immutable,但是arr对象指针指向还可以被再次重新赋值。OC是一门运行时语言,arr所指向的变量都是运行时获取对象。OC中使用immutable不直接等同于线程安全。Objective-C中存在深拷贝、浅拷贝,即使你调用一个[NSMutableArray Copy]得到的NSArray也不代表这个数组中的对象都是经过深拷贝。

我们先看一下如下代码会造成线程安全问题?:

1
2
3
if (self.xxx) {
[self.dict setObject:@"ah" forKey:self.xxx];
}

在单线程模式下肯定是没啥问题,但在多线程情况下比如线程A,线程B两者都同时进入了if条件,当A正要执行set操作,B已经对self.dict = nil。那么线程A肯定是不能设置成功的。
还有另外一种情况,当我们的数据资源比如集合,多线程对资源进行争夺。如添加和移除操作。为了保证最终结果的准确性,我们通常会给操作代码加锁。
1
2
3
OSSpinLock(&_lock);
[self.array addObject:@"data1"];
OSSpinUnlock(&_lock);

一个数组,要对元素进行去重操作有两种方式,要么重写equals方法做比较,要么利用Hash值做比较。
假设对象为点Point

1
2
3
4
struct Point {
let x: Int
let y: Int
}

让对象实现equals方法

1
2
3
4
5
extension Point: Equatable {
static func == (lhs: Point, rhs: Point) -> Bool {
return lhs.x == rhs.x && lhs.y == rhs.y
}
}

接着,我们对Array做扩展,新增去重方法并返回新数组:

1
2
3
4
5
6
7
8
9
10
11
extension Array where Element: Equatable {
func uniqueElements() -> [Element] {
var out = [Element]()
for element in self {
if !out.contains(element) {
out.append(element)
}
}
return out
}
}

用Hash值方式比较对象

把元素对象放至Set无序集合中,由于Set根据Hash值来存储,可存储唯一Hash值对象保证不重复。准备一个空数组接收。

1
extension Point: Hashable {}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
extension Array where Element: Hashable {
func uniqueElements() -> [Element] {
var seen = Set<Element>()
var out = [Element]()

for element in self {
if !seen.contains(element) {
out.append(element)
seen.insert(element)
}
}

return out
}
}

将代码用compactMap改进一下:遍历每一个元素并返回不为nil的元素。我们根据元素是否存在Set集合中为条件,当存在集合中时返回nil。
1
2
3
4
5
6
7
8
9
10
11
12
13
extension Array where Element: Hashable {
func uniqueElements() -> [Element] {
var seen = Set<Element>()

return self.compactMap { element in
guard !seen.contains(element)
else { return nil }

seen.insert(element)
return element
}
}
}

当我们不考虑原元素对象顺序的情况下,可以快速对元素去重
1
2
3
4
5
6
extension Array where Element: Hashable {
func unsortedUniqueElements() -> [Element] {
let set = Set(self)
return Array(set)
}
}

我们都想写得一手好代码。这包含防止内存泄露,当在写闭包时业务如果需要用到self,我们会使用[weak self] 修饰。但为什么需要弱捕获呢?是否任何情况下在闭包里都要用weak? 接下来我们将用捕获列表来回答这几个问题。我们将讲解到不同的捕获,以及什么时候用合适的捕获去处理问题。

什么是捕获列表

我们先看以下代码片段,调用闭包后显示的结果:

1
2
3
4
5
6
7
8
var name = "qiu"
var appendToName = { (string: String) -> String in
return name.appending(string)
}

let one = appendToName("gaoying") //qiugaoying
name = "liu"
let two = appendToName("gaoying") //liugaoying

闭包可对同一作用域范围内的属性做引用,也就是闭包内持有的属性在同一上下文作用域内变更受影响。我们再观察以下代码:

1
2
3
4
5
6
7
8
var name = "qiu"
var appendToName = { [name] (string: String) -> String in
return name.appending(string)
}

let one = appendToName("gaoying") //qiugaoying
name = "liu"
let two = appendToName("gaoying") //qiugaoying

这时输出的结果都为qiugaoying。[name] 将属性name放在闭包的捕获列表,明确地告诉闭包强持有闭包首次调用时的name值。如果要在闭包捕获列表内捕获多个值,则用逗号隔开,如[property, anotherProperty] in 方式。

在我们设计网络层响应结果的时候,Result是大家用的比较多的。和Optional有点类似,它是用来表述返回类型是成功或者失败。在Swift5.0中,Swift团队已经把Result加入到了标准库。这篇文章将讲解到何时去使用Result,以及怎样使用Result。
上篇文章,我们讲到Swift异常处理,在处理时候用do{}catch{}捕捉异常,以及在方法名后通过throws关键字将异常往上层抛。和抛错误不同的是,在代码中用Result作为返回类型可以达到我们需要的效果。更大的不同是,Result可以用于在异步处理时候,如鱼得水般地优雅处理成功与失败的回调返回。在讲解之前,我们先看一下以下片段1代码:

1
2
3
4
5
6
7
8
9
10
func loadData(from url: URL, completion:@escaping (Data?) -> Void) throws {
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
throw error
}
if let data = data {
completion(data)
}
}.resume()
}

按照以往的方式,当error有异常时候,我们进行抛出异常,然后方法上也加上throws。假设以上代码编译通过,那么在调用的时候代码片段如下:
1
2
3
4
5
6
7
8
9
do {
try loadData(from: aURL) { data in
print("fetched data")
}

print("This will be executed before any data is fetched from the network.") //这行代码会被先执行。由于loadData方法是异步处理请求数据,就算发生错误,error抛出存在一定的执行时长。这就违背了异常捕捉而中止的性质了,也谈不上异常捕捉。
} catch {
print(error)
}

在swift2.0, 苹果在swift中就介绍了throws 关键字。这方便开发者在写代码时候保证清晰的思路以及在执行时可能出现的错误做异常处理。可做异常捕捉或者忽略异常。这篇文章将详细讲解 throws 关键字,以及在代码中如何去处理异常。

处理异常

如果你在Swift中使用过JSONDecoder,就知道在编写代码时候可抛出异常。让我们来看以下代码片段:

1
2
3
4
5
6
7
8
9
var greeting = "Hello, playground"

do {
let data = greeting.data(using: .utf8)!
let decoder = JSONDecoder()
let string = try decoder.decode(String.self, from: data)
} catch { error
print(error)
}

在Playground 执行代码片段,发现控制台输出Swift.DecodingError异常错误,异常被catch捕捉到了。代码中,我们用do {} catch {} 方式把可能发生的代码一次用try 方式写在do{}里面。当捕捉到一次立马进入catch{error }段。catch将捕捉到Error, 在catch时候,我们也可以捕捉精确的异常进行处理。

在App开发中,数据层往往会做本地缓存,以便在网络不好的情况下能够先显示本地缓存数据。接下来将讲解到如何设计本地数据源中间件来实现业务数据的读取。在讲解之前我们会先了解一下简单泛型在Swift中的应用,结合associated types, 你也可以在协议中使用泛型。

在介绍之前,我刚提到我想要对泛型数据存储在本地,然后还需要一个远程数据接口API提供数据以便在本地数据没有的情况下调用。假设我需要用户信息数据:

1
2
3
4
5
6
7
let localDataStore = UserDataStore()
let remoteDataStore = UserApi()
let dataStore = CacheBackedDataStore(localDataStore, remoteDataStore)

dataStore.fetch(userID) { result in
// handle result
}