实际开发中的模块化开发 - 模块管理(以直播间为例)-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控制中台来管理模块管理对象和消息管理对象,也对模块进行一点调整,为了它能够注册,发送和处理消息。还构建了一个完整的消息总线,用来构建和分发消息。在接下来的博客中将会继续介绍在项目内,如何让模块管理和消息总线共同作用来实现模块间的通讯。