背景
最近在项目中遇到这样一个场景,在直播间的主播端有个功能区,里面是一些插件,这些插件之间在业务上存在互斥关系,也就是A处于开启状态时,B、C、D、E甚至是甲乙丙丁都不能打开。随着这块业务的增多,由于没有及时重构,导致互斥逻辑写的很是让人抓狂。
假设有A、B、C、D四个互斥模块,当A要启动时,需要
if (B is running) {
Log("B is running");
return;
}
if (C is running) {
Log("C is running");
return;
}
if (D is running) {
Log("D is running");
return;
}
A.run();
其它几个模块类似,在需要启动时先去判断与自己互斥的模块是否正在运行。
延伸
这只是四者之间,其实实际的场景中还会有E、F、G、H、甲、乙、丙、丁,他们之间存在一定的互斥关系。那么可以想象在每个模块需要启动时,将会有一连串的互斥判断,而且不同的情况还需要有不同的提示(比如上边例子中的Log),很不优雅!如何优化呢?
方法一:锁的思路
我想到了一个简单的模型,所有互斥模块间肯定至少存在一个互斥圈子,如下图:
我们自定义一个锁结构:
class Lock {
bool isLocking;
Map information;
}
每一个互斥圈子拥有一个Lock的实例lockA,这个圈子里任何一个模块启动时就锁住这个实例lockA,然后当这个圈子里的其它模块B要启动时,代码就变成了如下这样:
if (lockA.isLocking == true) {
Log(lockA.information...);
return;
}
B.run();
通过划分圈子,减少了判断逻辑,降低了模块之间的耦合度。这个模型在我遇到的场景中其实已经够用了,但是仔细想想,还是不够完美。比如模块A处于多个互斥圈子的话(也就意味着互斥关系比较负责,圈子比较多):
if (lockA.isLocking == true) {
Log(lockA.information...);
return;
}
if (lockB.isLocking == true) {
Log(lockB.information...);
return;
}
if (lockC.isLocking == true) {
Log(lockC.information...);
return;
}
A.run();
判断逻辑又多了起来,维护这样的关系容易出错,后期新加模块时的维护成本也很高。
方法二:互斥管理类作为中介
沿用 ~方法一~ 中互斥圈子的概念,我们抽象出来一个专门的互斥关系管理类,维护一个Map来记录互斥情况,Map的key用来区分不同互斥圈子。 类图如下:
伪代码实现如下:
class exclusionManager {
Map<string,Array> map;
public void add(IExclusive object) {
Array array = object.belongList();
foreach(string key in array) {
map[key].add(object);
}
}
public bool canRun(IExclusive object) {
Array array = object.belongList();
foreach(string key in array) {
foreach(IExclusive object in map[key]) {
if (object.isRunning == true)
return false;
}
}
return true;
}
}
class moduleA : IExclusive {
public bool isRunning() {
//TODO: 返回该模块是否正在运行的状态
return ...;
}
public Array belongList() {
//TODO:返回该模块所在的互斥圈子
return ["aCircle","bCircle","..."];
}
}
这样做降低模块间的耦合度,后期增删模块时也方便维护关系,算是一个不错的方案。
用Objective-C实现时的注意点
再回到我们Objective-C实现上,例子中exclusionManager实例对module实例的强引用其实是不合理的,我们可以把map中用来存储module实例的数组做成一个弱引用数组,具体实现方式有两种:
方式一:用不强引用的NSValue包装一下。
NSValue *value = [NSValue valueWithNonretainedObject:...];
[array addObject:value];
方式二:为NSMutableArray扩展一个Category,通过Core Foundation的API来生成一个不会retain元素的数组。
+ (id)mutableArrayUsingWeakReferencesWithCapacity:(NSUInteger)capacity {
CFArrayCallBacks callbacks = {0, NULL, NULL, CFCopyDescription, CFEqual};
// We create a weak reference array
return (id)CFBridgingRelease(CFArrayCreateMutable(0, capacity, &callbacks));
}
这样算是解决了互斥管理难题,代码也优雅了很多。
希望未来有新的想法可以继续改进,共勉!
参考:
https://stackoverflow.com/questions/9336288/nsarray-of-weak-references-unsafe-unretained-to-objects-under-arc/13351665
https://stackoverflow.com/questions/4692161/non-retaining-array-for-delegates/4692229