diff --git a/Podfile b/Podfile index 87891038..fec8b09c 100644 --- a/Podfile +++ b/Podfile @@ -1,5 +1,5 @@ # Uncomment the next line to define a global platform for your project - platform :ios, '11.0' + platform :ios, '13.0' source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git' target 'YuMi' do use_frameworks! @@ -72,7 +72,7 @@ post_install do |installer| installer.generated_projects.each do |project| project.targets.each do |target| target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' config.build_settings['DEBUG_INFORMATION_FORMAT'] = 'dwarf-with-dsym' config.build_settings['ENABLE_BITCODE'] = 'NO' xcconfig_path = config.base_configuration_reference.real_path diff --git a/YuMi.xcodeproj/project.pbxproj b/YuMi.xcodeproj/project.pbxproj index ef4b12f5..0e6f2a1f 100644 --- a/YuMi.xcodeproj/project.pbxproj +++ b/YuMi.xcodeproj/project.pbxproj @@ -481,6 +481,8 @@ 4C7F2A672E0BE0AB002F5058 /* FirstRechargeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C7F2A662E0BE0AB002F5058 /* FirstRechargeModel.m */; }; 4C7F2A6B2E0BE7E7002F5058 /* FirstRechargeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C7F2A6A2E0BE7E7002F5058 /* FirstRechargeManager.m */; }; 4C815A172CFEB758002A46A6 /* SuperBlockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C815A162CFEB758002A46A6 /* SuperBlockViewController.m */; }; + 4C816A7F2E826C7300EA6A45 /* XPMessageItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C816A7E2E826C7300EA6A45 /* XPMessageItem.m */; }; + 4C816A802E826C7300EA6A45 /* XPMessageDataSourceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C816A7C2E826C7300EA6A45 /* XPMessageDataSourceManager.m */; }; 4C84A9C22E5ED593002C10FC /* GameBannerGestureManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C84A9C12E5ED593002C10FC /* GameBannerGestureManager.m */; }; 4C84A9CB2E602B1A002C10FC /* BuglyManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C84A9CA2E602B1A002C10FC /* BuglyManager.m */; }; 4C85DB812DCDD83E00FD9839 /* CreateEventPresenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C85DB802DCDD83E00FD9839 /* CreateEventPresenter.m */; }; @@ -2577,6 +2579,10 @@ 4C7F2A6A2E0BE7E7002F5058 /* FirstRechargeManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FirstRechargeManager.m; sourceTree = ""; }; 4C815A152CFEB758002A46A6 /* SuperBlockViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SuperBlockViewController.h; sourceTree = ""; }; 4C815A162CFEB758002A46A6 /* SuperBlockViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SuperBlockViewController.m; sourceTree = ""; }; + 4C816A7B2E826C7300EA6A45 /* XPMessageDataSourceManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPMessageDataSourceManager.h; sourceTree = ""; }; + 4C816A7C2E826C7300EA6A45 /* XPMessageDataSourceManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPMessageDataSourceManager.m; sourceTree = ""; }; + 4C816A7D2E826C7300EA6A45 /* XPMessageItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPMessageItem.h; sourceTree = ""; }; + 4C816A7E2E826C7300EA6A45 /* XPMessageItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPMessageItem.m; sourceTree = ""; }; 4C84A9C02E5ED593002C10FC /* GameBannerGestureManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GameBannerGestureManager.h; sourceTree = ""; }; 4C84A9C12E5ED593002C10FC /* GameBannerGestureManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GameBannerGestureManager.m; sourceTree = ""; }; 4C84A9C92E602B1A002C10FC /* BuglyManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BuglyManager.h; sourceTree = ""; }; @@ -8725,6 +8731,10 @@ E84B0E432727EF2C008818C6 /* Model */ = { isa = PBXGroup; children = ( + 4C816A7B2E826C7300EA6A45 /* XPMessageDataSourceManager.h */, + 4C816A7C2E826C7300EA6A45 /* XPMessageDataSourceManager.m */, + 4C816A7D2E826C7300EA6A45 /* XPMessageItem.h */, + 4C816A7E2E826C7300EA6A45 /* XPMessageItem.m */, E87A24EF272935920086A794 /* XPMessageRemoteExtModel.h */, E87A24F0272935920086A794 /* XPMessageRemoteExtModel.m */, E8398069290288660084BFC8 /* XPMessageInfoModel.h */, @@ -12652,6 +12662,8 @@ 9B208A362779B50100F9E54A /* GiftNobleInfoModel.m in Sources */, E80A086227F2AC190027B30C /* RoomPKDetailInfoModel.m in Sources */, 4CAFF00A2DD342A400CD81DF /* MessagePublicEventModel.m in Sources */, + 4C816A7F2E826C7300EA6A45 /* XPMessageItem.m in Sources */, + 4C816A802E826C7300EA6A45 /* XPMessageDataSourceManager.m in Sources */, E824545126F5CE6E00BE8163 /* XPMineModifPayPwdPresenter.m in Sources */, E8098CB1282E86EF0090B9F0 /* XPMomentsContentView.m in Sources */, E85E3FA728B7A6F000268DC8 /* MessageContentMonentsView.m in Sources */, @@ -13302,7 +13314,7 @@ "PB_ENABLE_MALLOC=1", ); INFOPLIST_FILE = YuMi/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -13549,7 +13561,7 @@ ); GCC_PREFIX_HEADER = YuMi/Structure/PrefixHeader.pch; INFOPLIST_FILE = YuMi/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/YuMi/Modules/YMRoom/View/AnimationView/BannerScheduler.m b/YuMi/Modules/YMRoom/View/AnimationView/BannerScheduler.m index 155e665e..53e664d7 100644 --- a/YuMi/Modules/YMRoom/View/AnimationView/BannerScheduler.m +++ b/YuMi/Modules/YMRoom/View/AnimationView/BannerScheduler.m @@ -33,13 +33,9 @@ - (void)enqueueBanner:(id)banner { if (!banner) { - NSLog(@"⚠️ BannerScheduler: 尝试添加空的 Banner"); return; } - NSLog(@"🔄 BannerScheduler: 添加 Banner 到队列 - 类型: %@, 队列长度: %ld", - [banner class], (long)self.bannerQueue.count); - [self.bannerQueue addObject:banner]; // 如果当前没有在播放且未暂停,则开始处理 @@ -50,31 +46,26 @@ - (void)processNextBanner { if (self.isPaused) { - NSLog(@"⏸️ BannerScheduler: 调度器已暂停,跳过处理"); return; } // 🔧 新增:检查 delegate 是否有效 if (!self.delegate) { - NSLog(@"⚠️ BannerScheduler: delegate 已失效,停止处理"); [self clearQueue]; self.isPlaying = NO; return; } if (self.isPaused) { - NSLog(@"⏸️ BannerScheduler: 调度器已暂停,跳过处理"); return; } if (self.bannerQueue.count == 0) { - NSLog(@"🔄 BannerScheduler: 队列为空,停止播放"); self.isPlaying = NO; return; } if (self.isPlaying) { - NSLog(@"🔄 BannerScheduler: 已有 Banner 正在播放,跳过处理"); return; } @@ -84,10 +75,7 @@ // 取出队列中的第一个 Banner id nextBanner = [self.bannerQueue firstObject]; [self.bannerQueue removeObjectAtIndex:0]; - - NSLog(@"🔄 BannerScheduler: 开始播放 Banner - 类型: %@, 剩余队列: %ld", - [nextBanner class], (long)self.bannerQueue.count); - + self.isPlaying = YES; // 通知代理开始播放 @@ -102,7 +90,6 @@ } - (void)clearQueue { - NSLog(@"🗑️ BannerScheduler: 清空 Banner 队列 - 原有数量: %ld", (long)self.bannerQueue.count); [self.bannerQueue removeAllObjects]; } diff --git a/YuMi/Modules/YMRoom/View/AnimationView/GiftAnimationManager.m b/YuMi/Modules/YMRoom/View/AnimationView/GiftAnimationManager.m index 429aeee8..f3728910 100644 --- a/YuMi/Modules/YMRoom/View/AnimationView/GiftAnimationManager.m +++ b/YuMi/Modules/YMRoom/View/AnimationView/GiftAnimationManager.m @@ -94,29 +94,23 @@ - (void)processNextGift { dispatch_async(self.queue, ^{ if (self.giftQueue.count == 0) { - NSLog(@"[Combo effect] 📭 动画队列为空,停止处理"); [self stopGiftQueue]; return; } GiftReceiveInfoModel *giftInfo = self.giftQueue.firstObject; if (!giftInfo) { - NSLog(@"[Combo effect] ⚠️ 队列第一个元素为空,跳过处理"); return; } // 检查并修复连击计数 if (giftInfo.comboCount < 1) { - NSLog(@"[Combo effect] 🚨 动画处理中检测到连击计数异常 - comboCount: %ld", (long)giftInfo.comboCount); giftInfo.comboCount = 1; - NSLog(@"[Combo effect] 🔧 修复动画连击计数为 1"); } - NSLog(@"[Combo effect] 🎬 开始处理动画 - giftId: %ld, combo: %ld", (long)giftInfo.gift.giftId, (long)giftInfo.comboCount); // 在同一线程中移除元素 [self.giftQueue xpSafeRemoveObjectAtIndex:0]; - NSLog(@"[Combo effect] 📊 移除后动画队列数量: %ld", (long)self.giftQueue.count); @kWeakify(self); dispatch_async(dispatch_get_main_queue(), ^{ @@ -124,35 +118,27 @@ if (self) { [self distributeGiftAnimation:giftInfo]; } else { - NSLog(@"[Combo effect] ⚠️ self已释放,跳过动画分发"); } }); }); } - (void)distributeGiftAnimation:(GiftReceiveInfoModel *)giftInfo { - NSLog(@"[Combo effect] 🎯 开始分发动画 - uid: %@", giftInfo.uid); NSArray *targetUids = [self resolveTargetUids:giftInfo]; - NSLog(@"[Combo effect] 🎯 目标用户数量: %ld", (long)targetUids.count); CGPoint startPoint = CGPointZero; BOOL isComboAnimation = [self shouldUseComboAnimationForSender:giftInfo.uid]; - NSLog(@"[Combo effect] 🎯 是否使用连击动画: %@", isComboAnimation ? @"YES" : @"NO"); if (isComboAnimation) { startPoint = [self comboAnimationStartPoint]; - NSLog(@"[Combo effect] 🎯 使用连击动画起点: %@", NSStringFromCGPoint(startPoint)); } else { startPoint = [self calculateAnimationPoint:giftInfo.uid isEndPoint:NO]; - NSLog(@"[Combo effect] 🎯 使用普通动画起点: %@", NSStringFromCGPoint(startPoint)); } NSTimeInterval delay = isComboAnimation ? self.comboAnimationDelay : self.standardAnimationDelay; - NSLog(@"[Combo effect] 🎯 动画延迟时间: %.2f", delay); for (NSString *targetUid in targetUids) { CGPoint endPoint = [self calculateAnimationPoint:targetUid isEndPoint:YES]; - NSLog(@"[Combo effect] 🎯 为目标用户 %@ 创建动画 - 终点: %@", targetUid, NSStringFromCGPoint(endPoint)); [self scheduleAnimationWithDelay:delay giftInfo:giftInfo.gift startPoint:startPoint @@ -245,15 +231,12 @@ - (void)enqueueGift:(GiftReceiveInfoModel *)giftInfo { if (!giftInfo) { - NSLog(@"[Combo effect] ⚠️ 礼物信息为空,跳过动画队列"); return; } - NSLog(@"[Combo effect] 🎬 添加礼物到动画队列 - giftId: %ld, combo: %ld", (long)giftInfo.gift.giftId, (long)giftInfo.comboCount); dispatch_async(self.queue, ^{ [self.giftQueue addObject:giftInfo]; - NSLog(@"[Combo effect] 📊 动画队列当前数量: %ld", (long)self.giftQueue.count); [self startGiftQueue]; }); } @@ -268,7 +251,6 @@ BOOL isUserInCombo = [self.userComboStates[uid] boolValue]; if (isUserInCombo) { BOOL isCurrentUser = [uid isEqualToString:[AccountInfoStorage instance].getUid]; - NSLog(@"[Combo effect] 🎯 用户 %@ 处于combo状态,是否当前用户: %@", uid, isCurrentUser ? @"YES" : @"NO"); return isCurrentUser; } @@ -278,12 +260,10 @@ NSTimeInterval timeSinceLastGift = [[NSDate date] timeIntervalSinceDate:lastGiftTime]; if (timeSinceLastGift <= self.comboTimeWindow) { BOOL isCurrentUser = [uid isEqualToString:[AccountInfoStorage instance].getUid]; - NSLog(@"[Combo effect] 🎯 用户 %@ 在时间窗口内,是否当前用户: %@", uid, isCurrentUser ? @"YES" : @"NO"); return isCurrentUser; } } - NSLog(@"[Combo effect] 🎯 用户 %@ 不使用combo动画", uid); return NO; } @@ -310,16 +290,13 @@ - (void)setUserComboState:(BOOL)isCombo forUser:(NSString *)uid { if (!uid || uid.length == 0) { - NSLog(@"[Combo effect] ⚠️ 用户ID为空,无法设置combo状态"); return; } if (isCombo) { self.userComboStates[uid] = @(YES); - NSLog(@"[Combo effect] ✅ 设置用户 %@ 为combo状态", uid); } else { [self.userComboStates removeObjectForKey:uid]; - NSLog(@"[Combo effect] 🔄 清除用户 %@ 的combo状态", uid); } } @@ -333,7 +310,6 @@ } self.userLastGiftTime[uid] = [NSDate date]; - NSLog(@"[Combo effect] ⏰ 更新用户 %@ 的送礼时间", uid); } - (void)cleanupExpiredStates { @@ -349,7 +325,6 @@ for (NSString *uid in expiredUsers) { [self.userLastGiftTime removeObjectForKey:uid]; [self.userComboStates removeObjectForKey:uid]; - NSLog(@"[Combo effect] 🧹 清理过期用户状态: %@", uid); } } diff --git a/YuMi/Modules/YMRoom/View/AnimationView/RoomAnimationView.m b/YuMi/Modules/YMRoom/View/AnimationView/RoomAnimationView.m index a12e0e2a..a3e48287 100644 --- a/YuMi/Modules/YMRoom/View/AnimationView/RoomAnimationView.m +++ b/YuMi/Modules/YMRoom/View/AnimationView/RoomAnimationView.m @@ -1875,11 +1875,6 @@ BannerSchedulerDelegate // 自定义消息处理入口排查日志:first/second、payload size、线程 NSData *payloadJSON = nil; @try { payloadJSON = [NSJSONSerialization dataWithJSONObject:attachment.data ?: @{} options:0 error:nil]; } @catch (__unused NSException *e) {} - NSLog(@"[Combo effect][Anim] 🎞 handleNIMCustomMessage | first=%ld second=%ld | payload=%lub | main=%@ | ts=%.3f", - (long)attachment.first, (long)attachment.second, - (unsigned long)payloadJSON.length, - [NSThread isMainThread] ? @"YES" : @"NO", - [[NSDate date] timeIntervalSince1970]); #endif switch (attachment.first) { case CustomMessageType_User_Enter_Room: @@ -2018,27 +2013,22 @@ BannerSchedulerDelegate return; } - NSLog(@"[Combo effect] 📨 收到礼物消息 - second: %ld", (long)attachment.second); GiftReceiveInfoModel * receiveInfo = [GiftReceiveInfoModel modelWithJSON:attachment.data]; [receiveInfo giftDataAlignment]; // 检查并修复连击计数 if (receiveInfo.comboCount < 1) { - NSLog(@"[Combo effect] 🚨 收到云信消息中连击计数异常 - comboCount: %ld", (long)receiveInfo.comboCount); // 尝试从 attachment.data 中获取正确的连击计数 NSNumber *dataComboCount = attachment.data[@"comboCount"]; if (dataComboCount && [dataComboCount integerValue] >= 1) { - NSLog(@"[Combo effect] 🔧 从 attachment.data 修复连击计数 - 修复为: %@", dataComboCount); receiveInfo.comboCount = [dataComboCount integerValue]; } else { - NSLog(@"[Combo effect] 🔧 设置默认连击计数为 1"); receiveInfo.comboCount = 1; } } - NSLog(@"[Combo effect] 📨 礼物消息解析完成 - giftId: %ld, combo: %ld, uid: %@", (long)receiveInfo.gift.giftId, (long)receiveInfo.comboCount, receiveInfo.uid); @@ -2093,7 +2083,6 @@ BannerSchedulerDelegate } if (receiveInfo.isLuckyBagGift) { - NSLog(@"[Combo effect] 🎁 处理福袋礼物"); [self receiveGiftHandleSendGiftAnimationWith:receiveInfo attachment:attachment]; [self _handleBagGift:receiveInfo]; @@ -2101,11 +2090,9 @@ BannerSchedulerDelegate if (receiveInfo.gift.notifyFull == 1 && attachment.second == Custom_Message_Sub_Gift_Send) { // 全服礼物,但由自己发送,不在此逻辑播放 - NSLog(@"[Combo effect] 🎁 全服礼物由自己发送,跳过播放"); return; } // 处理从位置 发送者 到 接受者 的礼物移动动画 - NSLog(@"[Combo effect] 🎁 处理普通礼物动画"); [self receiveGiftHandleSendGiftAnimationWith:receiveInfo attachment:attachment]; // 播放礼物动画(svga/mp4)(如果有的话) @@ -4048,7 +4035,6 @@ BannerSchedulerDelegate - (void)bannerSchedulerDidFinishPlaying:(BannerScheduler *)scheduler { // Banner 播放完成,可以在这里进行清理工作 - NSLog(@"🔄 BannerScheduler: Banner 播放完成"); // 🔧 新增:确保手势容器状态正确 [self ensureBannerGestureContainersEnabled]; @@ -4057,7 +4043,6 @@ BannerSchedulerDelegate - (void)bannerScheduler:(BannerScheduler *)scheduler didStartPlayingBanner:(id)banner { // Banner 开始播放 - NSLog(@"🔄 BannerScheduler: Banner 开始播放 - 类型: %@", [banner class]); } #pragma mark - Private Methods @@ -4230,7 +4215,6 @@ BannerSchedulerDelegate NSString *uid = userInfo[@"uid"]; BOOL isCombo = [userInfo[@"isCombo"] boolValue]; - NSLog(@"[Combo effect] 🔔 收到combo状态变化通知 - 用户: %@, 状态: %@", uid, isCombo ? @"YES" : @"NO"); // 通知动画管理器更新combo状态 if (isCombo) { diff --git a/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageDataSourceManager.h b/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageDataSourceManager.h new file mode 100644 index 00000000..95a34fd9 --- /dev/null +++ b/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageDataSourceManager.h @@ -0,0 +1,94 @@ +// +// XPMessageDataSourceManager.h +// YUMI +// +// Created by YUMI on 2024/12/19. +// + +#import +#import "XPMessageItem.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * 消息数据源管理器 + * 统一管理所有消息数据,提供线程安全的数据操作 + * 支持 DiffableDataSource 的单一数据源架构 + */ +@interface XPMessageDataSourceManager : NSObject + +/// 所有消息(只读) +@property (nonatomic, strong, readonly) NSArray *allMessages; + +/// 聊天消息(只读) +@property (nonatomic, strong, readonly) NSArray *chatMessages; + +/// 礼物消息(只读) +@property (nonatomic, strong, readonly) NSArray *giftMessages; + +/// 最大消息数量,超过此数量会自动清理旧消息 +@property (nonatomic, assign) NSInteger maxMessages; + +/** + * 初始化方法 + * @param maxMessages 最大消息数量,默认1000 + */ +- (instancetype)initWithMaxMessages:(NSInteger)maxMessages; + +/** + * 添加消息 + * @param message 要添加的消息项 + */ +- (void)addMessage:(XPMessageItem *)message; + +/** + * 同步添加单条消息(调用返回即已写入),适合随后立刻刷新快照的场景 + */ +- (void)addMessageSync:(XPMessageItem *)message; + +/** + * 批量添加消息 + * @param messages 要添加的消息数组 + */ +- (void)addMessages:(NSArray *)messages; + +/** + * 移除消息 + * @param message 要移除的消息项 + */ +- (void)removeMessage:(XPMessageItem *)message; + +/** + * 清空所有消息 + */ +- (void)clearAllMessages; + +/** + * 根据显示类型获取消息 + * @param displayType 显示类型:1=所有, 2=聊天, 3=礼物 + * @return 对应类型的消息数组 + */ +- (NSArray *)messagesForDisplayType:(NSInteger)displayType; + +/** + * 手动清理旧消息 + * 当消息数量超过 maxMessages 时自动调用 + */ +- (void)cleanupOldMessages; + +/** + * 获取当前消息总数 + * @return 消息总数 + */ +- (NSInteger)totalMessageCount; + +/** + * 获取指定类型的消息数量 + * @param displayType 显示类型 + * @return 消息数量 + */ +- (NSInteger)messageCountForDisplayType:(NSInteger)displayType; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageDataSourceManager.m b/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageDataSourceManager.m new file mode 100644 index 00000000..3fe2dac0 --- /dev/null +++ b/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageDataSourceManager.m @@ -0,0 +1,254 @@ +// +// XPMessageDataSourceManager.m +// YUMI +// +// Created by YUMI on 2024/12/19. +// + +#import "XPMessageDataSourceManager.h" + +@interface XPMessageDataSourceManager () + +/// 所有消息(内部可变) +@property (nonatomic, strong) NSMutableArray *allMessages; + +/// 聊天消息(内部可变) +@property (nonatomic, strong) NSMutableArray *chatMessages; + +/// 礼物消息(内部可变) +@property (nonatomic, strong) NSMutableArray *giftMessages; + +/// 数据操作串行队列 +@property (nonatomic, strong) dispatch_queue_t dataQueue; + +@end + +@implementation XPMessageDataSourceManager + +#pragma mark - Initialization + +- (instancetype)init { + return [self initWithMaxMessages:1000]; +} + +- (instancetype)initWithMaxMessages:(NSInteger)maxMessages { + self = [super init]; + if (self) { + _allMessages = [NSMutableArray array]; + _chatMessages = [NSMutableArray array]; + _giftMessages = [NSMutableArray array]; + _dataQueue = dispatch_queue_create("com.yumi.message.data", DISPATCH_QUEUE_SERIAL); + _maxMessages = maxMessages; + } + return self; +} + +#pragma mark - Public Methods + +- (void)addMessage:(XPMessageItem *)message { + if (!message) return; + + dispatch_async(self.dataQueue, ^{ + NSMutableArray *allMessages = (NSMutableArray *)_allMessages; + NSMutableArray *chatMessages = (NSMutableArray *)_chatMessages; + NSMutableArray *giftMessages = (NSMutableArray *)_giftMessages; + + [allMessages addObject:message]; + + // 根据消息类型分发到对应的子数据源 + if ([message isChatMessage]) { + [chatMessages addObject:message]; + } + if ([message isGiftMessage]) { + [giftMessages addObject:message]; + } + + // 检查是否需要清理 + if (allMessages.count > self.maxMessages) { + [self cleanupOldMessages]; + } + }); +} + +// 同步写入版本,确保调用返回时数据已落入数组(适合随后立刻刷新快照的场景) +- (void)addMessageSync:(XPMessageItem *)message { + if (!message) return; + + dispatch_sync(self.dataQueue, ^{ + NSMutableArray *allMessages = (NSMutableArray *)_allMessages; + NSMutableArray *chatMessages = (NSMutableArray *)_chatMessages; + NSMutableArray *giftMessages = (NSMutableArray *)_giftMessages; + + [allMessages addObject:message]; + if ([message isChatMessage]) { + [chatMessages addObject:message]; + } + if ([message isGiftMessage]) { + [giftMessages addObject:message]; + } + if (allMessages.count > self.maxMessages) { + [self cleanupOldMessages]; + } + }); +} + +- (void)addMessages:(NSArray *)messages { + if (!messages || messages.count == 0) return; + + dispatch_async(self.dataQueue, ^{ + NSMutableArray *allMessages = (NSMutableArray *)_allMessages; + NSMutableArray *chatMessages = (NSMutableArray *)_chatMessages; + NSMutableArray *giftMessages = (NSMutableArray *)_giftMessages; + + for (XPMessageItem *message in messages) { + [allMessages addObject:message]; + + // 根据消息类型分发到对应的子数据源 + if ([message isChatMessage]) { + [chatMessages addObject:message]; + } + if ([message isGiftMessage]) { + [giftMessages addObject:message]; + } + } + + // 检查是否需要清理 + if (allMessages.count > self.maxMessages) { + [self cleanupOldMessages]; + } + }); +} + +- (void)removeMessage:(XPMessageItem *)message { + if (!message) return; + + dispatch_async(self.dataQueue, ^{ + NSMutableArray *allMessages = (NSMutableArray *)_allMessages; + NSMutableArray *chatMessages = (NSMutableArray *)_chatMessages; + NSMutableArray *giftMessages = (NSMutableArray *)_giftMessages; + + [allMessages removeObject:message]; + [chatMessages removeObject:message]; + [giftMessages removeObject:message]; + }); +} + +- (void)clearAllMessages { + dispatch_async(self.dataQueue, ^{ + NSMutableArray *allMessages = (NSMutableArray *)_allMessages; + NSMutableArray *chatMessages = (NSMutableArray *)_chatMessages; + NSMutableArray *giftMessages = (NSMutableArray *)_giftMessages; + + [allMessages removeAllObjects]; + [chatMessages removeAllObjects]; + [giftMessages removeAllObjects]; + }); +} + +- (NSArray *)messagesForDisplayType:(NSInteger)displayType { + __block NSArray *result = nil; + dispatch_sync(self.dataQueue, ^{ + switch (displayType) { + case 1: // 所有消息 + result = [_allMessages copy]; + break; + case 2: // 聊天消息 + result = [_chatMessages copy]; + break; + case 3: // 礼物消息 + result = [_giftMessages copy]; + break; + default: + result = [_allMessages copy]; + break; + } + }); + return result; +} + +- (void)cleanupOldMessages { + if (_allMessages.count <= self.maxMessages) return; + + // 计算需要移除的消息数量(移除一半) + NSInteger removeCount = self.maxMessages / 2; + NSArray *itemsToRemove = [_allMessages subarrayWithRange:NSMakeRange(0, removeCount)]; + + NSMutableArray *allMessages = (NSMutableArray *)_allMessages; + NSMutableArray *chatMessages = (NSMutableArray *)_chatMessages; + NSMutableArray *giftMessages = (NSMutableArray *)_giftMessages; + + // 从所有数据源中移除这些消息 + for (XPMessageItem *item in itemsToRemove) { + [allMessages removeObject:item]; + [chatMessages removeObject:item]; + [giftMessages removeObject:item]; + } + + NSLog(@"[XPMessageDataSourceManager] Cleaned up %ld old messages, remaining: %ld", + (long)removeCount, (long)_allMessages.count); +} + +- (NSInteger)totalMessageCount { + __block NSInteger count = 0; + dispatch_sync(self.dataQueue, ^{ + count = _allMessages.count; + }); + return count; +} + +- (NSInteger)messageCountForDisplayType:(NSInteger)displayType { + __block NSInteger count = 0; + dispatch_sync(self.dataQueue, ^{ + switch (displayType) { + case 1: // 所有消息 + count = _allMessages.count; + break; + case 2: // 聊天消息 + count = _chatMessages.count; + break; + case 3: // 礼物消息 + count = _giftMessages.count; + break; + default: + count = _allMessages.count; + break; + } + }); + return count; +} + +#pragma mark - Readonly Properties + +- (NSArray *)allMessages { + __block NSArray *result = nil; + dispatch_sync(self.dataQueue, ^{ + result = [_allMessages copy]; + }); + return result; +} + +- (NSArray *)chatMessages { + __block NSArray *result = nil; + dispatch_sync(self.dataQueue, ^{ + result = [_chatMessages copy]; + }); + return result; +} + +- (NSArray *)giftMessages { + __block NSArray *result = nil; + dispatch_sync(self.dataQueue, ^{ + result = [_giftMessages copy]; + }); + return result; +} + +#pragma mark - Debug + +- (NSString *)description { + return [NSString stringWithFormat:@"", + self, (long)[self totalMessageCount], (long)[self messageCountForDisplayType:2], + (long)[self messageCountForDisplayType:3], (long)self.maxMessages]; +} + +@end diff --git a/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageInfoModel.h b/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageInfoModel.h index 30b28788..1c93df8c 100644 --- a/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageInfoModel.h +++ b/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageInfoModel.h @@ -6,6 +6,7 @@ // #import +#import "PIBaseModel.h" #import "PIRoomPhotoAlbumItemModel.h" NS_ASSUME_NONNULL_BEGIN diff --git a/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageItem.h b/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageItem.h new file mode 100644 index 00000000..d86c641c --- /dev/null +++ b/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageItem.h @@ -0,0 +1,60 @@ +// +// XPMessageItem.h +// YUMI +// +// Created by YUMI on 2024/12/19. +// + +#import +#import "XPMessageInfoModel.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * DiffableDataSource 兼容的消息项模型 + * 用于替代原有的多数据源架构,提供统一的消息管理 + */ +@interface XPMessageItem : NSObject + +/// 消息模型 +@property (nonatomic, strong) XPMessageInfoModel *messageModel; + +/// 唯一标识符,用于 DiffableDataSource 的 itemIdentifier +@property (nonatomic, strong) NSString *uniqueIdentifier; + +/// 时间戳,用于排序和清理 +@property (nonatomic, assign) NSTimeInterval timestamp; + +/// 显示类型:1=所有消息, 2=聊天消息, 3=礼物消息 +@property (nonatomic, assign) NSInteger displayType; + +/** + * 初始化方法 + * @param model 消息模型 + * @param identifier 唯一标识符 + */ +- (instancetype)initWithMessageModel:(XPMessageInfoModel *)model + uniqueIdentifier:(NSString *)identifier; + +/** + * 判断是否为聊天消息 + * @return YES 如果是聊天消息 + */ +- (BOOL)isChatMessage; + +/** + * 判断是否为礼物消息 + * @return YES 如果是礼物消息 + */ +- (BOOL)isGiftMessage; + +/** + * 判断是否应该显示在指定类型中 + * @param displayType 显示类型 + * @return YES 如果应该显示 + */ +- (BOOL)shouldDisplayInType:(NSInteger)displayType; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageItem.m b/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageItem.m new file mode 100644 index 00000000..c48a8b92 --- /dev/null +++ b/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageItem.m @@ -0,0 +1,99 @@ +// +// XPMessageItem.m +// YUMI +// +// Created by YUMI on 2024/12/19. +// + +#import "XPMessageItem.h" + +@implementation XPMessageItem + +- (instancetype)initWithMessageModel:(XPMessageInfoModel *)model + uniqueIdentifier:(NSString *)identifier { + self = [super init]; + if (self) { + _messageModel = model; + _uniqueIdentifier = identifier; + _timestamp = [[NSDate date] timeIntervalSince1970]; + _displayType = 1; // 默认显示在所有类型中 + } + return self; +} + +- (BOOL)isChatMessage { + if (!self.messageModel) return NO; + + // 文本消息 (NIMMessageTypeText = 0) + if (self.messageModel.first == 0) { + return YES; + } + + // 表情消息 (CustomMessageType_Face = 9) + if (self.messageModel.first == 9) { + return YES; + } + + return NO; +} + +- (BOOL)isGiftMessage { + if (!self.messageModel) return NO; + + // 礼物相关消息类型 + switch (self.messageModel.first) { + case 3: // CustomMessageType_Gift - 单人送礼物 + case 63: // CustomMessageType_RoomBoom - 房间爆炸 + case 26: // CustomMessageType_Candy_Tree - 糖果树 + case 12: // CustomMessageType_AllMicroSend - 全麦送礼物 + return YES; + default: + return NO; + } +} + +- (BOOL)shouldDisplayInType:(NSInteger)displayType { + switch (displayType) { + case 1: // 所有消息 + return YES; + case 2: // 聊天消息 + return [self isChatMessage]; + case 3: // 礼物消息 + return [self isGiftMessage]; + default: + return YES; + } +} + +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone { + XPMessageItem *copy = [[XPMessageItem alloc] init]; + copy.messageModel = self.messageModel; + copy.uniqueIdentifier = self.uniqueIdentifier; + copy.timestamp = self.timestamp; + copy.displayType = self.displayType; + return copy; +} + +#pragma mark - NSObject + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[XPMessageItem class]]) { + return NO; + } + + XPMessageItem *other = (XPMessageItem *)object; + return [self.uniqueIdentifier isEqualToString:other.uniqueIdentifier]; +} + +- (NSUInteger)hash { + return [self.uniqueIdentifier hash]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"", + self, self.uniqueIdentifier, (long)self.displayType, self.timestamp]; +} + +@end diff --git a/YuMi/Modules/YMRoom/View/MessageContainerView/MsRoomMessageMainView.h b/YuMi/Modules/YMRoom/View/MessageContainerView/MsRoomMessageMainView.h index f1419ceb..6048d528 100644 --- a/YuMi/Modules/YMRoom/View/MessageContainerView/MsRoomMessageMainView.h +++ b/YuMi/Modules/YMRoom/View/MessageContainerView/MsRoomMessageMainView.h @@ -8,6 +8,7 @@ #import #import "RoomHostDelegate.h" #import "RoomGuestDelegate.h" +#import "XPRoomMessageContainerView.h" NS_ASSUME_NONNULL_BEGIN @interface MsRoomMessageMainView : UIView @@ -16,6 +17,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)handleNIMImageMessage:(NIMMessage *)message; ///0房间,1.聊天大厅 @property(nonatomic,assign) NSInteger type; + +@property(nonatomic,strong) XPRoomMessageContainerView *messageListView; + @end NS_ASSUME_NONNULL_END diff --git a/YuMi/Modules/YMRoom/View/MessageContainerView/MsRoomMessageMainView.m b/YuMi/Modules/YMRoom/View/MessageContainerView/MsRoomMessageMainView.m index 047de7f8..0575e358 100644 --- a/YuMi/Modules/YMRoom/View/MessageContainerView/MsRoomMessageMainView.m +++ b/YuMi/Modules/YMRoom/View/MessageContainerView/MsRoomMessageMainView.m @@ -5,7 +5,7 @@ // Created by duoban on 2024/5/10. // #import "MsRoomMessageMainView.h" -#import "XPRoomMessageContainerView.h" + #import "ClientConfig.h" #import "UserInfoModel.h" #import @@ -17,7 +17,7 @@ @interface MsRoomMessageMainView() -@property(nonatomic,strong) XPRoomMessageContainerView *messageListView; + ///房间的代理 @property (nonatomic,weak) id hostDelegate; diff --git a/YuMi/Modules/YMRoom/View/MessageContainerView/XPRoomMessageContainerView.h b/YuMi/Modules/YMRoom/View/MessageContainerView/XPRoomMessageContainerView.h index 128c9794..a239f6be 100644 --- a/YuMi/Modules/YMRoom/View/MessageContainerView/XPRoomMessageContainerView.h +++ b/YuMi/Modules/YMRoom/View/MessageContainerView/XPRoomMessageContainerView.h @@ -10,6 +10,10 @@ #import "RoomGuestDelegate.h" #import +// DiffableDataSource 相关导入 +#import "XPMessageItem.h" +#import "XPMessageDataSourceManager.h" + NS_ASSUME_NONNULL_BEGIN @class XPRoomMessageContainerView, AttachmentModel, NIMBroadcastMessage; @protocol XPRoomMessageContainerViewDelegate @@ -30,6 +34,31 @@ NS_ASSUME_NONNULL_BEGIN - (void)handleBroadcastMessageAttachment:(AttachmentModel *)attachment; - (void)handleBroadcastMessage:(NIMBroadcastMessage *)message; +#pragma mark - DiffableDataSource Support + +/// 是否使用 DiffableDataSource(开关控制) +@property (nonatomic, assign) BOOL useDiffableDataSource; + +/// DiffableDataSource 数据源 +@property (nonatomic, strong) UITableViewDiffableDataSource *diffableDataSource; + +/// 数据源管理器 +@property (nonatomic, strong) XPMessageDataSourceManager *dataSourceManager; + +/// 设置 DiffableDataSource +- (void)setupDiffableDataSource; + +/// 使用 DiffableDataSource 添加消息 +- (void)addMessageWithDiffableDataSource:(NIMMessage *)message; + +/// 使用 DiffableDataSource 切换显示类型 +- (void)changeTypeWithDiffableDataSource:(NSInteger)type; + +/// 使用 DiffableDataSource 滚动到底部 +- (void)scrollToBottomWithDiffableDataSource:(BOOL)animated; + +/// 更新 DiffableDataSource 快照 +- (void)updateDiffableDataSourceSnapshot; @end diff --git a/YuMi/Modules/YMRoom/View/MessageContainerView/XPRoomMessageContainerView.m b/YuMi/Modules/YMRoom/View/MessageContainerView/XPRoomMessageContainerView.m index 333623b3..784b1738 100644 --- a/YuMi/Modules/YMRoom/View/MessageContainerView/XPRoomMessageContainerView.m +++ b/YuMi/Modules/YMRoom/View/MessageContainerView/XPRoomMessageContainerView.m @@ -116,8 +116,17 @@ NSString * const kRoomShowTopicKey = @"kRoomShowTopicKey"; self.isLoadHistoryMessage = YES; self.forceReloadNextAppend = NO; self.hostDelegate = delegate; + + // DiffableDataSource 支持(默认关闭) + self.useDiffableDataSource = NO; + [self initSubViews]; [self initSubViewConstraints]; + + // 如果启用 DiffableDataSource,则设置它 + if (self.useDiffableDataSource) { + [self setupDiffableDataSource]; + } } return self; } @@ -1498,31 +1507,39 @@ NSString * const kRoomShowTopicKey = @"kRoomShowTopicKey"; #pragma mark - UITableViewDelegate And UITableViewDataSource - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - NSArray *source = @[]; - switch (self.displayType) { - case 1: - source = self.datasource; - break; - case 2: - source = self.datasource_chat; - break; - case 3: - source = self.datasource_gift; - break; - - default: - source = self.datasource; - break; + // 在 DiffableDataSource 模式下,使用 dataSourceManager 的数据计算行高 + XPMessageInfoModel *model = nil; + if (self.useDiffableDataSource) { + NSArray *messages = [self.dataSourceManager messagesForDisplayType:self.displayType]; + if (indexPath.row < messages.count) { + XPMessageItem *item = [messages objectAtIndex:indexPath.row]; + model = item.messageModel; + } + } else { + NSArray *source = @[]; + switch (self.displayType) { + case 1: + source = self.datasource; + break; + case 2: + source = self.datasource_chat; + break; + case 3: + source = self.datasource_gift; + break; + default: + source = self.datasource; + break; + } + model = [source xpSafeObjectAtIndex:indexPath.row]; } - XPMessageInfoModel *model = [source xpSafeObjectAtIndex:indexPath.row]; - // 预计算并缓存高度,避免使用 UITableViewAutomaticDimension if (model.rowHeight <= 0) { [self calculateRowHeightForModel:model]; } - if (model.first == CustomMessageType_RedPacket && model.second == Custom_Message_Sub_LuckyPackage) { + if (model && model.first == CustomMessageType_RedPacket && model.second == Custom_Message_Sub_LuckyPackage) { return model.rowHeight; } @@ -1853,4 +1870,166 @@ NSString * const kRoomShowTopicKey = @"kRoomShowTopicKey"; return _messageParser; } +#pragma mark - DiffableDataSource Implementation + +- (void)setupDiffableDataSource { + if (!self.useDiffableDataSource) return; + + // 初始化数据源管理器 + self.dataSourceManager = [[XPMessageDataSourceManager alloc] initWithMaxMessages:1000]; + + // 设置 DiffableDataSource + self.diffableDataSource = [[UITableViewDiffableDataSource alloc] initWithTableView:self.messageTableView cellProvider:^UITableViewCell *(UITableView *tableView, NSIndexPath *indexPath, XPMessageItem *item) { + return [self dequeueCellForMessageItem:item atIndexPath:indexPath]; + }]; + + // 重要:设置 UITableView 的 dataSource + self.messageTableView.dataSource = self.diffableDataSource; + + // 设置初始快照 + NSDiffableDataSourceSnapshot *snapshot = [[NSDiffableDataSourceSnapshot alloc] init]; + [snapshot appendSectionsWithIdentifiers:@[@"messages"]]; + [self.diffableDataSource applySnapshot:snapshot animatingDifferences:NO]; + + NSLog(@"[XPRoomMessageContainerView] DiffableDataSource setup completed, tableView.dataSource=%@", self.messageTableView.dataSource); +} + +- (UITableViewCell *)dequeueCellForMessageItem:(XPMessageItem *)item atIndexPath:(NSIndexPath *)indexPath { + NSLog(@"[DiffableDataSource] dequeueCellForMessageItem called, indexPath=%@, item=%@", indexPath, item); + + XPMessageInfoModel *model = item.messageModel; + NSString *cellKey = [NSString isEmpty:model.cellKey] ? NSStringFromClass([XPRoomMessageTableViewCell class]) : model.cellKey; + + // 复用现有的 cell 创建逻辑 + if(model.first == 59 || // CustomMessageType_Room_Album + (model.first == 1 && model.second == 1)){ // CustomMessageType_User_Enter_Room && Custom_Message_Sub_Pic_Message + PIRoomMessagePhotoAlbumCell * cell = [self.messageTableView dequeueReusableCellWithIdentifier:NSStringFromClass([PIRoomMessagePhotoAlbumCell class])]; + cell.delegate = self; + cell.messageInfo = model; + cell.roomType = self.hostDelegate.getRoomInfo.type; + return cell; + } else if (model.first == 60 && model.second == 1) { // CustomMessageType_RedPacket && Custom_Message_Sub_LuckyPackage + LuckyPackageMessageTableViewCell *cell = [self.messageTableView dequeueReusableCellWithIdentifier:@"LuckyPackageMessageTableViewCell" forIndexPath:indexPath]; + cell.dataSource = model.customInfo; + return cell; + } else if (model.first == 1 && model.second == 1) { // CustomMessageType_User_Enter_Room && Custom_Message_Sub_User_Enter_Room + CPEnterRoomTableViewCell *cell = [self.messageTableView dequeueReusableCellWithIdentifier:@"CPEnterRoomTableViewCell" forIndexPath:indexPath]; + cell.cpIndex = [[model.customInfo objectForKey:@"cpIndex"] integerValue]; + cell.dataSource = model.customInfo; + return cell; + } + else if (model.isBoom) { + cellKey = @"XPRoomMessageTableViewCell_Boom"; + } + + XPRoomMessageTableViewCell * cell = [self.messageTableView dequeueReusableCellWithIdentifier:cellKey]; + cell.delegate = self; + cell.messageInfo = model; + return cell; +} + +- (void)addMessageWithDiffableDataSource:(NIMMessage *)message { + NSLog(@"[DiffableDataSource] addMessageWithDiffableDataSource called, messageType=%ld", (long)message.messageType); + + if (!self.useDiffableDataSource) { + NSLog(@"[DiffableDataSource] useDiffableDataSource is NO, falling back to addRoomMessage"); + // 回退到原有方法 + [self addRoomMessage:message]; + return; + } + + // parser 内部创建了 UI 对象(如 NetImageView),必须在主线程执行 + dispatch_async(dispatch_get_main_queue(), ^{ + // 解析消息(主线程) + XPMessageInfoModel *model = [self.messageParser parseMessageAttribute:message]; + if (!model) { + NSLog(@"[DiffableDataSource] Failed to parse message, messageType=%ld", (long)message.messageType); + return; + } + + NSLog(@"[DiffableDataSource] Parsed message successfully, first=%ld, second=%ld", (long)model.first, (long)model.second); + + // 创建消息项(主线程) + NSString *identifier = [NSString stringWithFormat:@"%@_%f", + message.from, + message.timestamp]; + XPMessageItem *item = [[XPMessageItem alloc] initWithMessageModel:model uniqueIdentifier:identifier]; + + NSLog(@"[DiffableDataSource] Created XPMessageItem, identifier=%@", identifier); + + // 添加到数据源管理器(同步写入,保证后续快照看到最新数据) + if ([self.dataSourceManager respondsToSelector:@selector(addMessageSync:)]) { + [self.dataSourceManager addMessageSync:item]; + } else { + [self.dataSourceManager addMessage:item]; + } + + NSLog(@"[DiffableDataSource] Added to dataSourceManager, total count=%ld", (long)[self.dataSourceManager totalMessageCount]); + + // 直接更新 UI 快照 + [self updateDiffableDataSourceSnapshot]; + }); +} + +- (void)changeTypeWithDiffableDataSource:(NSInteger)type { + if (self.displayType == type) return; + + self.displayType = type; + + if (!self.useDiffableDataSource) { + // 回退到原有方法 + [self changeType:type]; + return; + } + + // 更新 tableHeaderView + if (self.displayType == 1) { + self.messageTableView.tableHeaderView = self.headerView; + } else { + self.messageTableView.tableHeaderView = nil; + } + + // 更新数据源 + [self updateDiffableDataSourceSnapshot]; + + // 延迟滚动到底部 + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self scrollToBottomWithDiffableDataSource:YES]; + }); +} + +- (void)scrollToBottomWithDiffableDataSource:(BOOL)animated { + if (!self.useDiffableDataSource) { + [self scrollToBottom:animated]; + return; + } + + NSArray *messages = [self.dataSourceManager messagesForDisplayType:self.displayType]; + if (messages.count > 0) { + NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:messages.count - 1 inSection:0]; + [self.messageTableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:animated]; + } +} + +- (void)updateDiffableDataSourceSnapshot { + if (!self.useDiffableDataSource) return; + + NSArray *messages = [self.dataSourceManager messagesForDisplayType:self.displayType]; + NSLog(@"[DiffableDataSource] updateDiffableDataSourceSnapshot called, displayType=%ld, messages count=%ld", (long)self.displayType, (long)messages.count); + + NSDiffableDataSourceSnapshot *snapshot = [[NSDiffableDataSourceSnapshot alloc] init]; + [snapshot appendSectionsWithIdentifiers:@[@"messages"]]; + + // 注意:appendItemsWithIdentifiers 需要的是对象本身作为“标识符”(需实现 isEqual/hash) + if (messages.count > 0) { + [snapshot appendItemsWithIdentifiers:messages intoSectionWithIdentifier:@"messages"]; + NSLog(@"[DiffableDataSource] Added %ld items to snapshot", (long)messages.count); + } else { + NSLog(@"[DiffableDataSource] No messages to add to snapshot"); + } + + NSLog(@"[DiffableDataSource] Applying snapshot with %ld items", (long)snapshot.numberOfItems); + [self.diffableDataSource applySnapshot:snapshot animatingDifferences:YES]; +} + @end diff --git a/YuMi/Modules/YMRoom/View/SendGiftView/GiftComboManager.h b/YuMi/Modules/YMRoom/View/SendGiftView/GiftComboManager.h index 858abd07..b64686b5 100644 --- a/YuMi/Modules/YMRoom/View/SendGiftView/GiftComboManager.h +++ b/YuMi/Modules/YMRoom/View/SendGiftView/GiftComboManager.h @@ -92,7 +92,6 @@ NS_ASSUME_NONNULL_BEGIN // 新增:连击状态检查方法 - (BOOL)isComboStateValid; - (NSDictionary * _Nonnull)getComboStateInfo; -- (void)printComboState; // 🔧 新增:状态通知方法 - (void)setUserComboState:(BOOL)isCombo forUser:(NSString *)uid; diff --git a/YuMi/Modules/YMRoom/View/SendGiftView/GiftComboManager.m b/YuMi/Modules/YMRoom/View/SendGiftView/GiftComboManager.m index 6017128e..9933d4f5 100644 --- a/YuMi/Modules/YMRoom/View/SendGiftView/GiftComboManager.m +++ b/YuMi/Modules/YMRoom/View/SendGiftView/GiftComboManager.m @@ -71,7 +71,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific #pragma mark - 单例方法 - (void)dealloc { - NSLog(@"[Combo effect] 🗑️ GiftComboManager dealloc开始 - %p", self); // 🔥 修复:确保所有timer都被停止 [self forceStopAllTimers]; @@ -87,7 +86,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific self.enableCombo = NO; self.actionCallback = nil; - NSLog(@"[Combo effect] 🗑️ GiftComboManager dealloc完成 - %p", self); } + (instancetype)sharedManager { @@ -136,20 +134,14 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific // 开始连击,重置 - (void)reset { - NSLog(@"[Combo effect] 🔄 开始连击重置 - combo: %ld -> 1, enableCombo: %@, actionCallback: %@", - (long)self.combo, - self.enableCombo ? @"YES" : @"NO", - self.actionCallback ? @"可用" : @"为空"); - + // 🔥 修复:检查是否正在重置过程中 if (self.isCombing && self.combo == 0) { - NSLog(@"[Combo effect] ⚠️ 正在重置过程中,跳过重复重置"); return; } // 🔥 修复:如果actionCallback为空,延迟执行reset if (!self.actionCallback) { - NSLog(@"[Combo effect] ⚠️ actionCallback为空,延迟执行reset"); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self reset]; }); @@ -158,7 +150,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific // 🔥 修复:确保enableCombo状态正确 if (!self.enableCombo) { - NSLog(@"[Combo effect] ⚠️ enableCombo为NO,尝试重新激活"); self.enableCombo = YES; } @@ -167,7 +158,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific _errorMessage = @""; // 验证重置后的状态 - NSLog(@"[Combo effect] 🔍 重置后验证 - combo: %ld, enableCombo: %@", (long)self.combo, self.enableCombo ? @"YES" : @"NO"); // 发送通知,让 GiftComboView 重置显示 [[NSNotificationCenter defaultCenter] postNotificationName:@"ComboCountReset" object:nil]; @@ -177,7 +167,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific // 检查是否应该显示连击面板 - 确保在主线程执行UI回调 if (self.actionCallback) { - NSLog(@"[Combo effect] 📱 触发连击面板显示回调"); @kWeakify(self); [self safeExecuteUIBlock:^{ @kStrongify(self); @@ -186,11 +175,9 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific } }]; } else { - NSLog(@"[Combo effect] ⚠️ actionCallback为空,不显示连击面板"); } if (self.handleRoomUIChanged) { - NSLog(@"[Combo effect] 🎮 隐藏房间UI元素"); @kWeakify(self); [self safeExecuteUIBlock:^{ @kStrongify(self); @@ -199,7 +186,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific } }]; } - NSLog(@"[Combo effect] ✅ 连击重置完成 - isCombing: %@, enableCombo: %@", self.isCombing ? @"YES" : @"NO", self.enableCombo ? @"YES" : @"NO"); } - (void)registerActions:(void (^)(ComboActionType))action { @@ -210,12 +196,10 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific // 新增:实现其他简化接口方法 - (void)activate { - NSLog(@"[Combo effect] 🔧 激活连击功能"); self.enableCombo = YES; } - (void)deactivate { - NSLog(@"[Combo effect] 🔧 停用连击功能"); self.enableCombo = NO; } @@ -226,25 +210,21 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific - (NSInteger)currentCount { // 确保连击计数最少为 1 if (self.combo < 1) { - NSLog(@"[Combo effect] ⚠️ currentCount: 连击计数异常,重置为1 - 当前: %ld", (long)self.combo); self.combo = 1; } return self.combo; } - (void)incrementCount { - NSLog(@"[Combo effect] 🔢 增加连击计数 - 当前: %ld -> %ld", (long)self.combo, (long)(self.combo + 1)); self.combo += 1; } - (void)clear { - NSLog(@"[Combo effect] 🗑️ 清除连击状态"); [self forceBoomStateReset]; // 🔥 修复:确保在主线程执行UI回调,并检查回调是否有效 if (self.actionCallback) { - NSLog(@"[Combo effect] 📱 触发连击面板移除回调"); @kWeakify(self); [self safeExecuteUIBlock:^{ @kStrongify(self); @@ -256,7 +236,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific } - (void)send { - NSLog(@"[Combo effect] 📤 发送连击礼物"); [self sendGift]; } @@ -273,7 +252,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific } - (void)handleError:(NSError *)error { - NSLog(@"[Combo effect] ❌ 处理错误: %@", error.localizedDescription); self.errorMessage = error.localizedDescription; } @@ -288,33 +266,20 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific - (void)forceBoomStateReset { - NSLog(@"[Combo effect] 🚨 执行强制Boom连击状态重置"); // 1. 立即停止所有定时器(无条件停止) - NSLog(@"[Combo effect] ⏰ 停止所有定时器"); [self forceStopAllTimers]; // 2. 清空所有队列 - NSLog(@"[Combo effect] 🗑️ 清空所有队列"); [self clearAllQueues]; // 3. 重置所有状态标志 - NSLog(@"[Combo effect] 🔄 重置状态标志 - isCombing: %@ -> NO", - self.isCombing ? @"YES" : @"NO"); self.isCombing = NO; // 4. 重置combo计数为0 _combo = 0; - NSLog(@"[Combo effect] 🔄 combo计数重置为0"); - - // 注意:不重置 enableCombo,保持连击功能可用状态 - // self.enableCombo = NO; // 移除这行,保持连击功能可用 - - // 注意:不清理 actionCallback,保持回调可用,以便重新进入连击状态 - // self.actionCallback = nil; // 移除这行,保持回调可用 // 5. 发送通知(优先级最高,通知所有相关组件) - NSLog(@"[Combo effect] 📢 发送强制重置通知"); dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:kBoomStateForceResetNotification object:nil]; @@ -322,19 +287,14 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific // 6. 强制恢复UI(确保底部栏和侧栏显示) if (self.handleRoomUIChanged) { - NSLog(@"[Combo effect] 🎮 恢复房间UI元素"); dispatch_async(dispatch_get_main_queue(), ^{ self.handleRoomUIChanged(NO); }); } - NSLog(@"[Combo effect] ✅ 强制重置完成 - enableCombo保持: %@, actionCallback保持: %@", - self.enableCombo ? @"YES" : @"NO", - self.actionCallback ? @"可用" : @"为空"); } // 无条件停止定时器 - (void)forceStopAllTimers { - NSLog(@"[Combo effect] ⏰ 强制停止所有Timer"); // 停止主定时器 if (self.timer) { @@ -387,23 +347,14 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific } } -// 新增:打印当前连击状态(用于调试) -- (void)printComboState { - NSDictionary *stateInfo = [self getComboStateInfo]; - // 检查并修复连击计数异常 - [self validateAndFixComboCount]; -} - // 新增:验证并修复连击计数 - (void)validateAndFixComboCount { @synchronized (self) { if (self.combo < 1) { - NSLog(@"[Combo effect] 🚨 检测到连击计数异常,自动修复 - 当前: %ld -> 1", (long)self.combo); self.combo = 1; } if (self.combo > 1000) { - NSLog(@"[Combo effect] 🚨 检测到连击计数异常,自动修复 - 当前: %ld -> 100", (long)self.combo); self.combo = 100; } } @@ -412,10 +363,8 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific #pragma mark - 处理飘屏逻辑 - (void)receiveGiftInfoForDisplayComboFlags:(GiftReceiveInfoModel *)receiveInfo container:(UIView *)container { - NSLog(@"[Combo effect] 🎪 收到连击飘屏请求 - combo: %ld, giftId: %ld", (long)receiveInfo.comboCount, (long)receiveInfo.gift.giftId); self.containerView = container; [self.uiQueue addObject:receiveInfo]; - NSLog(@"[Combo effect] 📊 连击飘屏队列数量: %ld", (long)self.uiQueue.count); [self startProcessingUIQueue]; } @@ -467,9 +416,7 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific } GiftReceiveInfoModel *receiveInfo = [self.uiQueue firstObject]; - NSLog(@"[Combo effect] 🎪 处理连击飘屏 - combo: %ld, giftId: %ld", (long)receiveInfo.comboCount, (long)receiveInfo.gift.giftId); [self.uiQueue xpSafeRemoveObjectAtIndex:0]; - NSLog(@"[Combo effect] �� 移除后UI动画队列数量: %ld", (long)self.uiQueue.count); dispatch_async(dispatch_get_main_queue(), ^{ [self handleGiftInfo:receiveInfo]; }); @@ -602,7 +549,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific dispatch_source_set_event_handler(self.timer, ^{ @kStrongify(self); if (!self) { - NSLog(@"[Combo effect] ⚠️ self已释放,忽略timer回调"); return; } [self processRequestQueue]; @@ -649,11 +595,10 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific }); } else if ([networkData isKindOfClass:[NSDictionary class]]) { // 处理包含info和metadata的字典 - NSDictionary *comboData = (NSDictionary *)networkData; - GiftReceiveInfoModel *info = comboData[@"info"]; - NSDictionary *metadata = comboData[@"metadata"]; +// NSDictionary *comboData = (NSDictionary *)networkData; +// GiftReceiveInfoModel *info = comboData[@"info"]; +// NSDictionary *metadata = comboData[@"metadata"]; // 这里可以添加相应的处理逻辑 - NSLog(@"[Combo effect] �� 处理网络数据 - info: %@, metadata: %@", info, metadata); } } else { [self stopProcessingQueue]; @@ -701,7 +646,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific sendType:(RoomSendGiftType)sendType giftNum:(NSString *)giftNum { - NSLog(@"[Combo effect] 🔧 统一配置连击参数"); self.giftInfo = giftInfo; self.sendGiftToUIDs = UIDs; @@ -712,9 +656,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific self.giftSourceType = sourceType; self.roomSendGiftType = sendType; self.giftNumPerTimes = giftNum; - - NSLog(@"[Combo effect] ✅ 连击参数配置完成 - giftId: %ld, targetCount: %ld", - (long)giftInfo.giftId, (long)UIDs.count); } - (BOOL)loadEnable { @@ -724,7 +665,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific #pragma mark - XPGiftPresenter - (void)sendGift { - NSLog(@"[Combo effect] 🎁 开始发送连击礼物 - combo: %ld, isCombing: %@", (long)self.combo, self.isCombing ? @"YES" : @"NO"); NSString *allUIDs = @""; for (NSString *item in self.sendGiftToUIDs) { @@ -744,25 +684,21 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific @"roomUid":self.roomUID }; - NSLog(@"[Combo effect] 📦 添加礼物请求到队列 - giftId: %ld, targetUids: %@", (long)self.giftInfo.giftId, allUIDs); [self.requestQueue addObject:dic]; [self startProcessingQueue]; } - (void)handleSendGift:(NSDictionary *)dic { NSString *allUIDs = [dic objectForKey:@"targetUids"]; - NSString *giftId = [dic objectForKey:@"giftId"]; - NSLog(@"[Combo effect] 🌐 开始调用送礼API - giftId: %@, targetUids: %@", giftId, allUIDs); +// NSString *giftId = [dic objectForKey:@"giftId"]; @kWeakify(self); [Api requestSendGift:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) { @kStrongify(self); if (!self) { - NSLog(@"[Combo effect] ⚠️ self已释放,忽略API回调"); return; } if (code == 200) { - NSLog(@"[Combo effect] ✅ 送礼API成功 - giftId: %@, combo: %ld", giftId, (long)self.combo); GiftReceiveInfoModel *receive = [GiftReceiveInfoModel modelWithJSON:data.data]; receive.sourceType = [[dic objectForKey:@"giftSource"] integerValue]; receive.roomSendGiftType = [[dic objectForKey:@"giftType"] integerValue]; @@ -774,11 +710,9 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific @"Price": @(receive.gift.goldPrice * receive.giftNum * array.count), @"isFromWinning":@(NO)}]; } else { - NSLog(@"[Combo effect] ❌ 送礼API失败 - code: %ld, msg: %@", (long)code, msg); // 区分错误类型,优化恢复策略 if (code > 500 && code < 600) { // 服务器错误,可能是临时问题,保持连击状态 - NSLog(@"[Combo effect] 🔄 服务器错误,保持连击状态 - code: %ld", (long)code); #if DEBUG self.errorMessage = [NSString stringWithFormat:@"服务器繁忙,请稍后重试 - %@", msg]; #else @@ -798,12 +732,10 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific // 临时错误,不重置连击状态,允许用户重试 } else if (code == 31005) { // 余额不足,需要重置连击状态 - NSLog(@"[Combo effect] 💰 余额不足,强制移除连击状态"); self.errorMessage = YMLocalizedString(@"XPCandyTreeInsufficientBalanceView1"); [self clear]; } else if (code == 8535) { // VIP等级不足,需要重置连击状态, 但不可能出现 - NSLog(@"[Combo effect] 👑 VIP等级不足,强制移除连击状态"); self.errorMessage = @""; [self clear]; } else { @@ -812,10 +744,8 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific // 对于网络错误等临时问题,保持连击状态 if (code >= 1000 && code < 2000) { // 网络相关错误,保持连击状态 - NSLog(@"[Combo effect] 🌐 网络错误,保持连击状态 - code: %ld", (long)code); } else { // 其他错误,重置连击状态 - NSLog(@"[Combo effect] 🚨 其他错误,强制移除连击状态 - code: %ld", (long)code); [self clear]; } } @@ -843,14 +773,12 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific - (void)handleSendGiftSuccess:(GiftReceiveInfoModel *)receive sourceData:(BaseModel *)response { - NSLog(@"[Combo effect] 🎉 连击礼物发送成功 - 当前combo: %ld", (long)self.combo); // 验证连击计数有效性 [self validateAndFixComboCount]; // 在API成功时递增combo计数 if (self.isCombing) { - NSLog(@"[Combo effect] 🔢 API成功,递增连击计数 - 当前: %ld -> %ld", (long)self.combo, (long)(self.combo + 1)); self.combo += 1; // 更新UI显示 - 确保在主线程执行UI回调 @@ -868,20 +796,14 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific // 确保连击计数最少为 1 NSInteger comboToSet = self.combo; if (comboToSet < 1) { - NSLog(@"[Combo effect] 🚨 发送云信消息时连击计数异常,修复为 1 - 当前: %ld", (long)comboToSet); comboToSet = 1; } [dic setObject:@(comboToSet) forKey:@"comboCount"]; - // 验证连击计数设置 - NSNumber *setComboCount = dic[@"comboCount"]; - NSLog(@"[Combo effect] 🔍 连击计数设置验证 - 设置值: %@, 当前combo: %ld", setComboCount, (long)self.combo); - self.sendGiftReceiveInfo = receive; if (self.handleComboSuccess) { - NSLog(@"[Combo effect] 📨 调用连击成功回调,发送云信消息"); self.handleComboSuccess(receive, dic); } @@ -894,11 +816,9 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific }]; } - NSLog(@"[Combo effect] ✅ 连击礼物处理完成"); } - (void)sendCustomMessage:(AttachmentModel *)attachment { - NSLog(@"[Combo effect] 📨 发送云信自定义消息 - combo: %ld", (long)self.combo); NIMMessage *message = [[NIMMessage alloc]init]; message.setting.quickDeliveryEnabled = YES; // 开启快速投递 @@ -924,9 +844,7 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific NSError *error = nil; [[NIMSDK sharedSDK].chatManager sendMessage:message toSession:session error:&error]; if (error) { - NSLog(@"[Combo effect] ❌ 云信消息发送失败 - error: %@", error.localizedDescription); } else { - NSLog(@"[Combo effect] ✅ 云信消息发送成功"); } }]; } @@ -941,24 +859,12 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific dispatch_async(dispatch_get_main_queue(), uiBlock); } } - -// 新增:性能监控方法 -- (void)logPerformanceMetrics { - NSLog(@"[Combo effect] 📊 性能指标 - 网络队列: %lu, 请求队列: %lu, 定时器: %@", - (unsigned long)self.networkQueue.count, - (unsigned long)self.requestQueue.count, - self.timer ? @"运行中" : @"已停止"); -} - // 🔧 新增:状态通知方法实现 - - (void)setUserComboState:(BOOL)isCombo forUser:(NSString *)uid { if (!uid || uid.length == 0) { - NSLog(@"[Combo effect] ⚠️ 用户ID为空,无法设置combo状态"); return; } - NSLog(@"[Combo effect] 🔔 通知动画管理器设置用户 %@ 的combo状态为: %@", uid, isCombo ? @"YES" : @"NO"); // 通过通知中心通知动画管理器 [[NSNotificationCenter defaultCenter] postNotificationName:@"GiftComboStateChanged" diff --git a/YuMi/Modules/YMRoom/View/SendGiftView/View/CountdownRingView.m b/YuMi/Modules/YMRoom/View/SendGiftView/View/CountdownRingView.m index c30af72a..17c9383f 100644 --- a/YuMi/Modules/YMRoom/View/SendGiftView/View/CountdownRingView.m +++ b/YuMi/Modules/YMRoom/View/SendGiftView/View/CountdownRingView.m @@ -29,12 +29,10 @@ - (void)dealloc { - NSLog(@"[Combo effect] 🗑️ CountdownRingView dealloc开始 - %p", self); // 🔥 修复:清理所有资源 [self cleanup]; - NSLog(@"[Combo effect] 🗑️ CountdownRingView dealloc完成 - %p", self); } - (instancetype)initWithFrame:(CGRect)frame duration:(NSInteger)duration { @@ -90,11 +88,9 @@ - (void)startCountdown { // 🔥 修复:检查运行状态,避免重复启动 if (self.isRunning) { - NSLog(@"[Combo effect] ⚠️ 倒计时已在运行中,忽略重复启动"); return; } - NSLog(@"[Combo effect] ⏰ 开始倒计时"); self.isRunning = YES; [self animateRing]; @@ -106,16 +102,13 @@ userInfo:nil repeats:YES]; - NSLog(@"[Combo effect] ⏰ 倒计时已启动"); } // 重置倒计时 - (void)resetCountdown { - NSLog(@"[Combo effect] ⏰ 重置倒计时开始"); // 🔥 修复:检查运行状态 if (!self.isRunning) { - NSLog(@"[Combo effect] ⚠️ 倒计时未运行,直接启动"); [self startCountdown]; return; } @@ -138,7 +131,6 @@ [self triggerLabelAnimation]; }); - NSLog(@"[Combo effect] ⏰ 重置倒计时完成"); } // 新增:触发标签动画 @@ -158,12 +150,10 @@ @synchronized(self) { // 🔥 修复:检查self是否存在 if (!self) { - NSLog(@"[Combo effect] ⚠️ self已释放,忽略updateCountdown调用"); return; } if (!self.isRunning) { - NSLog(@"[Combo effect] ⚠️ 倒计时已停止,忽略updateCountdown调用"); [self stopCountdown]; return; } @@ -171,7 +161,6 @@ self.remainingTime -= self.timerInterval; if (self.remainingTime <= 0) { - NSLog(@"[Combo effect] ⏰ 倒计时结束,准备触发回调"); // 🔥 优化:先保存回调,再停止Timer,最后调用回调 void (^completion)(void) = self.completionHandler; @@ -179,7 +168,6 @@ // 🔥 优化:安全调用回调,不涉及具体业务逻辑 if (completion) { - NSLog(@"[Combo effect] ⏰ 执行倒计时结束回调"); completion(); // 纯回调,由上层处理业务逻辑 } } @@ -189,16 +177,13 @@ - (void)stopCountdown { // 🔥 修复:防止重复停止 if (self.isStopping) { - NSLog(@"[Combo effect] ⚠️ 已在停止过程中,忽略重复调用"); return; } if (!self.isRunning) { - NSLog(@"[Combo effect] ⚠️ 倒计时未运行,无需停止"); return; } - NSLog(@"[Combo effect] ⏰ 停止倒计时开始"); self.isStopping = YES; @synchronized(self) { @@ -206,13 +191,11 @@ if (self.timer) { [self.timer invalidate]; self.timer = nil; - NSLog(@"[Combo effect] ⏰ Timer已停止"); } // 停止动画 if (self.foregroundLayer) { [self.foregroundLayer removeAllAnimations]; - NSLog(@"[Combo effect] ⏰ 动画已停止"); } // 重置UI状态 @@ -228,7 +211,6 @@ } self.isStopping = NO; - NSLog(@"[Combo effect] ⏰ 停止倒计时完成"); } // 环形倒计时动画 @@ -244,12 +226,10 @@ animation.removedOnCompletion = YES; // 🔥 修复:允许自动移除,避免内存泄漏 [self.foregroundLayer addAnimation:animation forKey:@"ringAnimation"]; - NSLog(@"[Combo effect] 🎬 环形动画已启动,时长: %.1f秒", self.remainingTime); } // 🔥 新增:完全清理方法(用于dealloc等场景) - (void)cleanup { - NSLog(@"[Combo effect] 🗑️ 完全清理开始"); @synchronized(self) { [self stopCountdown]; @@ -258,7 +238,6 @@ self.completionHandler = nil; } - NSLog(@"[Combo effect] 🗑️ 完全清理完成"); } @end diff --git a/YuMi/Modules/YMRoom/View/SendGiftView/View/GiftComboView.m b/YuMi/Modules/YMRoom/View/SendGiftView/View/GiftComboView.m index 97d7f5e7..1b14eb30 100644 --- a/YuMi/Modules/YMRoom/View/SendGiftView/View/GiftComboView.m +++ b/YuMi/Modules/YMRoom/View/SendGiftView/View/GiftComboView.m @@ -40,7 +40,6 @@ - (void)dealloc { - NSLog(@"[Combo effect] 🗑️ GiftComboView dealloc开始 - %p", self); // 🔥 修复:设置销毁标记,防止懒加载属性重新创建 self.isDeallocating = YES; @@ -69,7 +68,6 @@ [self.updateGoldQueue removeAllObjects]; self.feedbackGenerator = nil; - NSLog(@"[Combo effect] 🗑️ GiftComboView dealloc完成 - %p", self); } - (instancetype)init { @@ -151,7 +149,6 @@ @kStrongify(self); if (!self) return; // 🔥 修复:检查self是否还存在 - NSLog(@"[Combo effect] 📢 收到连击计数重置通知"); [self resetComboCount]; }]; } @@ -182,14 +179,10 @@ NSForegroundColorAttributeName: UIColorFromRGB(0xFFE07B), NSShadowAttributeName: shadow}]; self.comboCountLabel.attributedText = string; - - // 打印当前连击状态,确认计数正确 - [[GiftComboManager sharedManager] printComboState]; } // 新增方法:重置连击计数显示 - (void)resetComboCount { - NSLog(@"[Combo effect] 🔄 重置连击计数显示"); NSString *countStr = @"x1"; // 重置为 x1 NSShadow *shadow = [[NSShadow alloc] init]; shadow.shadowBlurRadius = 3; @@ -204,7 +197,6 @@ // 新增方法:使用指定的 combo 计数更新显示 - (void)updateCountWithCombo:(NSInteger)comboCount { - NSLog(@"[Combo effect] 🔢 使用指定计数更新连击次数显示 - combo: %ld", (long)comboCount); NSString *countStr = [NSString stringWithFormat:@"x%ld", comboCount]; NSShadow *shadow = [[NSShadow alloc] init]; shadow.shadowBlurRadius = 3; @@ -284,22 +276,18 @@ } - (void)setupTimer { - NSLog(@"[Combo effect] ⏰ 设置连击倒计时"); @kWeakify(self); [self.countdownRingView setupCompletionHandler:^{ @kStrongify(self); - NSLog(@"[Combo effect] ⏰ 连击倒计时结束,触发强制移除"); self.userInteractionEnabled = NO; [[GiftComboManager sharedManager] clear]; }]; [self.countdownRingView startCountdown]; - NSLog(@"[Combo effect] ⏰ 连击倒计时已启动"); } - (void)handleTap { static BOOL isHandlingTap = NO; if (isHandlingTap) { - NSLog(@"[Combo effect] ⚠️ 点击间隔过短,忽略此次点击"); return; } @@ -308,22 +296,17 @@ if (self.feedbackGenerator) { @try { [self.feedbackGenerator impactOccurred]; - NSLog(@"[Combo effect] 📳 震动反馈已触发"); } @catch (NSException *exception) { - NSLog(@"[Combo effect] ⚠️ 震动反馈失败: %@", exception.reason); } } else { - NSLog(@"[Combo effect] ⚠️ 震动反馈生成器未初始化"); } } else { - NSLog(@"[Combo effect] ⚠️ 设备不支持震动反馈 (iOS < 10.0)"); } #if RELEASE isHandlingTap = YES; #endif - NSLog(@"[Combo effect] 👆 连击面板被点击,发送礼物"); // 移除用户点击时的combo递增逻辑,改为在API成功时递增 // NSInteger comboCount = [[GiftComboManager sharedManager] loadComboCountFromSendGiftView]; @@ -333,7 +316,6 @@ [[GiftComboManager sharedManager] sendGift]; [self.playImageView startAnimation]; [self.countdownRingView resetCountdown]; - NSLog(@"[Combo effect] ⏰ 重置连击倒计时"); // 处理点击事件 // 延迟重置标志位 @@ -354,7 +336,6 @@ if (@available(iOS 10.0, *)) { if (self.feedbackGenerator) { [self.feedbackGenerator prepare]; - NSLog(@"[Combo effect] 📳 震动反馈已重新准备"); } } } diff --git a/YuMi/Modules/YMRoom/View/SendGiftView/View/XPSendGiftView.m b/YuMi/Modules/YMRoom/View/SendGiftView/View/XPSendGiftView.m index 2eb5e2e7..8ae54328 100644 --- a/YuMi/Modules/YMRoom/View/SendGiftView/View/XPSendGiftView.m +++ b/YuMi/Modules/YMRoom/View/SendGiftView/View/XPSendGiftView.m @@ -132,18 +132,15 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; // 移除连击相关视图 - (void)removeAllComboRelatedViews { - NSLog(@"[Combo effect] 🗑️ 开始移除连击相关视图"); // 移除连击面板 if (self.comboView && self.comboView.superview) { - NSLog(@"[Combo effect] 🗑️ 移除comboView"); [self.comboView stopTimer]; [self.comboView endCombo]; [self.comboView removeFromSuperview]; self.comboView = nil; } else if (self.comboView) { // 🔥 修复:即使没有superview也要清理 - NSLog(@"[Combo effect] 🗑️ comboView存在但无superview,直接清理"); [self.comboView stopTimer]; [self.comboView endCombo]; self.comboView = nil; @@ -161,7 +158,6 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; self.bravoGiftView.hidden = NO; } - NSLog(@"[Combo effect] 🗑️ 连击相关视图移除完成"); } // 强制重置连击状态 @@ -221,7 +217,6 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; if (self.usingplaceType == SendGiftType_User) { return; } - NSLog(@"[Combo effect] 📱 开始注册actionCallback - usingplaceType: %ld", (long)self.usingplaceType); @kWeakify(self); [[GiftComboManager sharedManager] registerActions:^(ComboActionType type) { @kStrongify(self); @@ -232,13 +227,11 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; case ComboAction_ShowPanel: { // 🔥 修复:检查连击状态,避免在重置过程中显示面板 if (![[GiftComboManager sharedManager] isActive]) { - NSLog(@"[Combo effect] ⚠️ 连击未激活,跳过显示面板"); return; } // 🔥 修复:检查usingplaceType,确保在正确的场景下显示面板 if (self.usingplaceType == SendGiftType_User) { - NSLog(@"[Combo effect] ⚠️ 私聊模式,跳过显示连击面板"); return; } @@ -258,7 +251,6 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; // 🔥 修复:检查comboView是否已存在,避免重复创建 if (!self.comboView) { - NSLog(@"[Combo effect] 📱 创建新的comboView"); self->_comboView = [[GiftComboView alloc] init]; } @@ -299,7 +291,6 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; } break; case ComboAction_Combo_Count_Update: { - NSLog(@"[Combo effect] 📱 收到连击计数更新回调"); [self.comboView updateCount]; } break; @@ -432,13 +423,10 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; // 检查连击计数在云信消息中的传递 NSNumber *comboCount = data[@"comboCount"]; - NSLog(@"[Combo effect] 📨 云信消息连击计数检查 - comboCount: %@, giftId: %ld", comboCount, (long)receiveModel.gift.giftId); // 如果连击计数为 0 或 nil,尝试修复 if (!comboCount || [comboCount integerValue] < 1) { - NSLog(@"[Combo effect] 🚨 检测到云信消息中连击计数异常 - comboCount: %@", comboCount); NSInteger currentCombo = [[GiftComboManager sharedManager] currentCount]; - NSLog(@"[Combo effect] 🔧 使用当前连击计数修复 - 当前: %ld", (long)currentCombo); [data setObject:@(currentCombo) forKey:@"comboCount"]; } @@ -577,24 +565,6 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; NIMMessage *message = [[NIMMessage alloc]init]; NIMCustomObject *object = [[NIMCustomObject alloc] init]; - // 连击计数应该从 attachment.data 中获取,而不是重新计算 - // 这样可以避免重复递增连击计数 - { - BOOL onMain = [NSThread isMainThread]; - NSInteger comboToSend = [attachment.data[@"comboCount"] integerValue]; - NSData *payloadJSON = nil; - @try { - payloadJSON = [NSJSONSerialization dataWithJSONObject:attachment.data ?: @{} options:0 error:nil]; - } @catch (__unused NSException *e) {} - NSLog(@"[Combo effect][Send] 📨 即将发送 | sessionId=%@ type=%@ | combo=%ld | payload=%lub | main=%@ | ts=%.3f", - sessionID, - (self.usingplaceType == SendGiftType_Room ? @"Chatroom" : @"P2P"), - (long)comboToSend, - (unsigned long)(payloadJSON.length), - onMain ? @"YES" : @"NO", - [[NSDate date] timeIntervalSince1970]); - } - attachment.data = [self removeNSNullValuesAndEmptyStringsRecursively:attachment.data]; object.attachment = attachment; @@ -734,17 +704,14 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; /// 初始化/重置 连击礼物功能状态 - (void)readyForCombo:(XPGiftCountModel *)giftCount gift:(GiftInfoModel *)giftInfo { - NSLog(@"[Combo effect] 🔧 准备连击状态 - giftType: %ld, segmentType: %ld, usingplaceType: %ld", (long)giftInfo.giftType, (long)self.segmentType, (long)self.usingplaceType); // 🔥 修复:检查usingplaceType,私聊模式不支持连击 if (self.usingplaceType == SendGiftType_User) { - NSLog(@"[Combo effect] ❌ 私聊模式不支持连击"); [[GiftComboManager sharedManager] deactivate]; return; } if (self.segmentType == GiftSegmentType_Pack) { - NSLog(@"[Combo effect] ❌ 背包礼物不支持连击"); [[GiftComboManager sharedManager] deactivate]; return; } @@ -754,12 +721,10 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; giftInfo.giftType != GiftType_Lucky24 && giftInfo.giftType != GiftType_Lucky25 && giftInfo.giftType != GiftType_Bravo) { - NSLog(@"[Combo effect] ❌ 礼物类型不支持连击 - giftType: %ld", (long)giftInfo.giftType); [[GiftComboManager sharedManager] deactivate]; return; } - NSLog(@"[Combo effect] ✅ 礼物支持连击,启用连击功能"); [[GiftComboManager sharedManager] activate]; NSString *sessionID = self.usingplaceType == SendGiftType_User ? [NSString stringWithFormat:@"%ld", self.userArray.firstObject.uid] : [NSString stringWithFormat:@"%ld", [self.delegate getRoomInfo].roomId]; @@ -775,7 +740,6 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; sendType:[self dealRoomSendGiftType:giftInfo giftCount:giftCount] giftNum:[self dealSendGiftCount:giftCount gift:giftInfo]]; - NSLog(@"[Combo effect] ✅ 连击状态准备完成"); } #pragma mark - XPGiftBarViewDelegate @@ -785,18 +749,8 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; GiftInfoModel *giftInfo = self.giftInfoView.lastSelectGift; if (self.usingplaceType == SendGiftType_Room) { if (uids.count > 0) { - NSLog(@"[Combo effect] 🎁 开始送礼物流程"); [self readyForCombo:giftCount gift:giftInfo]; - [[GiftComboManager sharedManager] printComboState]; - - // 检查连击状态是否准备就绪 - if ([GiftComboManager sharedManager].enableCombo) { - NSLog(@"[Combo effect] ✅ 连击功能已启用,准备调用resetCombo"); - } else { - NSLog(@"[Combo effect] ❌ 连击功能未启用,无法进入连击状态"); - } - ///送礼物的人 NSString * uidString = [self dealSendGiftUids:uids]; ///送礼物的个数 @@ -1206,12 +1160,10 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; } else { self.giftBarView.walletInfoModel = receiveInfo.userPurse; } - NSLog(@"[Combo effect] 📱 检查连击状态 - enableCombo: %@", [GiftComboManager sharedManager].enableCombo ? @"YES" : @"NO"); if ([GiftComboManager sharedManager].enableCombo && self.usingplaceType == SendGiftType_Room) { - NSLog(@"[Combo effect] 📱 启用连击模式,重置连击状态"); // 检查 originDic 中的连击计数 NSNumber *originComboCount = originDic[@"comboCount"]; @@ -1220,7 +1172,6 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; editDic[@"comboCount"] = @(1); originDic = editDic.copy; } - NSLog(@"[Combo effect] 📱 originDic 连击计数检查 - comboCount: %@", originComboCount); [[GiftComboManager sharedManager] reset]; @@ -1230,11 +1181,9 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification; @kWeakify(self); [[GiftComboManager sharedManager] setHandleComboSuccess:^(GiftReceiveInfoModel * _Nonnull receiveModel, NSMutableDictionary * _Nonnull originDic) { @kStrongify(self); - NSLog(@"[Combo effect] 📱 连击回调中发送消息 - comboCount: %@", originDic[@"comboCount"]); [self sendCustomMessage:receiveInfo oringinDic:originDic.copy]; }]; } else { - NSLog(@"[Combo effect] 📱 未启用连击模式,直接发送消息"); } [self sendCustomMessage:receiveInfo oringinDic:originDic]; diff --git a/YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.m b/YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.m index 90cd534c..27e453eb 100644 --- a/YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.m +++ b/YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.m @@ -102,6 +102,9 @@ // 遍历匹配并播放对应等级的SVGA(仅在CP麦位开关开启且不是小游戏房时) if (self.cpMicEnabled && self.roomType != 7) { // 7 = RoomType_MiniGame for (MicCpInfoModel *obj in cpList) { + if (obj.cpLevel < 1) { + continue; + } BOOL match = (obj.uid == leftUid && obj.loverUid == rightUid) || (obj.uid == rightUid && obj.loverUid == leftUid); if (match) { NSInteger safeLevel = MAX(1, MIN(5, obj.cpLevel+1)); diff --git a/YuMi/Modules/YMRoom/View/XPRoomViewController.m b/YuMi/Modules/YMRoom/View/XPRoomViewController.m index a00e316a..2c692b0b 100644 --- a/YuMi/Modules/YMRoom/View/XPRoomViewController.m +++ b/YuMi/Modules/YMRoom/View/XPRoomViewController.m @@ -662,6 +662,65 @@ XPCandyTreeInsufficientBalanceViewDelegate> } #pragma mark - Private Method + +/// DiffableDataSource 开关控制(默认关闭) +- (BOOL)useDiffableDataSource { + // 可以通过配置或用户设置来控制 + return NO; // 暂时关闭,等测试完成后再开启 +} + +/// 统一的消息分发方法 - 支持 DiffableDataSource 和原有系统 +- (void)distributeMessage:(NIMMessage *)message toContainer:(MsRoomMessageMainView *)container { + if (!message || !container) return; + + if ([self useDiffableDataSource]) { + // 使用 DiffableDataSource 版本 - 直接调用 messageListView 的方法 + if ([container.messageListView respondsToSelector:@selector(addMessageWithDiffableDataSource:)]) { + [container.messageListView addMessageWithDiffableDataSource:message]; + return; + } + // 如果不支持则回退到原有方法 + } + + // 使用原有系统(带安全包装) + dispatch_async(dispatch_get_main_queue(), ^{ + @try { + switch (message.messageType) { + case NIMMessageTypeText: + [container handleNIMTextMessage:message]; + break; + case NIMMessageTypeTip: + [container handleNIMTextMessage:message]; + break; + case NIMMessageTypeImage: + [container handleNIMImageMessage:message]; + break; + case NIMMessageTypeCustom: + [container handleNIMCustomMessage:message]; + break; + case NIMMessageTypeNotification: + [container handleNIMNotificationMessage:message]; + break; + default: + break; + } + } @catch (NSException *exception) { + NSLog(@"[DistributeMessage] Caught %@: %@", exception.name, exception.reason); + } + }); +} + +- (void)safeMessageContainerCall:(NSString *)origin block:(void (^)(MsRoomMessageMainView *view))block { + MsRoomMessageMainView *view = self.messageContainerView; + if (!view || !block) { return; } + dispatch_async(dispatch_get_main_queue(), ^{ + @try { + block(view); + } @catch (NSException *exception) { + NSLog(@"[SafeMessage][%@] Caught %@: %@", origin, exception.name, exception.reason); + } + }); +} - (void)initSubViews { self.view.backgroundColor = [UIColor darkGrayColor]; [self.view addSubview:self.backContainerView]; @@ -2147,14 +2206,10 @@ XPCandyTreeInsufficientBalanceViewDelegate> // 非房间内消息不处理(记录过滤原因) if (message.session.sessionType != NIMSessionTypeChatroom) { - NSLog(@"[Recv] ⛔️ 过滤:非聊天室消息 | type=%ld | sid=%@", - (long)message.session.sessionType, message.session.sessionId); continue; } if (![message.session.sessionId isEqualToString:@(self.roomInfo.roomId).stringValue]) { - NSLog(@"[Recv] ⛔️ 过滤:房间不匹配 | msg.sid=%@ | curRoomId=%@", - message.session.sessionId, @(self.roomInfo.roomId).stringValue); continue; } @@ -2165,12 +2220,12 @@ XPCandyTreeInsufficientBalanceViewDelegate> } else if (message.messageType == NIMMessageTypeCustom) { [self handleNimCustomTypeMessage:message]; } else if(message.messageType == NIMMessageTypeText) { - [self.messageContainerView handleNIMTextMessage:message]; + [self distributeMessage:message toContainer:self.messageContainerView]; [self.littleGameView handleNIMTextMessage:message]; } else if(message.messageType == NIMMessageTypeTip) { - [self.messageContainerView handleNIMTextMessage:message]; + [self distributeMessage:message toContainer:self.messageContainerView]; }else if(message.messageType == NIMMessageTypeImage){ - [self.messageContainerView handleNIMImageMessage:message]; + [self distributeMessage:message toContainer:self.messageContainerView]; } } } @@ -2338,7 +2393,7 @@ XPCandyTreeInsufficientBalanceViewDelegate> [self.roomHeaderView onRoomUpdate]; [self.stageView handleNIMNotificationMessage:message]; [self.animationView handleNIMNotificationMessage:message]; - [self.messageContainerView handleNIMNotificationMessage:message]; + [self distributeMessage:message toContainer:self.messageContainerView]; [self.sideMenu handleNIMNotificationMessage:message]; [self.menuContainerView handleNIMNotificationMessage:message]; [self.functionView handleNIMNotificationMessage:message]; @@ -2451,7 +2506,7 @@ XPCandyTreeInsufficientBalanceViewDelegate> [self.sideMenu handleNIMCustomMessage:message]; [self.functionView handleNIMCustomMessage:message]; [self.littleGameView handleNIMCustomMessage:message]; - [self.messageContainerView handleNIMCustomMessage:message]; + [self distributeMessage:message toContainer:self.messageContainerView]; switch (attachment.first) { case ClientMessage_Type: @@ -3018,7 +3073,7 @@ XPCandyTreeInsufficientBalanceViewDelegate> object.attachment = attachment; message.messageObject = object; - [self.messageContainerView handleNIMTextMessage:message]; + [self distributeMessage:message toContainer:self.messageContainerView]; } } //发送消息成功回调 @@ -3078,10 +3133,10 @@ XPCandyTreeInsufficientBalanceViewDelegate> self.roomInfo.hasAnimationEffect = roomInfo.hasAnimationEffect; } [self.roomHeaderView onRoomUpdate]; - [self.messageContainerView handleNIMCustomMessage:message]; + [self distributeMessage:message toContainer:self.messageContainerView]; } }else if(message.messageType == NIMMessageTypeText) { - [self.messageContainerView handleNIMTextMessage:message];; + [self distributeMessage:message toContainer:self.messageContainerView]; } } @@ -3806,15 +3861,18 @@ XPCandyTreeInsufficientBalanceViewDelegate> case NIMMessageTypeCustom: [self handleNimCustomTypeMessage:message]; break; - case NIMMessageTypeText: - [self.messageContainerView handleNIMTextMessage:message]; + case NIMMessageTypeText: { + [self distributeMessage:message toContainer:self.messageContainerView]; [self.littleGameView handleNIMTextMessage:message]; + } break; - case NIMMessageTypeTip: - [self.messageContainerView handleNIMTextMessage:message]; + case NIMMessageTypeTip: { + [self distributeMessage:message toContainer:self.messageContainerView]; + } break; - case NIMMessageTypeImage: - [self.messageContainerView handleNIMImageMessage:message]; + case NIMMessageTypeImage:{ + [self distributeMessage:message toContainer:self.messageContainerView]; + } break; default: break; diff --git a/YuMi/Network/HttpRequestHelper.m b/YuMi/Network/HttpRequestHelper.m index 2beb7196..6647bf6e 100644 --- a/YuMi/Network/HttpRequestHelper.m +++ b/YuMi/Network/HttpRequestHelper.m @@ -94,7 +94,7 @@ editParam = [MSParamsDecode msDecodeParams:editParam]; params = [self configBaseParmars:editParam]; -#if 0 +#if DEBUG // 构建完整的 URL NSString *baseUrl = [HttpRequestHelper getHostUrl]; NSString *fullUrl = [NSString stringWithFormat:@"%@/%@", baseUrl, method]; @@ -118,7 +118,7 @@ @kWeakify(self); [manager GET:method parameters:params headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject]; -#if 0 +#if DEBUG NSLog(@"%@ - \n%@\n", method, [baseModel toJSONString]); #else #endif @@ -148,7 +148,7 @@ params = [self configBaseParmars:params]; -#if 0 +#if DEBUG // 构建完整的 URL NSString *baseUrl = [HttpRequestHelper getHostUrl]; NSString *fullUrl = [NSString stringWithFormat:@"%@/%@", baseUrl, method]; @@ -163,7 +163,7 @@ [manager POST:method parameters:params headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject]; -#if 0 +#if DEBUG NSLog(@"\n%@", [baseModel toJSONString]); #else #endif diff --git a/issues/diffable-migration-notes.md b/issues/diffable-migration-notes.md new file mode 100644 index 00000000..0544c76c --- /dev/null +++ b/issues/diffable-migration-notes.md @@ -0,0 +1,50 @@ +# DiffableDataSource 迁移记录(阶段性回退) + +时间:2025-09-23 + +## 背景 +- 目标:将房间消息公屏迁移到 UITableViewDiffableDataSource(iOS 13+)。 +- 核心组件:`XPMessageItem`、`XPMessageDataSourceManager`、`XPRoomMessageContainerView`、`XPRoomViewController`。 + +## 已完成 +- 新增 `XPMessageItem`(实现 NSCopying/isEqual/hash/description)。 +- 新增 `XPMessageDataSourceManager`:串行队列封装、读写隔离;修复了在队列内访问只读属性导致的递归/死锁,改用 `_allMessages/_chatMessages/_giftMessages`;新增 `addMessageSync:` 用于快照直后立刻刷新。 +- `XPRoomMessageContainerView`: + - 增加 DiffableDataSource 支持:`setupDiffableDataSource`、`addMessageWithDiffableDataSource:`、`updateDiffableDataSourceSnapshot`、`scrollToBottomWithDiffableDataSource:`、`changeTypeWithDiffableDataSource:`。 + - 绑定 `tableView.dataSource = diffableDataSource`。 + - 修正行高计算在 Diffable 模式下基于 `dataSourceManager`。 + - 解析/建模移动到主线程,避免 parser 内创建 UIView 触发 MTC。 +- `XPRoomViewController`:统一分发入口 `distributeMessage:toContainer:`,在启用时路由到 `container.messageListView addMessageWithDiffableDataSource:`。 + +## 遇到的问题 +1) Main Thread Checker:parser 内创建 `NetImageView`,后台线程调用崩溃 → 已将解析迁至主线程。 +2) 数据源递归死锁:在队列内通过 getter 访问只读数组,getter 内 `dispatch_sync` → 已用 ivar 替代。 +3) API 调用不兼容:`appendItems:toSection:`(Swift 签名)在 Obj‑C 不可用 → 改为 `appendItemsWithIdentifiers:intoSectionWithIdentifier:`。 +4) UI 不显示:日志显示 snapshot item 数量正确、cellProvider 触发,但 cell 行高与旧数组耦合,渲染为空 → 已在 Diffable 模式下改为从 `dataSourceManager` 读取。 +5) 仍存在: + - 部分 cell 样式依赖旧数据流与时序; + - 插入数据不全(需要进一步梳理 parser→model→item 的丢弃路径与过滤条件)。 + +## 现状与决策 +- 暂时关闭 DiffableDataSource(`XPRoomViewController.useDiffableDataSource` 返回 NO,`XPRoomMessageContainerView.useDiffableDataSource = NO`),回退旧方案,确保稳定性。 + +## 后续建议(待恢复时) +1) 将 parser 改为纯数据产出(不创建 UIView/Attachment UI),UI 组件在 cell 渲染时构造。 +2) 为 `XPMessageItem`/`XPMessageInfoModel` 增加单元测试与快照测试,覆盖典型 first/second 组合、礼物/文本/通知。 +3) 建立统一的高度计算服务,消除表与数据源的耦合分支。 +4) 快照构建前后加一致性校验(section/items 数量、索引有效性)。 +5) 按功能开关分阶段恢复:先纯文本 → 表情 → 通知 → 礼物 → 特殊 cell。 + +## 变更点列表(代码) +- `YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageItem.{h,m}` 新增。 +- `YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageDataSourceManager.{h,m}` 新增与修复(addMessageSync、ivar 访问等)。 +- `YuMi/Modules/YMRoom/View/MessageContainerView/XPRoomMessageContainerView.m` 多处新增/修改(Diffable 支持、主线程解析、行高来源切换、dataSource 绑定)。 +- `YuMi/Modules/YMRoom/View/XPRoomViewController.m` 新增 `distributeMessage:toContainer:`,并默认关闭 Diffable。 + +## 回退开关位置 +- `XPRoomViewController - (BOOL)useDiffableDataSource` → NO +- `XPRoomMessageContainerView.useDiffableDataSource` → NO + +--- +记录人:系统自动 + diff --git a/issues/gift-animation-optimization.md b/issues/gift-animation-optimization.md index 0056ad2f..410c47a2 100644 --- a/issues/gift-animation-optimization.md +++ b/issues/gift-animation-optimization.md @@ -53,7 +53,6 @@ BOOL isUserInCombo = [self.userComboStates[uid] boolValue]; if (isUserInCombo) { BOOL isCurrentUser = [uid isEqualToString:[AccountInfoStorage instance].getUid]; - NSLog(@"[Combo effect] 🎯 用户 %@ 处于combo状态,是否当前用户: %@", uid, isCurrentUser ? @"YES" : @"NO"); return isCurrentUser; } @@ -63,12 +62,10 @@ NSTimeInterval timeSinceLastGift = [[NSDate date] timeIntervalSinceDate:lastGiftTime]; if (timeSinceLastGift <= self.comboTimeWindow) { BOOL isCurrentUser = [uid isEqualToString:[AccountInfoStorage instance].getUid]; - NSLog(@"[Combo effect] 🎯 用户 %@ 在时间窗口内,是否当前用户: %@", uid, isCurrentUser ? @"YES" : @"NO"); return isCurrentUser; } } - NSLog(@"[Combo effect] 🎯 用户 %@ 不使用combo动画", uid); return NO; } ```