春节长假归来,相信大多数人都犯了节后综合征,那么就写一篇博文来收收心。没有心思干活的同学们,可以看看我的这篇文章,权当是散散心,找找感觉。
本篇文章主要介绍了关于上下文(_Context_)的一些概念,并提出了在设计上下文时应该考虑到的问题,最后通过一个实例来演示如何用Objective-C
实现一个上下文。相信通过阅读本篇文章,大家能够基本掌握软件设计中上下文的使用,并且,我相信,想象力如此丰富的你们,会将此推演到更高的境界。
那么,让我们从一些比较轻松的环节开始吧!
什么是上下文
既然我们要说上下文(_Context_),那么我们首先应该能够比较清晰的理解,什么是上下文,以及它适用于哪些场景。那么什么是上下文呢?上下文就是在某个特定的场景里,用于记录该场景特定状态的一种抽象。
要想解释清楚这样一种抽象的概念,还是比较困难的,不过在我们现实的开发中,其实也已或多或少用到过上下文。这些上下文通常都是以XXXContext
来命名,并且通常都有明确的区间分割,比如下面使用UIKit
进行绘图的代码:
1 | CGImageRef flip(CGImageRef im) { |
上面的代码中,ImageContext
便是一种上下文,它会记录下在Begin
和End
区间中的一些信息,并影响这其间其他方法的行为。在广为人知的 GoF 设计模式中,解析器模式(_Interpreter_)的一般实现里,也会有上下文,用于记录解析过程的中间状态。类似的例子还有很多,这里就不一一列出了。
那么,接下来我们来看看,如果要去实现一个上下文,需要注意哪些问题。
嵌套上下文
首先我们需要注意的是,一个健全的上下文必须是需要支持嵌套的,比如这样一段代码片段:
1 | BeginXXContext(); |
理想的情况下,我们在 区间A 里所设定的信息应该是不能影响到 区间B 的,因为 区间B 是一个独立的上下文。这样的设计比起上下文行为继承,我觉得会更加合理,如果 区间B 继承 区间A 上下文的信息,会导致一些不可预料的后果。比如,整个 区间B 是在另一个子函数里,那么就无法确保这个子函数对外能有一个确定的行为表现了。
那么,我们如何来实现这样的需求呢?其实很简单,我们确保在 区间A 里获取到的上下文与在 区间B 里获取到的上下文是两个对象即可。这样就需要我们在抽象时,考虑父子关系,下面是简略的代码实现:
1 | @implementation XXContext { |
上面的代码还是非常简陋的,未做任何异常处理,但这里只是提供出实现的思路,有兴趣的朋友,可以自己再细化下。
好的,我们解决了嵌套的问题,那么接下来要谈谈线程安全了。
线程安全问题
上下文的实现中,非常重要的一环就是要考虑上下文的线程安全。考虑一下,上一节代码实现的上下文,在如下的代码中,表现会是怎样:
1 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ |
很明显的可以看出来,如果是之前的实现,在面对这种多线程并发操作的情况下,会有不可预料的结果。上面代码里,区间A 或 区间B 里获取到的[XXContext current]
都是不确定的,因为无法保证代码的执行顺序。那么,我们如果来解决这样的问题呢?
换个角度来思考下,我们可以确保的是,begin
和end
中的这段代码肯定是在一个线程里,或者说,上下文是线程相关的,一个上下文针对一个线程。所以下面这段代码是不对的(_或者说是不允许的_):
1 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ |
这样分析下来,我们应该能够很容易的想到一个概念:线程本地存储,也就是所谓的TLS(_Thread Local Storage_),顾名思义,就是可以针对线程存储一些信息,并且存储的这些信息只有在该线程才可以访问到,与其他线程是隔离的。
在Objective-C
中,TLS 的使用非常简单,NSThread
中有个threadDictionary
属性,用于存储信息,所以,我们可以将上面的实现改成如下这样:
1 | @implementation XXContext { |
上面的改动其实很简单,也就是把原先的sXXContext
静态变量,替换成[NSThread currentThread].threadDictionary[@"xx-ctx"]
这样一种线程相关的存储方式。经过这样的改造,我们可以轻松面对本节开头的那段代码了,所以,接下来我们可以做一些更有意义的事情。
实现举例 - 事件总线
前先时间,在微信上看到一篇关于蘑菇街组件化的文章(_怎么又是蘑菇街?_),里面讲到了它们的MGJRouter,用于模块间的解耦。这个库主要都是主动去取另一个模块的数据,但模块间除了这种主动的行为,有时还会需要监听另一个模块的特定事件,这种被动的行为,在Objective-C
中有Notification
可以使用,但,Notification
太弱,类型太弱,需要太多的约定。
所以,我们有必要自己再造一个轮子,事件总线(_Event Bus_),更进一步的将模块解耦。这个库我已经放到了 GitHub:
https://github.com/prinsun/MKXEventBus
这个库支持这样一些特性:
- 强类型事件发布
- 事件支持合并配置,在符合条件的情况下,多个事件会自动合并成一个事件发布
- 事件订阅支持
block
也支持selector
- 事件订阅支持指定回调的
dispatch_queue
(_这里用到了上下文_) - 事件订阅者通过弱引用自动回收
具体实现可以看代码,也欢迎大家来发现问题,并贡献代码,下面是一般的使用示例:
1 | // 发布事件 |
上面代码中的beginSubscribe
和endSubscribe
便是一个典型的上下文设计,实现方式也与本文中所描述类似,感兴趣的可以去瞅瞅代码。
OK,那么这个栗子就举到这里吧!
接下来该做什么
看到了这里,我觉得大家可以再深入的去思考下,上下文除了这些简短生命周期的实现外,其实还有很多生命周期是非常长的。比如应用上下文、服务上下文、账户上下文等,上下文的核心设计理念在于隔离存储,这是一个非常有用,也非常有意思的东西。
所以,接下来发挥你的想象,用上下文去创造奇迹吧!