实际开发中的模块化开发 - 模块管理(以直播间为例)-CSDN博客
在前面的两篇博客中,我们已经介绍了直播模块的简单结构,创建了模块管理器和模块抽象基类,并且通过模块化实现了两个小业务功能模块。接下来,我们构建了一个用于模块间通讯的消息总线,这个消息总线可以在模块间进行通讯和数据传递。不过,消息总线还没有实际应用到我们的项目中。本篇博客中,我们将模块管理和消息总线整合到一起,并将它们应用到直播间内。
在使用它们之前我们仍然有许多准备工作需要完成,首先我们需要创建一个专属于直播间的模块抽象基类,它并不会单独使用,但是会为其它子模块提供一些直播间内的信息和方法。
import UIKit
import PHRoomModuleManager
class PHRoomModule: PHModule {
/// 直播间视图控制器
var roomViewController:PHRoomViewController? {
if let roomViewController = self.controlCenter?.ownerController as? PHRoomViewController {
return roomViewController
}
return nil
}
/// 直播间视图
var roomView:UIView? {
return self.roomViewController?.view
}
/// 是否是主播
var isAnchor:Bool {
return self.roomViewController?.isAnchor ?? false
}
/// 主播信息
var anchorInfo:PHAnchorInfo? {
return self.roomViewController?.anchorInfo
}
/// 直播间信息
var roomInfo:PHRoomInfo? {
return self.roomViewController?.roomInfo
}
}
在上面的截图中可以看到我们还创建了一个三个模块构建器,分别负责创建公共模块,主播专属模块和观众专属模块。
公共模块:我们会将所有主播端和观众端都包含的功能模块在这里面创建,比如用户卡片。
import UIKit
import PHRoomModuleManager
class PHRoomCommonModuleBuilder: NSObject {
/// 模块列表
private(set) var modules: [PHModuleModel] = []
/// 创建所有模块
func buildModules() {
}
/// 添加模块
/// - Parameters:
/// - moduleIdentifier: 模块标识
/// - moduleIndex: 模块序号
/// - moduleDescription: 模块描述
/// - moduleClassString: 模块类字符串
/// - receiverMessage: 模块接收的消息
func addModule(moduleIdentifier: String, moduleIndex: Int = 0, moduleDescription: String, moduleClassString: String,receiverMessage: [String] = []) {
let moduleModel = PHModuleModel()
moduleModel.moduleIdentifier = moduleIdentifier
moduleModel.moduleIndex = moduleIndex
moduleModel.moduleDescription = moduleDescription
moduleModel.moduleClassString = moduleClassString
moduleModel.receiverMessage = receiverMessage
modules.append(moduleModel)
}
}
主播模块:专属与主播的业务功能将会在这里面创建,比如美颜模块。
import UIKit
class PHRoomSPModuleBuilder: PHRoomCommonModuleBuilder {
/// 创建所有模块
override func buildModules() {
super.buildModules()
}
}
观众模块:观众的业务功能模块将会在这里面创建,比如礼物面板模块。
import UIKit
class PHRoomLPModuleBuilder: PHRoomCommonModuleBuilder {
/// 创建所有模块
override func buildModules() {
super.buildModules()
}
}
另外还有两个文件主要负责定义模块标识字符和消息标识字符串。
接下里我们把重点转移到直播间的视图控制器内,开始创建控制中台来整合模块管理和消息总线。
import UIKit
import PHRoomModuleManager
class PHRoomViewController: UIViewController {
/// 模块化中台
private var controlCenter: PHRoomControlCenter!
/// 是否是主播
var isAnchor: Bool = false
/// 主播信息
private(set) var anchorInfo: PHAnchorInfo?
/// 直播间信息
private(set) var roomInfo: PHRoomInfo?
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
setupModuleManager()
// 其它准备工作
// ... 请求主播信息
// ... 请求直播间信息
controlCenter.moduleDidLoad()
}
/// 初始化模块管理器
private func setupModuleManager() {
let builder: PHRoomCommonModuleBuilder
if isAnchor {
builder = PHRoomSPModuleBuilder()
} else {
builder = PHRoomLPModuleBuilder()
}
builder.buildModules()
controlCenter = PHRoomControlCenter(ownerController: self, modules:builder.modules)
}
deinit {
controlCenter.unloadModules()
print("房间控制器销")
}
}
在这个直播间场景中我们创建了三个模块,主播信息模块,直播信息模块和用户公告板模块。
其中主播信息模块比较独立,而直播信息和用户公共版之间将会涉及到消息通讯,下面让我们来看一下如何构建模块的吧。
由于三个都属于公共模块,所以他们的创建都会放在PHRoomCommonModuleBuilder下,代码如下:
/// 创建所有模块
func buildModules() {
// 房间间信息模块
addModule(moduleIdentifier: PHRoomModuleIdentifier, moduleIndex: 0, moduleDescription: "直播间信息模块", moduleClassString: "PHCPRoomInfoModule")
// 直播信息模块
addModule(moduleIdentifier: PHCPLiveInfoModuleIdentifier, moduleIndex: 1, moduleDescription: "直播信息模块", moduleClassString: "PHCPLiveInfoModule")
// 公告板
addModule(moduleIdentifier: PHCPAnnouncementModuleIdentifier, moduleIndex: 2, moduleDescription: "公告板", moduleClassString: "PHCPAnnouncementModule",receiverMessage: [kAnnouncementButtonClickedMessage])
}
其中公告板模块需要接收一个标识符为kAnnouncementButtonClickedMessage的消息,也就是点击公告板按钮的消息。
模块内的具体UI代码,在这里就一一介绍了,稍后项目会上传到资源中。
发送消息:
在直播间信息的UI中有一个公告板的按钮,我们实现它的点击事件并传递到房间信息模块中,在点击事件内发送我们定义好的消息,此消息不需要携带任何参数,所以数据我们可以不传。
/// 添加信息视图
private func addAnchorVolumeView() {
guard let roomView = self.roomView else { return }
roomView.addSubview(anchorVolumeView)
anchorVolumeView.layer.cornerRadius = 16.0
anchorVolumeView.backgroundColor = UIColor.white.withAlphaComponent(0.5)
anchorVolumeView.snp.makeConstraints { (make) in
make.leading.equalToSuperview().offset(16.0)
make.trailing.equalToSuperview().offset(-16.0)
make.top.equalToSuperview().offset(navigationBarHeight + 32.0)
make.height.equalTo(126.0)
}
anchorVolumeView.bulletinAction = { [weak self] in
self?.postMessage(messageIdentifier: kAnnouncementButtonClickedMessage, messageData: nil)
}
}
处理消息:
在公告板的模块内接收并处理消息,来决定公告板的显示和隐藏。
class PHCPAnnouncementModule: PHRoomModule {
/// 公告板
private var announcementView:SVAnnouncementView?
override func receiveMessage(message: PHMessage) {
if message.messageIdentifier == kAnnouncementButtonClickedMessage {
if let animationView = self.announcementView {
UIView.animate(withDuration: 0.25, animations: {
animationView.alpha = 0
}) { _ in
animationView.removeFromSuperview()
self.announcementView = nil
}
} else {
addAnnouncementView()
}
}
}
func addAnnouncementView() {
self.announcementView = SVAnnouncementView()
guard let roomView = self.roomView else { return }
guard let announcementView = announcementView else { return }
roomView.addSubview(announcementView)
announcementView.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-16.0)
make.top.equalToSuperview().offset(navigationBarHeight + 32.0 + 40.0)
}
announcementView.renderUser()
announcementView.alpha = 0
UIView.animate(withDuration: 0.25) {
self.announcementView?.alpha = 1
}
}
}
博客到此为止,我们完整演示了如何在模块化开发中利用模块管理和消息总线来实现模块间的消息和数据传递。消息总线的引入显著减少了模块之间的依赖关系,使得在大多数情况下,我们无需从一个模块中直接访问另一个模块。
尽管目前的方法已经适用于大多数项目,但在处理复杂的直播间业务时,它依然显得过于简洁。目前的系统仅区分了主播和观众,尚未考虑房间类型的区分。此外,所有模块都是直接加载的,这可能会导致资源浪费。在模块化结构中,模块加载的顺序也是至关重要的,我们将在后续版本中进一步完善这些问题。