Compare commits

...

3 Commits

30 changed files with 1143 additions and 302 deletions

View File

@@ -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

View File

@@ -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 = "<group>"; };
4C815A152CFEB758002A46A6 /* SuperBlockViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SuperBlockViewController.h; sourceTree = "<group>"; };
4C815A162CFEB758002A46A6 /* SuperBlockViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SuperBlockViewController.m; sourceTree = "<group>"; };
4C816A7B2E826C7300EA6A45 /* XPMessageDataSourceManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPMessageDataSourceManager.h; sourceTree = "<group>"; };
4C816A7C2E826C7300EA6A45 /* XPMessageDataSourceManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPMessageDataSourceManager.m; sourceTree = "<group>"; };
4C816A7D2E826C7300EA6A45 /* XPMessageItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPMessageItem.h; sourceTree = "<group>"; };
4C816A7E2E826C7300EA6A45 /* XPMessageItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPMessageItem.m; sourceTree = "<group>"; };
4C84A9C02E5ED593002C10FC /* GameBannerGestureManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GameBannerGestureManager.h; sourceTree = "<group>"; };
4C84A9C12E5ED593002C10FC /* GameBannerGestureManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GameBannerGestureManager.m; sourceTree = "<group>"; };
4C84A9C92E602B1A002C10FC /* BuglyManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BuglyManager.h; sourceTree = "<group>"; };
@@ -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",

View File

@@ -190,6 +190,45 @@
make.height.mas_equalTo(kGetScaleWidth(20));
}];
}
- (void)prepareForReuse {
[super prepareForReuse];
//
[self.pkImageView stopAnimation];
self.pkImageView.videoItem = nil;
self.pkImageView.hidden = YES;
//
[self.avatarView cancelLoadImage];
self.avatarView.imageUrl = nil;
self.rankImageView.image = nil;
[self.tagImageView cancelLoadImage];
self.tagImageView.image = nil;
self.tagImageView.hidden = YES;
[self.levelImageView cancelLoadImage];
self.levelImageView.imageUrl = nil;
[self.boomImageView cancelLoadImage];
self.boomImageView.imageUrl = nil;
//
self.nameLabel.attributedText = nil;
self.nameLabel.text = @"";
self.subLabel.text = @"";
self.heatNumLable.text = @"0";
// +
for (NetImageView *iconView in self.iconViews) {
[iconView cancelLoadImage];
iconView.imageUrl = nil;
iconView.hidden = YES;
}
}
- (void)dealloc {
//
[self.pkImageView stopAnimation];
self.pkImageView.videoItem = nil;
}
- (void)setRoomInfo:(HomePlayRoomModel *)roomInfo{
_roomInfo = roomInfo;

View File

@@ -123,6 +123,28 @@
}];
}
- (void)prepareForReuse {
[super prepareForReuse];
//
[self.avatarView cancelLoadImage];
self.avatarView.imageUrl = nil;
self.rankImageView.image = nil;
[self.tagImageView cancelLoadImage];
self.tagImageView.image = nil;
[self.levelImageView cancelLoadImage];
self.levelImageView.imageUrl = nil;
[self.boomImageView cancelLoadImage];
self.boomImageView.imageUrl = nil;
[self.flagImage cancelLoadImage];
self.flagImage.imageUrl = nil;
//
self.nameLabel.attributedText = nil;
self.nameLabel.text = @"";
self.subLabel.text = @"";
self.heatNumLable.text = @"0";
}
- (void)setRoomInfo:(HomePlayRoomModel *)roomInfo {
_roomInfo = roomInfo;
self.avatarView.imageUrl = roomInfo.avatar;

View File

@@ -91,6 +91,25 @@ static NSString * const kHomeLayoutTypeKey = @"kHomeLayoutTypeKey";
return _bannerView;
}
- (void)prepareForReuse {
[super prepareForReuse];
// /CPU
self.bannerView.autoScroll = NO;
self.bannerView.imageURLStringsGroup = nil;
}
- (void)stopAndRelease {
//
self.bannerView.autoScroll = NO;
}
// VC
- (void)resumeIfNeeded {
if (self.bannerView.imageURLStringsGroup.count > 0) {
self.bannerView.autoScroll = YES;
}
}
#pragma mark - SDCycleScrollViewDelegate
- (void)cycleScrollView:(SDCycleScrollView *)cycleScrollView didSelectItemAtIndex:(NSInteger)index {
HomeBannerInfoModel * bannerInfo = [self.bannerInfoList xpSafeObjectAtIndex:index];
@@ -152,8 +171,16 @@ static NSString * const kHomeLayoutTypeKey = @"kHomeLayoutTypeKey";
}
- (void)loadLayoutTypeFromCache {
// UserDefaults NO
self.isTwoColumnLayout = [[NSUserDefaults standardUserDefaults] boolForKey:kHomeLayoutTypeKey];
// UserDefaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
id storedValue = [defaults objectForKey:kHomeLayoutTypeKey];
if (storedValue == nil) {
self.isTwoColumnLayout = YES; //
[defaults setBool:YES forKey:kHomeLayoutTypeKey];
[defaults synchronize];
} else {
self.isTwoColumnLayout = [storedValue boolValue];
}
}
- (void)saveLayoutTypeToCache {
@@ -387,6 +414,27 @@ static NSString * const kHomeLayoutTypeKey = @"kHomeLayoutTypeKey";
return CGSizeMake(kGetScaleWidth(345), kGetScaleWidth(92));
}
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
// cell
if ([cell isKindOfClass:[HomePartyBannerCell class]]) {
HomePartyBannerCell *bannerCell = (HomePartyBannerCell *)cell;
[bannerCell stopAndRelease];
} else if ([cell isKindOfClass:[XPNewHomePartyCollectionViewCell class]]) {
// prepareForReuse
[cell prepareForReuse];
} else if ([cell isKindOfClass:[XPPartyRoomItemCollectionViewCell class]]) {
[cell prepareForReuse];
}
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
// banner
if ([cell isKindOfClass:[HomePartyBannerCell class]]) {
HomePartyBannerCell *bannerCell = (HomePartyBannerCell *)cell;
[bannerCell resumeIfNeeded];
}
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
id item = [self.datasource xpSafeObjectAtIndex:indexPath.row];

View File

@@ -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];
}

View File

@@ -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<NSString *> *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);
}
}

View File

@@ -1875,11 +1875,6 @@ BannerSchedulerDelegate
// first/secondpayload 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) {

View File

@@ -0,0 +1,94 @@
//
// XPMessageDataSourceManager.h
// YUMI
//
// Created by YUMI on 2024/12/19.
//
#import <Foundation/Foundation.h>
#import "XPMessageItem.h"
NS_ASSUME_NONNULL_BEGIN
/**
* 消息数据源管理器
* 统一管理所有消息数据,提供线程安全的数据操作
* 支持 DiffableDataSource 的单一数据源架构
*/
@interface XPMessageDataSourceManager : NSObject
/// 所有消息(只读)
@property (nonatomic, strong, readonly) NSArray<XPMessageItem *> *allMessages;
/// 聊天消息(只读)
@property (nonatomic, strong, readonly) NSArray<XPMessageItem *> *chatMessages;
/// 礼物消息(只读)
@property (nonatomic, strong, readonly) NSArray<XPMessageItem *> *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<XPMessageItem *> *)messages;
/**
* 移除消息
* @param message 要移除的消息项
*/
- (void)removeMessage:(XPMessageItem *)message;
/**
* 清空所有消息
*/
- (void)clearAllMessages;
/**
* 根据显示类型获取消息
* @param displayType 显示类型1=所有, 2=聊天, 3=礼物
* @return 对应类型的消息数组
*/
- (NSArray<XPMessageItem *> *)messagesForDisplayType:(NSInteger)displayType;
/**
* 手动清理旧消息
* 当消息数量超过 maxMessages 时自动调用
*/
- (void)cleanupOldMessages;
/**
* 获取当前消息总数
* @return 消息总数
*/
- (NSInteger)totalMessageCount;
/**
* 获取指定类型的消息数量
* @param displayType 显示类型
* @return 消息数量
*/
- (NSInteger)messageCountForDisplayType:(NSInteger)displayType;
@end
NS_ASSUME_NONNULL_END

View File

@@ -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<XPMessageItem *> *)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<XPMessageItem *> *)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<XPMessageItem *> *)allMessages {
__block NSArray *result = nil;
dispatch_sync(self.dataQueue, ^{
result = [_allMessages copy];
});
return result;
}
- (NSArray<XPMessageItem *> *)chatMessages {
__block NSArray *result = nil;
dispatch_sync(self.dataQueue, ^{
result = [_chatMessages copy];
});
return result;
}
- (NSArray<XPMessageItem *> *)giftMessages {
__block NSArray *result = nil;
dispatch_sync(self.dataQueue, ^{
result = [_giftMessages copy];
});
return result;
}
#pragma mark - Debug
- (NSString *)description {
return [NSString stringWithFormat:@"<XPMessageDataSourceManager: %p, total: %ld, chat: %ld, gift: %ld, max: %ld>",
self, (long)[self totalMessageCount], (long)[self messageCountForDisplayType:2],
(long)[self messageCountForDisplayType:3], (long)self.maxMessages];
}
@end

View File

@@ -6,6 +6,7 @@
//
#import <Foundation/Foundation.h>
#import "PIBaseModel.h"
#import "PIRoomPhotoAlbumItemModel.h"
NS_ASSUME_NONNULL_BEGIN

View File

@@ -0,0 +1,60 @@
//
// XPMessageItem.h
// YUMI
//
// Created by YUMI on 2024/12/19.
//
#import <Foundation/Foundation.h>
#import "XPMessageInfoModel.h"
NS_ASSUME_NONNULL_BEGIN
/**
* DiffableDataSource 兼容的消息项模型
* 用于替代原有的多数据源架构,提供统一的消息管理
*/
@interface XPMessageItem : NSObject <NSCopying>
/// 消息模型
@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

View File

@@ -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:@"<XPMessageItem: %p, identifier: %@, type: %ld, timestamp: %.0f>",
self, self.uniqueIdentifier, (long)self.displayType, self.timestamp];
}
@end

View File

@@ -8,6 +8,7 @@
#import <UIKit/UIKit.h>
#import "RoomHostDelegate.h"
#import "RoomGuestDelegate.h"
#import "XPRoomMessageContainerView.h"
NS_ASSUME_NONNULL_BEGIN
@interface MsRoomMessageMainView : UIView<RoomGuestDelegate>
@@ -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

View File

@@ -5,7 +5,7 @@
// Created by duoban on 2024/5/10.
//
#import "MsRoomMessageMainView.h"
#import "XPRoomMessageContainerView.h"
#import "ClientConfig.h"
#import "UserInfoModel.h"
#import <NIMSDK/NIMSDK.h>
@@ -17,7 +17,7 @@
@interface MsRoomMessageMainView()<XPRoomMessageContainerViewDelegate, NIMBroadcastManagerDelegate>
@property(nonatomic,strong) XPRoomMessageContainerView *messageListView;
///
@property (nonatomic,weak) id<RoomHostDelegate> hostDelegate;

View File

@@ -10,6 +10,10 @@
#import "RoomGuestDelegate.h"
#import <JXCategoryView/JXCategoryListContainerView.h>
// DiffableDataSource 相关导入
#import "XPMessageItem.h"
#import "XPMessageDataSourceManager.h"
NS_ASSUME_NONNULL_BEGIN
@class XPRoomMessageContainerView, AttachmentModel, NIMBroadcastMessage;
@protocol XPRoomMessageContainerViewDelegate <NSObject>
@@ -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<NSString *, XPMessageItem *> *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

View File

@@ -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

View File

@@ -32,6 +32,12 @@ NS_ASSUME_NONNULL_BEGIN
// 🔧 新增:更新礼物特效开关状态(通过 RoomInfo.hasAnimationEffect 更新)
- (void)updateGiftEffectsForRoom:(NSString *)roomId enabled:(BOOL)enabled;
// 支持来源标记fromUser=YES 表示用户手动开关,打上覆盖标记
- (void)updateGiftEffectsForRoom:(NSString *)roomId enabled:(BOOL)enabled fromUser:(BOOL)fromUser;
// 用户覆盖标记:用于阻止服务端推送覆盖用户选择
- (void)setGiftEffectsOverrideForRoom:(NSString *)roomId enabled:(BOOL)enabled;
- (BOOL)isGiftEffectsUserOverriddenForRoom:(NSString *)roomId;
// 🔧 新增:获取各开关状态
- (BOOL)isGiftEffectsEnabledForRoom:(NSString *)roomId;

View File

@@ -87,11 +87,43 @@
// 🔧 便UI
[[NSNotificationCenter defaultCenter] postNotificationName:kTurboGiftEffectsEnabledChanged
object:nil
userInfo:@{@"roomId": roomId, @"on": @(enabled)}];
userInfo:@{ @"roomId": roomId, @"on": @(enabled) }];
NSLog(@"🎮 TurboModeStateManager: 房间 %@ 礼物特效开关更新为 %@", roomId, enabled ? @"开启" : @"关闭");
}
//
- (void)updateGiftEffectsForRoom:(NSString *)roomId enabled:(BOOL)enabled fromUser:(BOOL)fromUser {
if (!roomId) return;
[self ensureRoomSwitchStatesExist:roomId];
NSMutableDictionary *roomStates = [self.roomSwitchStates[roomId] mutableCopy];
roomStates[@"giftEffects"] = @(enabled);
if (fromUser) {
roomStates[@"giftEffectsOverride"] = @(YES);
}
self.roomSwitchStates[roomId] = roomStates;
[[NSNotificationCenter defaultCenter] postNotificationName:kTurboGiftEffectsEnabledChanged
object:nil
userInfo:@{ @"roomId": roomId, @"on": @(enabled) }];
NSLog(@"🎮 TurboModeStateManager: 房间 %@ 礼物特效开关更新为 %@ (fromUser=%@)", roomId, enabled ? @"开启" : @"关闭", fromUser ? @"YES" : @"NO");
}
//
- (void)setGiftEffectsOverrideForRoom:(NSString *)roomId enabled:(BOOL)enabled {
if (!roomId) return;
[self ensureRoomSwitchStatesExist:roomId];
NSMutableDictionary *roomStates = [self.roomSwitchStates[roomId] mutableCopy];
roomStates[@"giftEffectsOverride"] = @(enabled);
self.roomSwitchStates[roomId] = roomStates;
}
- (BOOL)isGiftEffectsUserOverriddenForRoom:(NSString *)roomId {
if (!roomId) return NO;
[self ensureRoomSwitchStatesExist:roomId];
return [self.roomSwitchStates[roomId][@"giftEffectsOverride"] boolValue];
}
// 🔧 turbo mode
- (BOOL)isGiftEffectsEnabledForRoom:(NSString *)roomId {
if (!roomId) return NO;
@@ -259,6 +291,7 @@
// RoomAnimationView roomInfo
BOOL giftEffects = YES;
BOOL giftEffectsOverride = NO;
id giftScreenObj = [defaults objectForKey:kTurboGlobalGiftScreenEnabledKey(roomId)];
BOOL globalGiftScreen = (giftScreenObj != nil) ? [defaults boolForKey:kTurboGlobalGiftScreenEnabledKey(roomId)] : NO;
@@ -438,6 +471,7 @@
//
BOOL giftEffects = YES;
BOOL giftEffectsOverride = NO;
// NSUserDefaults
id giftScreenObj = [defaults objectForKey:kTurboGlobalGiftScreenEnabledKey(roomId)];
@@ -451,6 +485,7 @@
self.roomSwitchStates[roomId] = @{
@"giftEffects": @(giftEffects),
@"giftEffectsOverride": @(giftEffectsOverride),
@"globalGiftScreen": @(globalGiftScreen),
@"globalGameScreen": @(globalGameScreen),
@"cpMic": @(cpMic)

View File

@@ -211,8 +211,8 @@
TurboModeStateManager *manager = [TurboModeStateManager sharedManager];
if (sender.tag == 0) { //
//
[manager updateGiftEffectsForRoom:self.roomId enabled:isOn];
//
[manager updateGiftEffectsForRoom:self.roomId enabled:isOn fromUser:YES];
} else if (sender.tag == 1) { //
[manager setGlobalGiftScreenEnabledForRoom:self.roomId enabled:isOn];

View File

@@ -92,7 +92,6 @@ NS_ASSUME_NONNULL_BEGIN
// 新增:连击状态检查方法
- (BOOL)isComboStateValid;
- (NSDictionary * _Nonnull)getComboStateInfo;
- (void)printComboState;
// 🔧 新增:状态通知方法
- (void)setUserComboState:(BOOL)isCombo forUser:(NSString *)uid;

View File

@@ -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;
}
// 🔥 actionCallbackreset
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. combo0
_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] <20><> 移除后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]]) {
// infometadata
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] <20><> 处理网络数据 - 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];
// APIcombo
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"

View File

@@ -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

View File

@@ -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] 👆 连击面板被点击,发送礼物");
// comboAPI
// 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] 📳 震动反馈已重新准备");
}
}
}

View File

@@ -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];

View File

@@ -102,9 +102,12 @@
// SVGACP
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));
NSInteger safeLevel = MAX(1, MIN(5, obj.cpLevel));
NSString *svgaName = [NSString stringWithFormat:@"mic_cp_lv%ld", (long)safeLevel];
[self playSVGAAnimationAtFrame:frame withNamed:svgaName];
break;

View File

@@ -221,6 +221,8 @@ XPCandyTreeInsufficientBalanceViewDelegate>
@property (nonatomic, assign) BOOL currentUserWasOnMic; //
@property (nonatomic, assign) NSInteger currentUserMicPosition; // -1
@property (nonatomic, assign) BOOL hasCompletedRoomInitialization; //
/// UIDCP
@property (nonatomic, assign) BOOL hasRequestedInitialCpByRoomUid;
@end
@@ -363,6 +365,65 @@ XPCandyTreeInsufficientBalanceViewDelegate>
self.upMicAskTimer = nil;
}
// 🔧 /
[TTPopup dismiss];
// 🔧 Turbo Mode Tips
if ([[XPTurboModeTipsManager sharedManager] respondsToSelector:@selector(stopTipsMonitoringInRoom)]) {
[[XPTurboModeTipsManager sharedManager] performSelector:@selector(stopTipsMonitoringInRoom)];
}
// 🔧 GiftComboManager UI VC
if ([GiftComboManager sharedManager]) {
[[GiftComboManager sharedManager] setHandleRoomUIChanged:nil];
}
// 🔧 sideMenu block
if (self.sideMenu) {
if ([self.sideMenu respondsToSelector:@selector(setOpenRedPacketHandle:)]) {
[self.sideMenu performSelector:@selector(setOpenRedPacketHandle:) withObject:nil];
}
if ([self.sideMenu respondsToSelector:@selector(setShowSendGiftView:)]) {
[self.sideMenu performSelector:@selector(setShowSendGiftView:) withObject:nil];
}
}
// 🔧 / delegate
NSArray *possibleDelegates = @[
self.backContainerView,
self.stageView,
self.messageContainerView,
self.quickMessageContainerView,
self.menuContainerView,
self.functionView,
self.littleGameView,
self.anchorScrollView
];
for (id obj in possibleDelegates) {
if (!obj) { continue; }
if ([obj respondsToSelector:@selector(setDelegate:)]) {
[obj performSelector:@selector(setDelegate:) withObject:nil];
}
if ([obj respondsToSelector:@selector(setHostDelegate:)]) {
[obj performSelector:@selector(setHostDelegate:) withObject:nil];
}
if ([obj respondsToSelector:@selector(setAnchorScrollDelegate:)]) {
[obj performSelector:@selector(setAnchorScrollDelegate:) withObject:nil];
}
}
// 🔧 UI
[self __removeAllViews];
self.anchorScrollView = nil;
self.backContainerView = nil;
self.stageView = nil;
self.messageContainerView = nil;
self.quickMessageContainerView = nil;
self.menuContainerView = nil;
self.sideMenu = nil;
self.functionView = nil;
self.littleGameView = nil;
NSLog(@"🔄 XPRoomViewController: 销毁完成");
}
@@ -662,6 +723,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];
@@ -1727,7 +1847,10 @@ XPCandyTreeInsufficientBalanceViewDelegate>
// roominfo
NSString *roomId = @(roomInfo.roomId).stringValue;
[[TurboModeStateManager sharedManager] updateGiftEffectsForRoom:roomId
enabled:roomInfo.hasAnimationEffect];
enabled:roomInfo.hasAnimationEffect
fromUser:NO];
//
[[TurboModeStateManager sharedManager] setGiftEffectsOverrideForRoom:roomId enabled:NO];
[self requestBoomData];
@@ -1812,6 +1935,9 @@ XPCandyTreeInsufficientBalanceViewDelegate>
// 🔧
[self initializeCurrentUserMicStatus];
// 🔧 UIDCP
[self tryRequestInitialCpByRoomUidIfNeeded];
//
if (self.roomInfo != nil) {
@@ -1825,6 +1951,18 @@ XPCandyTreeInsufficientBalanceViewDelegate>
}
}
/// UIDCP
- (void)tryRequestInitialCpByRoomUidIfNeeded {
@synchronized (self) {
if (self.hasRequestedInitialCpByRoomUid) { return; }
if (!self.hasCompletedRoomInitialization) { return; }
if (self.isExitingRoom || !self.isViewActive) { return; }
self.hasRequestedInitialCpByRoomUid = YES;
}
NSString *roomUid = [NSString stringWithFormat:@"%ld", (long)self.roomInfo.uid];
[self.presenter micCpListByRoomUid:roomUid];
}
- (void)enterRoomFail:(NSInteger)code {
[XNDJTDDLoadingTool hideHUDInView:self.navigationController.view];
[self hideHUD];
@@ -2144,14 +2282,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;
}
@@ -2162,12 +2296,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];
}
}
}
@@ -2335,7 +2469,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];
@@ -2448,7 +2582,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:
@@ -2498,6 +2632,16 @@ XPCandyTreeInsufficientBalanceViewDelegate>
RoomInfoModel * roomInfo = [RoomInfoModel modelWithDictionary:dic];
self.roomInfo.hasAnimationEffect = roomInfo.hasAnimationEffect;
[self.roomHeaderView onRoomUpdate];
// TurboModeStateManager
{
NSString *roomId = @(self.roomInfo.roomId).stringValue;
BOOL userOverridden = [[TurboModeStateManager sharedManager] isGiftEffectsUserOverriddenForRoom:roomId];
if (!userOverridden) {
[[TurboModeStateManager sharedManager] updateGiftEffectsForRoom:roomId
enabled:self.roomInfo.hasAnimationEffect
fromUser:NO];
}
}
}
break;
}
@@ -3005,7 +3149,7 @@ XPCandyTreeInsufficientBalanceViewDelegate>
object.attachment = attachment;
message.messageObject = object;
[self.messageContainerView handleNIMTextMessage:message];
[self distributeMessage:message toContainer:self.messageContainerView];
}
}
//
@@ -3065,10 +3209,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];
}
}
@@ -3793,15 +3937,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;
@@ -4150,6 +4297,8 @@ XPCandyTreeInsufficientBalanceViewDelegate>
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.hasCompletedRoomInitialization = YES;
NSLog(@"🔧 进房初始化完成,后续麦位变动将触发 micCpListByUidList 调用");
// UIDCP
[self tryRequestInitialCpByRoomUidIfNeeded];
});
}

View File

@@ -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

View File

@@ -0,0 +1,50 @@
# DiffableDataSource 迁移记录(阶段性回退)
时间2025-09-23
## 背景
- 目标:将房间消息公屏迁移到 UITableViewDiffableDataSourceiOS 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 Checkerparser 内创建 `NetImageView`,后台线程调用崩溃 → 已将解析迁至主线程。
2) 数据源递归死锁:在队列内通过 getter 访问只读数组getter 内 `dispatch_sync` → 已用 ivar 替代。
3) API 调用不兼容:`appendItems:toSection:`Swift 签名)在 ObjC 不可用 → 改为 `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 UIUI 组件在 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
---
记录人:系统自动

View File

@@ -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;
}
```