通过观察者模式实现简单好用的iOS App构架

在工作中我开发过的APP都采用了相同的构架,这个构架非常简单易用,在团队写作中带来了诸多便利,我决定写出来分享。

  • 数据和界面分离,在宏观层面上也遵从了MVC的思想
  • 提高代码的可维护性和重用性
  • 在多个App之间低成本快速共享业务
  • 多个App项目组的开发人员横向流动需要快速上手新App的代码

这个简单的构架主要思想是观察者模式,因为不能开源公司产品代码,所以我采用Swift重写构架核心,尽管Swift实现与Objective-C实现存在差异,不过核心思想是相通的,很容易就可以用Objective-C重写一个轮子出来😊😊😊

观察者模式介绍

基础知识

观察者模式是软件设计模式中的一种,这个设计模式由两部分组成,分别是订阅者发布者。当发布者的状态发生变化时,所有订阅者都会得到通知。这种一对多的依赖关系,最适合做业务数据与用户界面的分离,当业务数据发生变化,用户界面得到通知,更新显示;而当用户界面需要修改时,只需要重构界面,业务数据不需要发生变化。

实现要点

  • 观察者被观察者之间的互动不能做成直接调用,这样会导致两者高度耦合
  • 下面的UML图清晰的展示了观察者模式的实现形式,本文也会按照这种形式实现观察者模式

ObserverPatternUML

用Swift实现观察者模式

得益于Swift面向协议的特点,通过基于protocol的实现,可以给客户端提供最大的灵活性。下面给出最基础的观察者模式的Swift实现。

接口定义

先声明两个协议,用于提供最基础的接口

  • 被观察者提供注册接口、注销接口、通知观察者的接口
  • 观察者提供接收通知的接口
1
2
3
4
5
6
7
8
9
protocol Observer: class {
func notify(param:AnyObject?)
}

protocol Observable: class {
func addObserver(observer: Observer)
func removeObserver(observer: Observer)
func notifyObservers(param:AnyObject?)
}

实现接口

为简单起见,用数组来管理观察者,且观察者接收到通知后仅打印出通知内容。代码非常简短清晰,不必注释。

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
class Poster: Observable {
var observers = [Observer]()

func addObserver(observer: Observer) {
observers.append(observer)
}

func removeObserver(observer: Observer) {
for i in observers.indices {
if observers[i] === observer {
observers.removeAtIndex(i)
break
}
}
}

func notifyObservers(param:AnyObject?) {
for observer in observers {
observer.notify(param)
}
}
}

class Subscriber: Observer {

func notify(param:AnyObject?) {
print("\(String(self.dynamicType)) been notified, param = \(param as! String)")
}
}

class AnotherSubscriber: Observer{

func notify(param:AnyObject?) {
print("\(String(self.dynamicType)) been notified, param = \(param as! String)")
}
}

使用示例

使用观察者模式也非常简单,需要注意的是,当对某一事件不再感兴趣时,应该主动结束观察。注册和注销行为最好成对调用,这是基本的正确使用习惯。

1
2
3
4
5
6
7
8
9
10
11
let poster = Poster()
let subscriber = Subscriber()
let anotherSubscriber = AnotherSubscriber()

poster.addObserver(subscriber)
poster.addObserver(anotherSubscriber)

poster.notifyObservers("Hello Oberservers")

poster.removeObserver(anotherSubscriber)
poster.removeObserver(subscriber)
1
2
Subscriber been notified, param = Hello Oberservers
AnotherSubscriber been notified, param = Hello Oberservers

通过上面的代码可以看出通过Swift实现观察者模式非常简单。在这个最基础的观察者模式实例中,也体现了使用观察者模式需要注意的两点细节:

  • 实现时,要确保观察者被观察者之间松耦合
  • 使用时,注册和注销要成对调用

上面的代码仅仅是对观察者模式实现和使用进行了说明,为了满足实际项目的使用,接下来还要对以上原始代码进行改造,增强可用性。

观察者模式跟UIViewController结合

在iOS开发中已有大量观察者模式的应用,最为大家熟悉的就是NotificationCenter和KVO。通过NotificationCenter我们可以注册我们感兴趣的通知,也可以发出通知来来触发事件函数。NotificationCenter应用非常广泛,但是也很容易被滥用,导致代码难以维护,满天的通知到处飞。

所以要尽量避免这样的情况。

回顾上一节中实现的观察者模式,有两个明显的问题:

  • 被观察者通知观察者时,遍历全部观察者发送notify消息,这在实际中会引起我们不希望出现的惊群现象
  • 观察者都统一实现了notify方法,实际应用中,我们希望事件触发的方法更加灵活多变

现在对朴素的实现进行修改,使之可以避免上述问题。同时,在改造之前,需要明确一点目标,被观察者应该被设计成为数据层,即被观察者是MVC中的M角色。

解决灵活性

为了解决观察者触发事件的灵活性问题,对Observer protocol增加一个Selector参数:

1
2
3
protocol Observer: class {
func notify(action:Selector?, param:AnyObject?)
}

解决通知指定目标的问题

被观察者在通知观察者时,增加一个机制只通知指定的观察者,这里通过改造Observable protocol中的notifyOberservers方法,增加target来实现我们的目的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation

class BasePoster: Observable {

var observers = [Observer]()

// addObserver方法略去...
// removeObserver方法略去...

func notifyOberservers(target: AnyObject?, action:Selector = "", param:AnyObject? = nil) {
if nil == target {
for observer in observers {
observer.notify(action, param: param)
}
} else {
for observer in observers {
if observer === target {
observer.notify(action, param: param)
}
}
}
}
}

数据层抽象

为了抽象出数据层,把与一类业务相关的数据行为(诸如网络请求、数据库增删改查、数据转换加工等等),都统一放在一个Module中。这个Module代表了一类业务的数据层,显然在一个应用内相同的业务不应该有多个数据层,所以这里通过单例实现Module,并继承自BasePoster以获得被观察者的能力——通知观察者响应事件。在Module中,把数据相关方法暴露出去,使用者就可以直接调用。

以下代码实现一个SampleModule,以获取知乎日报首页列表数据为例,暴露requestLatestNews:方法给ViewController使用。同时可以看到通过successBlockfailureBlock两个closure来触发通知行为。根据Alamofire的成功或失败,调用不同的closure,把需要通知到的观察者、需要发送的消息、需要传递的参数,都一并丢给观察者模式。

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

class SampleModule: BasePoster {

static let sharedInstance = SampleModule()
var latestNews:LatestNewsModel?

private override init() {
super.init()
}

func requestLatestNews(observer:YAZBaseViewController? = nil) {

let successBlock = {(response:NSDictionary) in
self.latestNews = LatestNewsModel(response)
self.notifyOberservers(observer, action: "requestLatestNewsDidSuccess:", param: response)
}

let failureBlock = {(error:NSDictionary) in

self.notifyOberservers(observer, action: "requestLatestNewsDidFalure:", param: error)
}

Alamofire.request(.GET, "http://news-at.zhihu.com/api/4/news/latest", parameters: nil)
.responseJSON { response in

if response.result.isSuccess {
let jsonDic = response.result.value as! NSDictionary
successBlock(jsonDic)

} else {
let httpError: NSError = response.result.error!
let statusCode = httpError.code
let error:NSDictionary = ["error" : httpError,"statusCode" : statusCode]
failureBlock(error)
}
}
}
}

为了方便使用,把响应观察者模式的notify方法实现在ViewController的基类中,子类专注于处理Module中制定的Selector即可,也省去了重复写notify的麻烦。

1
2
3
4
5
6
7
8
9
10
import UIKit

class BaseViewController: UIViewController, Observer {

func notify(action: Selector?, param: AnyObject?) {
if self.respondsToSelector(action!) {
self.performSelector(action!, withObject: param)
}
}
}

使用示例

使用方法:

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
class SampleViewController: BaseViewController {

override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)

SampleModule.sharedInstance.addObserver(self)
SampleModule.sharedInstance.requestLatestNews()

//only notify this observer
//SampleModule.sharedInstance.requestLatestNews(self)
}

override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)

SampleModule.sharedInstance.removeObserver(self)
}

func requestLatestNewsDidSuccess(param: AnyObject) {
print("requestLatestNewsDidSuccess")
}

func requestLatestNewsDidFalure(param: AnyObject) {
print("requestLatestNewsDidFalure")
}
}

通过ViewController的生命周期方法来注册/注销,这也充分利用了iOS开发的特点。
从上面的使用方法来看,这个构架是非常简单易用的,只要理解了整个构架的本质——观察者模式,就可以迅速上手业务开发。
这里只是给出了构架的基本思路,实际使用中,还可以做很多改善工作使得这个构架更加好用。
例如错误码处理、统一的数据转换等等,都可以把这些类似于工具性质的代码交给构架中的Poster来完成,让ViewController更加精简,更方便维护。

总结

尽管只用到两个最基础的设计模式,观察者模式和单例模式,但是这个构架生命力非常强大,绝对部分普通的APP都可以胜任,可谓是万金油工具。
但是这个构架也存在一定的弊端,例如当业务越来越多时,意味着大量的Module单例会一直存在于内存中。所以使用中要避免太多、太零碎的Module,应该对业务进行归纳和抽象,避免Module数量太多而每个都只做零散工作的情况。

由于把业务相关的所有数据行为都放到了Module,封闭性比较好,所在业务移植时非常方便。整个Module以及相关类一起打包拿走就可以用,这是我觉得非常方便的一点。

如果我的这篇文章对你有帮助,你可以用微信扫下面的二维码,请我喝杯☕️:)

Donation