高效处理 iOS 应用中的大规模礼物数据:以直播项目为例(1-礼物池)-CSDN博客
引言
在上一篇博客中,我们探讨了直播项目中如何通过构建高效的礼物池加载和更新机制来应对大规模数据处理的跳转。通过设计合理的数据架构和增量更新策略,不仅优化了资源管理,还未后续功能奠定了基础。然而,在实际开发中,随着礼物池数据的不断增长,我们需要考虑更多的场景,比如使用本地数据库来存储礼物池数据。
尽管礼物池在系统中起到了至关重要的支撑作用,但我们的最终目的还是给用户展示礼物面板。礼物面板不仅承载了礼物的展示和交互功能,还直接影响用户的使用体验。因此,在本篇博客中我们就将重点讨论如何设计和优化里面面板,让它能够加载海量数据的同时,仍然保持流程的响应和良好的视觉效果。
礼物面板
在直播应用中,礼物面板是用户最直接的交互界面,因此,如何高效地管理和展示这些礼物,直接影响到用户体验。随着礼物池数据的持续增长,礼物面板的更新与渲染也变得尤为关键。我们不仅要确保数据的正确性,还要优化性能,确保用户在选择、筛选礼物时的流程体验。
礼物的列表会根据不同的主播,不同的直播类型,不同的地区来动态变化,因此我们需要频繁更新这个数据,如果每次都读取大量的礼物数据显然这并不明智。因此我们将礼物面板上的数据进行简化,让它成为一个十分轻量级的接口,以便频繁请求。后续在通过与礼物池数据相结合来构建完成的礼物面板数据。
礼物面板的部分数据如下:
{
"errorCode": 0,
"message": "OK",
"data": {
"giftTabRelation": [{
"name_language": "{\"en\": \"HOT\", \"ja-jp\": \"HOT\", \"zh-cn\": \"HOT\", \"zh-tw\": \"HOT\"}",
"tabIds": [2, 4, 6, 7, 8, 10, 9, 11, 12, 13, 14, 15]
}, {
"name_language": "{\"en\": \"en-activity\", \"ja-jp\": \"jp-activity\", \"zh-cn\": \"zh-activity\", \"zh-tw\": \"tw-activity\"}",
"tabIds": [6, 2]
}, {
"name_language": "{\"en\": \"Campaign\", \"ja-jp\": \"Campaign\", \"zh-cn\": \"Campaign\", \"zh-tw\": \"Campaign\"}",
"tabIds": [2, 9]
}],
"tabs": [{
"extension": "",
"bigGiftPos": [],
"id": 3,
"mtime": 1709711103,
"category": 0,
"tabTitles": {
"zh-tw": {
"title": "EmpowHER"
},
"en": {
"title": "EmpowHER"
},
"zh-cn": {
"title": "EmpowHER"
},
"ja-jp": {
"title": "EmpowHER"
}
},
"gifts": [103225, 103226, 103227, 103228, 103229, 103230, 103231, 103232]
},
...
],
"id": "3",
"anchorId": 17207807,
"mtime": "1705035837",
},
"retCode": 0
}
数据分析:
- giftTabRelation列表为礼物面板的一级标签,除了标签名称以外还有一个tabIds数组,表示所有属于该一级标签下的二级标签id。
- tabs与giftTabRelation同级,表示礼物面板的二级标签,该标签下除了标签名称之外,还有一个id表示二级标签的唯一id。另外还有一个gifts数组,表示二级标签下的所有礼物的id。
礼物池与礼物面板数据结合
接下来我们需要把礼物面板的数据与礼物池的数据结合到一起,来构建一个完整的礼物面板数据。
为此特别创建了一个名为MWGiftBoardPresenter礼物面板数据处理类,主要负责请求礼物面板的数据,以及礼物池数据与礼物面板数据合并的工作。
我们先来看一下它声明的属性:
class MWGiftBoardPresenter: NSObject {
/// 礼物面板数据
private var giftBoardModel: MWGiftBoardModel?
/// 礼物池数据
private var giftPoolMap:[Int:MWGiftModel] = [:]
/// 礼物数据回调
private var giftBoardModelCallback: ((MWGiftBoardModel?)->Void)?
}
我们在里面定义了礼物面板的数据模型,以及用于接收礼物池的数据字典,还有一个获取到完整礼物面板数据的回调。
再来看一下它的方法:
首先是初始化方法,在该方法中我们开始从礼物中读取礼物池内的所有数据,并赋值给当前对象的giftPoolMap属性,留作备用。
override init() {
super.init()
MWGiftPoolManager.shared.getGiftData {[weak self] giftPoolMap in
guard let self = self else { return }
MWLogHelper.debug("获取到礼物池数据", context: "MWGiftBoardPresenter")
self.giftPoolMap = giftPoolMap
mergeGiftData()
}
}
下面一个方法请求请求轻量级的礼物面板缩略数据,请求完成会回有一个回调,需要注意的是这里面的回调的是礼物面板的完整数据,也就是和礼物池合并过的数据模型,为此我们使用giftBoardModelCallback数据进行了一下承接。
/// 请求礼物面板数据
/// - Parameters:
/// - anchorId: 主播id
/// - roomType: 直播间类型(动态和私信传64)
/// - callback
func requestGiftBoardData(anchorId:Int, roomType:Int,callback: @escaping (MWGiftBoardModel?) -> Void) {
giftBoardModelCallback = callback
var params = [String:Any]()
params["anchorId"] = anchorId
params["roomType"] = roomType
MWNetworkHelper.request(endpoint: MWAPINormalEndpoint.api_giftPanelData, parameters: params,modelType: MWGiftBoardModel.self) {[weak self] model, data, error in
guard let self = self else { return }
MWLogHelper.debug("获取到礼物面板数据", context: "MWGiftBoardPresenter")
if error != nil{
MWLogHelper.error("请求礼物面板数据失败,错误信息:\(error?.description ?? "")")
} else {
self.giftBoardModel = model
self.mergeGiftData()
}
}
}
在获取到了礼物面板的缩略数据以及礼物池的所有数据之后,我们就可以开始合并礼物数据了,礼物池数据在加载时被构建成了一个礼物id对应一个礼物模型的形式,这样在这一步获取礼物的查找效率要比存放在数组中提高了许多。
/// 合并礼物数据
private func mergeGiftData() {
guard let giftBoardModel = giftBoardModel else {
return
}
guard giftPoolMap.count > 0 else {
return
}
guard let giftBoardFistTabs = giftBoardModel.firstTabs else {
return
}
MWLogHelper.debug("开始合并数据", context: "MWGiftBoardPresenter")
// 开启全局并发
let global_queue = DispatchQueue.global()
global_queue.async {
/// 创建一个tabid对应的表吧
var tabMap = [Int:MWGiftBoardSubTabModel]()
guard var subTabs = giftBoardModel.subTabs,subTabs.count > 0 else {
MWLogHelper.error("subTabs is nil", context: "MWGiftBoardPresenter")
return
}
// 直接遍历tabs
for (subTabIndex,subTab) in subTabs.enumerated() {
// 该tab下的所有礼物id
let gifts = subTab.gifts ?? []
if gifts.count == 0 {
continue
}
// 循环礼物id列表
for (_,giftId) in gifts.enumerated() {
if let giftModel = self.giftPoolMap[giftId] {
// 重复数据 就会重复添加咯
subTab.giftModels.append(giftModel)
}
}
if let tabId = subTab.tabId {
tabMap[tabId] = subTab
} else {
MWLogHelper.error("tabId is nil", context: "MWGiftBoardPresenter")
}
subTabs[subTabIndex] = subTab
self.giftBoardModel?.subTabs = subTabs
}
// 循环1级tab
for (_,firstTab) in giftBoardFistTabs.enumerated() {
let tabids = firstTab.tabids ?? []
if tabids.count == 0 {
continue
}
var subTabs = [MWGiftBoardSubTabModel]()
for(_,tabId) in tabids.enumerated() {
if let subTab = tabMap[tabId] {
subTabs.append(subTab)
}
}
firstTab.subTabs = subTabs
}
}
// 主线程回调
DispatchQueue.main.async {
self.giftBoardModelCallback?(self.giftBoardModel)
}
MWLogHelper.debug("数据合并完成", context: "MWGiftBoardPresenter")
}
由于一级tab和二级tab都在同一层级下,所以我们使用礼物池的方式同样也创建了一个二级tab的字典。在二级tab的循环中将所有数据该tab下的礼物数据添加到二级tab的礼物列表。
而在一级tab的循环中将所有数据该一级tab的二级tab数据添加到一级tab的二级tab列表中。
在主线程调用self.giftBoardModelCallback?此时回调回去的就是已经合并好的礼物面板数据模型。
结语
通过本篇文章,我们探讨了如何将礼物池的数据与礼物面板结合起来,以实现高效的数据渲染。礼物池作为后台数据源,与礼物面板的数据结合能够为用户呈现实时且精准的礼物信息,而确保数据的流畅更新则是提升用户体验的关键。
然而,随着虽然礼物面板展示完成了,但是如何管理礼物的大图资源和预览图数据将成为下一个挑战。如何设计高效的数据加载方案,将是我们下一篇博客的重点内容。