Hulk' Den

in-depth thinking and keep moving.

对各模块间的互斥关系管理的小思考

背景

最近在项目中遇到这样一个场景,在直播间的主播端有个功能区,里面是一些插件,这些插件之间在业务上存在互斥关系,也就是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

最近的文章

iOS触控响应中那些没有细想过的问题

写在前面:这篇文章的重点在于讨论我想到的一些关于iOS触控响应这块容易忽略的问题,限于篇幅就不对基本的iOS触控响应知识做说明了。一、iOS给我们提供的响应触控的方式1、The touch-delivery methods定义在UIResponder中的以下方法,通过重写可以实现自定义的触控响应逻辑。- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;- (...…

iOS继续阅读
更早的文章

类似逐帧动画或者tableView列表中有大量图片展示需求的场景我们可以做哪些优化?

背景最近公司在调研后打算替换掉老的识别库,据说新库在识别率上做的更好一些。但是在集成后实际压测发现并没有什么提升。(由于分析过程可能牵扯到公司业务,所以此处省略分析发现问题的过程,见谅)……….那么思路捋到这里我们可以总结出来两个至关重要的优化点: 避免频繁的解压资源包操作,每次解压绝对是件耗费的操作。这个是比较低级但在俯瞰全局时容易忽略的点,本篇不做讨论。 逐帧动画的图片数量比较大质量比较高,能否把PNG/JPEG图片解码成位图后保存到本地,避免CPU进行重复频繁的解码图片操作?基于...…

iOS | 优化继续阅读