实际开发中的模块化开发 - 模块间通讯(以直播间为例)

news2024/11/24 16:07:07

实际开发中的模块化开发 - 模块管理(以直播间为例)-CSDN博客

引言

在之前的博客中,我们讨论了模块化开发的概念、使用场景及其优势,并通过简单的案例实现了一个基础的模块化结构。我们创建了用户卡片模块和礼物展示模块,将相关业务代码分散到对应的模块中,并且随着直播间的创建和销毁,它们的生命周期方法也得到了良好的管理。

然而,在实际开发中,模块与模块之间往往并非完全独立。例如,在用户卡片模块中可能会有一个送礼按钮。当用户点击该按钮时,需要展示礼物,而礼物展示的功能代码存在于礼物展示模块内。这时我们该如何处理呢?一种方法是在礼物展示模块中定义一个 public 修饰的方法,并在用户卡片模块中获取到礼物展示模块,在点击送礼按钮时调用该方法。虽然这样做可以实现功能,但模块之间的直接引用似乎已经破坏了模块的独立性。

为了避免这种直接依赖,我们需要一种更为解耦的方式来进行模块间的通信。本篇博客将深入探讨如何通过消息通信的方式,在模块化开发中实现模块间的解耦与协作,从而提高代码的可维护性与扩展性。

结构调整

为了让消息总线和模块管理可以同时工作,我们首先需要调整一下项目结构,创建一个PHRoomControlCenter类负责管理模块管理对象和消息总线对象,之后其它的整体功能也将都由这个控制中台来进行管理,那么我们就将不会在直接使用PHModuleManager。

PHRoomControlCenter类的内容也很简单代码如下:

public class PHRoomControlCenter: NSObject {
    
    /// 所属控制器
    private(set) public weak var ownerController: UIViewController!
    /// 模块管理器
    private(set) public var moduleManager: PHModuleManager!
    /// 消息总线
    private(set) public var messageBusManager: PHMessageBusManager!
    
    /// 初始化模块管理控制中台
    /// - Parameter :
    /// - modules : 模块数组
    /// - ownerController : 所属控制器
    /// - Returns: 模块管理控制中台
    public init(ownerController: UIViewController,modules: [PHModuleModel]) {
        super.init()
        self.ownerController = ownerController
        self.moduleManager = PHModuleManager(controlCenter: self)
        self.messageBusManager = PHMessageBusManager(controlCenter: self)
        loadModules(modules: modules)
    }
    
    /// 加载所有模块
    /// - Parameter modules: 模块模型数组
    func loadModules(modules: [PHModuleModel]) {
        moduleManager.loadModules(modules: modules)
    }
    
    /// 模块加载完成
    public func moduleDidLoad() {
        moduleManager.allModuleDidLoad()
    }
    
    /// 卸载所有模块
    public func unloadModules() {
        moduleManager.removeAllModules()
    }
}

主要就是初始化模块管理对象,和消息总线管理对象,以及定义了一些模块管理类的同名方法,主要为了方便模块生命周期的管理。

模块调整

为了接收和处理消息,模块的抽象父类也需要进行相应的调整。

在创建模块管理类中定义个控制中台,在初始化时传递模块管理对象中。

在模块注册时,同时注册该模块所以监听的消息。

public class PHModuleManager: NSObject {
    
    /// 控制中台
    private(set) weak var controlCenter: PHRoomControlCenter!
    /// 已注册 模块总表
    private(set) var moduleTable: [String:PHModule] = [:]
    
    
    /// 创建模块管理器
    /// - Parameter controlCenter: 控制中台
    /// - Returns: 模块管理器
    init(controlCenter: PHRoomControlCenter) {
        super.init()
        self.controlCenter = controlCenter
    }
        /// 注册模块
    /// - Parameter module: 模块模型
    func registerModule(module: PHModuleModel) {
        guard let moduleIdentifier = module.moduleIdentifier else {
            assert(false, "模块标识不能为空")
            return
        }
        guard moduleTable[moduleIdentifier] == nil else {
            assert(false, "模块标识已存在")
            return
        }
        guard let moduleClassString = module.moduleClassString else {
            assert(false, "模块类名不能为空")
            return
        }
        guard let moduleDescription = module.moduleDescription else {
            assert(false, "模块描述不能为空")
            return
        }
        let className = "ShortVideo.\(moduleClassString)"
        guard let moduleClass = NSClassFromString(className) as? PHModule.Type else {
            assert(false, "模块类不存在")
            return
        }
        let moduleInstance = moduleClass.init(moduleIdentifier: moduleIdentifier, moduleDescription: moduleDescription, controlCenter: controlCenter, isLoaded: false)
        moduleInstance.moduleRun()
        moduleTable[moduleIdentifier] = moduleInstance
        
        /// 注册消息监听
        for messageIdentifier in module.receiverMessage {
            controlCenter.messageBusManager.registerMessageObserver(messageIdentifier: messageIdentifier, messageObserver: moduleInstance, messageSelector: #selector(moduleInstance.receiveMessage(message:)))
        }
    }
    .....

}

模块的抽象父类也需要添加相应的方法用来接收和发送消息。

    /// 接收到消息
    /// - Parameters:
    /// - message: 消息
     @objc open func receiveMessage(message: PHMessage) {
        // 子类实现
    }
    
    /// 发送消息
    /// - Parameters:
    /// - messageIdentifier: 消息标识
    /// - messageData: 消息数据
    open func  postMessage(messageIdentifier: String, messageData: Any?) {
        controlCenter.messageBusManager.postMessage(messageIdentifier: messageIdentifier, messageData: messageData)
    }

消息通讯

接下来我们就来开始实现模块间的消息通讯,除了发送消息之外我们还应该为其它模块传递一些所需的数据。

消息体

那么首先我们需要创建一个简单的消息体,消息体里面包含了一个消息的身份标识以及消息所携带的数据:

open class PHMessage: NSObject {
    
    /// 消息标识
    private(set) public var messageIdentifier: String!
    /// 消息数据
    private(set) public var messageData: Any?
    
    /// 初始化消息
    /// - Parameters:
    /// - messageIdentifier: 消息标识
    /// - messageData: 消息数据
    /// - Returns: 消息
    public required init(messageIdentifier: String, messageData: Any?) {
        self.messageIdentifier = messageIdentifier
        self.messageData = messageData
    }

}

消息总线

有了消息体之后还需要一个消息的管理器用来负责处理和分发消息。

public class PHMessageBusManager: NSObject {
    
    /// 控制中台
    private(set) weak var controlCenter: PHRoomControlCenter!
    /// 消息总线总表
    private(set) var messageBusTable: [String:[PHMessageObserver]] = [:]
    /// 消息队列
    private(set) var messageQueue: [PHMessage] = []
    /// 信号量
    private let semaphore = DispatchSemaphore(value: 1)

    /// 初始化消息总线管理器
    /// - Parameter controlCenter: 控制中台
    /// - Returns: 消息总线管理器
    init(controlCenter: PHRoomControlCenter) {
        super.init()
        self.controlCenter = controlCenter
    }
    
    .... 
}

消息总线中会有几个关键的方法,我们分开来介绍它们:

注册消息观察者
    /// 注册消息观察者 (标识,消息处理对象,消息处理方法)
    /// - Parameters:
    /// - messageIdentifier: 消息标识
    /// - messageObserver: 消息处理对象
    /// - messageSelector: 消息处理方法
    func registerMessageObserver(messageIdentifier: String, messageObserver: AnyObject, messageSelector: Selector) {
        let observer = PHMessageObserver(messageIdentifier: messageIdentifier, messageObserver: messageObserver, messageSelector: messageSelector)
        if var observers = messageBusTable[messageIdentifier] {
            observers.append(observer)
            messageBusTable[messageIdentifier] = observers
        } else {
            messageBusTable[messageIdentifier] = [observer]
        }
    }

我们在上面定义了一个接收消息总表,表中存的数据是一个消息标识对应多个消息接收者。

PHMessageObserver消息接收者,里面包含了消息标识,处理消息的对象,以及对应的方法:

class PHMessageObserver: NSObject {
    
    /// 消息标识
    private(set) var messageIdentifier: String!
    /// 消息处理对象
    private(set) weak var messageObserver: AnyObject!
    /// 消息处理方法
    private(set) var messageSelector: Selector!
    
    /// 初始化消息观察者
    /// - Parameters:
    /// - messageIdentifier: 消息标识
    /// - messageObserver: 消息处理对象
    /// - messageSelector: 消息处理方法
    /// - Returns: 消息观察者
    required init(messageIdentifier: String, messageObserver: AnyObject, messageSelector: Selector) {
        self.messageIdentifier = messageIdentifier
        self.messageObserver = messageObserver
        self.messageSelector = messageSelector
    }

}

那么当所有的模块都执行完注册消息的方法之后,表中所存的内容将是每个消息标识以及所有接收该消息的模块和方法所所构建的消息观察者。

这样做有一个好处,我们不需要询问每个模块是否需要处理这个消息,而是可以通过这个表将需要处理这个消息的所有模块都检索出来。

发送消息
    /// 发送消息
    /// - Parameters:
    /// - messageIdentifier: 消息标识
    /// - messageData: 消息数据
    public func postMessage(messageIdentifier: String, messageData: Any? = nil) {
        let message = PHMessage(messageIdentifier: messageIdentifier, messageData: messageData)
        semaphore.wait()
        messageQueue.append(message)
        semaphore.signal()
        // 异步处理消息,避免递归调用导致死锁
        DispatchQueue.global().async {
            self.dealWithMessage(messageIdentifier: messageIdentifier)
        }
    }

我们在上面定义了一个消息队列,和信号量。因为直播的模块随着业务的发展可能会变的非常多,而且我们又不能确定消息会从哪个线程发送过来,所以我们需要使用信号量来保证队列的线程安全,将新的消息添加到消息队列中。

处理消息
    /// 发送消息
    /// - Parameters:
    /// - message: 消息
    func dealWithMessage(messageIdentifier: String) {
        semaphore.wait()
        if !messageQueue.isEmpty {
            let message = messageQueue.removeFirst()
            semaphore.signal()
            
            guard let observers = messageBusTable[messageIdentifier], observers.count > 0 else {
                return
            }
            
            for observer in observers {
                if Thread.current.isMainThread {
                    observer.messageObserver.perform(observer.messageSelector, with: message)
                } else {
                    DispatchQueue.main.async {
                        observer.messageObserver.perform(observer.messageSelector, with: message)
                    }
                }
            }
        } else {
            semaphore.signal()
        }
    }

在处理消息时,首先获取队列的第一个元素,根据消息的标识获取到所有需要处理该消息观察者。

遍历所有观察者,获取到观察者内的模块和方法,在主线程中调方法并传递参数。

结语

本篇博客主要介绍了模块化开发时模块与模块之间的通讯方案-消息总线的实现过程。我们首先修改了项目的整体结构使用PHRoomControlCenter控制中台来管理模块管理对象和消息管理对象,也对模块进行一点调整,为了它能够注册,发送和处理消息。还构建了一个完整的消息总线,用来构建和分发消息。在接下来的博客中将会继续介绍在项目内,如何让模块管理和消息总线共同作用来实现模块间的通讯。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2058373.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

同样的东西,京东贵多了,为啥还有人选择京东呢?

现在很少有商品,只在一个平台上出售了,几乎哪个平台都能买到。 那为什么京东贵多了,还有人去京东买? 小编就以自己的实际体验来说一说。 先看个案例: 小编去年在京东自营店买了一块西数的机械硬盘,用了…

PHP网上花店管理系统—计算机毕业设计源码无偿分享可私信21170

目 录 摘要 1 绪论 1.1研究背景 1.2项目背景 1.3 Thinkphp框架介绍 1.4论文结构与章节安排 2 网上花店管理系统系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1数据增加流程 2.2.2数据修改流程 2.2.3数据删除流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非…

怎样更改电脑的MAC地址?

怎样更改电脑的MAC地址? 电脑的机器码是可以修改的。 操作步骤: 1、通过按WINR键,调来电脑的接运行窗口,打开CMD命令来查看机器码。 2、命令提示符窗口里输入ipconfig /all,回车,即可显示出当前电脑的网…

ARM——操作示例

操作流程: 一、实现一个led亮灯 (1)GPIO:可编程的输入输出引脚 每一组io都有一个寄存GP*CON控制引脚作用,每个io都有2个位,控制引脚作用 每一组io都有一个寄存GP*DAT控制引脚数据,每个io都有1个位&a…

电脑硬盘坏了怎么恢复数据?

在数字化时代,电脑硬盘作为存储核心,承载着我们的工作文档、学习资料、家庭照片以及无数珍贵的回忆。然而,硬盘作为机械设备,也有其寿命和脆弱性,一旦出现故障,数据恢复便成为了一个紧迫而棘手的问题。本文…

【小趴菜前端学习日记3】

学习项目 一、深度(穿透)选择器1. /deep/2.>>>3. ::v-deep 二、vue-particles1.安装2.全局引入3.使用 三、v-bind对于样式控制的增强之操作类名class四、CryptoJs加密五、自定义指令的封装和使用防抖 六、mixins七、复制字段vue-clipboard复制文…

复制与引用

复制 复制有复制的特点。 复制可以将不可思议的巧合转变成必然。 假设基于很大的运气成分,探索出了一个执行流程。如果没有任何记录,那么下次再复现出这个流程,会需要同样的运气,甚至可能更多。但运气并不会总是发生的&#xff0c…

微服务注册中心

目录 一、微服务的注册中心 1、注册中心的主要作用 (1)服务发现 (2)服务配置 (3)服务健康检测 2、 常见的注册中心 二、nacos简介 1、nacos实战入门 (1)搭建nacos环境 &am…

20240821 每日AI必读资讯

🎮《黑神话:悟空》震撼上线,英伟达AI技术立功! - 中国游戏史上的奇迹:《黑神话:悟空》预售销售额达3.9亿元,刷新国产游戏预售纪录。 - 游戏美学效果惊人:孙悟空形象深入人心&#…

Bootstrap 插件概览

在前面 布局组件 章节中所讨论到的组件仅仅是个开始。Bootstrap 自带 12 种 jQuery 插件,扩展了功能,可以给站点添加更多的互动。即使您不是一名高级的 JavaScript 开发人员,您也可以着手学习 Bootstrap 的 JavaScript 插件。利用 Bootstrap …

【重磅】WHO推荐的2024-2025年流感疫苗株组分更新了,快来看看有哪些变化吧?

前 言: 流感病毒会引起季节性流感,甚至有可能引起大流行暴发。流感病毒是负链RNA病毒,其分类复杂,亚型众多,容易突变。目前公认的预防流感的最佳方法是接种疫苗。为了保证疫苗的有效性,世界卫生组织&#…

【SAP HANA 41】HANA中函数 COUNT(DISTINCT(xxx)) 的方式使用

目录 一、语法 二、COUNT(*) 三、COUNT( [ ALL ] ) 四、COUNT(DISTINCT ) 在SAP HANA数据库中,COUNT 函数用于计算表中行的数量或者特定列中非NULL值的数量。你提到的语法是COUNT函数的不同用法,它们允许你根据需要对数据进行计数。下面是对每种用法的解释以及示例。 一…

路由高阶用法 Vue2

1.几个注意点 Home.vue <template><div><h2>我是Home内容</h2><ul class"nav nav-tabs"><li class"nav-item"><router-link class"nav-link" active-class"active" to"/home/news"…

TilesetLaye存在时,使用mask遮罩层,会出现锯齿的解决方案

TilesetLaye存在时&#xff0c;使用mask遮罩层&#xff0c;会出现锯齿 function addDemoGeoJsonLayer1() {const tiles3dLayer new mars3d.layer.TilesetLayer({name: "合肥市建筑物",url: "//data.mars3d.cn/3dtiles/jzw-hefei/tileset.json",maximumSc…

SparkSQL数据类型

支持的数据类型 SparkSQL支持的数据类型如下&#xff1a; 数值类型 ByteType&#xff1a;表示1字节带符号整数&#xff08;“带符号”意味着它可以表示正数和负数。&#xff09;。数字的范围是-128到127。ShortType&#xff1a;表示2字节带符号整数。数字的范围是-32768到32…

打造更高效的项目:如何选择合适的管理工具

国内外主流的 10 款项目工程管理系统对比&#xff1a;PingCode、Worktile、Asana、Trello、Monday.com、ClickUp、Wrike、泛微项目协同工具、广联达项目管理软件、泛普OA。 在选择项目工程管理系统时&#xff0c;你是否经常感到无从下手&#xff0c;担心投资不当或工具不适合自…

细数全球七大网络空间安全搜索引擎

随着网络攻击的频率和复杂性不断增加&#xff0c;安全专业人士需要利用各种工具来识别和应对潜在的威胁&#xff0c;网络安全搜索引擎就是其中之一&#xff0c;它们帮助安全专家查找漏洞、分析威胁情报以及监控互联网活动&#xff0c;本文将介绍全球七大网络安全搜索引擎。 1.…

误闯机器学习(第一关-概念和流程)

以下内容&#xff0c;皆为原创&#xff0c;实属不易&#xff0c;请各位帅锅&#xff0c;镁铝点点赞赞和关注吧&#xff01; 好戏开场了。 一.什么是机器学习 机器学习就是从数据中自动分析获取模型&#xff08;总结出的数据&#xff09;&#xff0c;并训练模型&#xff0c;去预…

Gadmin极速开发平台,几分钟给你整一个OA系统出来

Gadmin极速开发平台 在企业信息化的大潮中&#xff0c;Gadmin极速开发平台以其独特的低代码开发模式&#xff0c;为企业提供了一套高效、灵活的解决方案。本文将介绍Gadmin平台的基本信息、核心特点&#xff0c;以及它如何帮助企业快速实现信息化建设。 软件简介 Gadmin是一个…

华为:数据入湖,企业数据的逻辑汇聚(附数据湖建设方案下载)

往期回顾>> 华为内部“维度数据”解析 数据入湖是个什么鬼&#xff1f; 为什么数据治理工作越来越迷茫&#xff1f;(附数据治理方案PPT下载) 数字化的本质逻辑:连接、数据、智能 125页PPT&#xff1a;数据中台应用技术方案 数据中台解决方案&#xff0c;附55页PPT…