From 12c76609c5646f77e03ae6a5f20035471593af72 Mon Sep 17 00:00:00 2001 From: edwinQQQ Date: Wed, 24 Sep 2025 19:40:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9C=A8=20XPHomePartyViewController=E3=80=81X?= =?UTF-8?q?PNewHomePartyCollectionViewCell=20=E5=92=8C=20XPPartyRoomItemCo?= =?UTF-8?q?llectionViewCell=20=E4=B8=AD=E6=96=B0=E5=A2=9E=20prepareForReus?= =?UTF-8?q?e=20=E6=96=B9=E6=B3=95=E4=BB=A5=E4=BC=98=E5=8C=96=E5=86=85?= =?UTF-8?q?=E5=AD=98=E7=AE=A1=E7=90=86=EF=BC=8C=E7=A1=AE=E4=BF=9D=E5=9C=A8?= =?UTF-8?q?=E5=A4=8D=E7=94=A8=E6=97=B6=E6=B8=85=E7=90=86=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E3=80=82=E6=9B=B4=E6=96=B0=20XPRoomViewController=20=E4=BB=A5?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=88=BF=E9=97=B4UID=E7=9A=84CP=E6=8B=89?= =?UTF-8?q?=E5=8F=96=E9=80=BB=E8=BE=91=EF=BC=8C=E5=B9=B6=E5=9C=A8=20deallo?= =?UTF-8?q?c=20=E6=96=B9=E6=B3=95=E4=B8=AD=E6=B7=BB=E5=8A=A0=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E9=87=8A=E6=94=BE=E4=BF=9D=E9=9A=9C=E3=80=82=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20MicMidpointRectManager=20=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E5=AE=89=E5=85=A8=E7=BA=A7=E5=88=AB=E8=AE=A1=E7=AE=97=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E7=A1=AE=E4=BF=9D=E4=BB=A3=E7=A0=81=E6=95=B4?= =?UTF-8?q?=E6=B4=81=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cell/XPNewHomePartyCollectionViewCell.m | 39 ++++++++++ .../Cell/XPPartyRoomItemCollectionViewCell.m | 22 ++++++ .../View/XPHomePartyViewController.m | 52 ++++++++++++- .../View/StageView/MicMidpointRectManager.m | 2 +- .../YMRoom/View/XPRoomViewController.m | 78 +++++++++++++++++++ 5 files changed, 190 insertions(+), 3 deletions(-) diff --git a/YuMi/Modules/YMNewHome/View/Cell/XPNewHomePartyCollectionViewCell.m b/YuMi/Modules/YMNewHome/View/Cell/XPNewHomePartyCollectionViewCell.m index 2b408094..5b592f29 100644 --- a/YuMi/Modules/YMNewHome/View/Cell/XPNewHomePartyCollectionViewCell.m +++ b/YuMi/Modules/YMNewHome/View/Cell/XPNewHomePartyCollectionViewCell.m @@ -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; diff --git a/YuMi/Modules/YMNewHome/View/Cell/XPPartyRoomItemCollectionViewCell.m b/YuMi/Modules/YMNewHome/View/Cell/XPPartyRoomItemCollectionViewCell.m index 86563f99..57fdbe77 100644 --- a/YuMi/Modules/YMNewHome/View/Cell/XPPartyRoomItemCollectionViewCell.m +++ b/YuMi/Modules/YMNewHome/View/Cell/XPPartyRoomItemCollectionViewCell.m @@ -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; diff --git a/YuMi/Modules/YMNewHome/View/XPHomePartyViewController.m b/YuMi/Modules/YMNewHome/View/XPHomePartyViewController.m index dc609f46..da4ee1d4 100644 --- a/YuMi/Modules/YMNewHome/View/XPHomePartyViewController.m +++ b/YuMi/Modules/YMNewHome/View/XPHomePartyViewController.m @@ -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]; diff --git a/YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.m b/YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.m index 27e453eb..0a5070e3 100644 --- a/YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.m +++ b/YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.m @@ -107,7 +107,7 @@ } 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; diff --git a/YuMi/Modules/YMRoom/View/XPRoomViewController.m b/YuMi/Modules/YMRoom/View/XPRoomViewController.m index 2c692b0b..5bab2bec 100644 --- a/YuMi/Modules/YMRoom/View/XPRoomViewController.m +++ b/YuMi/Modules/YMRoom/View/XPRoomViewController.m @@ -221,6 +221,8 @@ XPCandyTreeInsufficientBalanceViewDelegate> @property (nonatomic, assign) BOOL currentUserWasOnMic; // 当前用户之前是否在麦上 @property (nonatomic, assign) NSInteger currentUserMicPosition; // 当前用户的麦位位置(-1表示不在麦上) @property (nonatomic, assign) BOOL hasCompletedRoomInitialization; // 是否已完成进房初始化 +/// 是否已请求按房间UID获取CP列表(首进房) +@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: 销毁完成"); } @@ -1874,6 +1935,9 @@ XPCandyTreeInsufficientBalanceViewDelegate> // 🔧 新增:初始化当前用户的麦位状态 [self initializeCurrentUserMicStatus]; + + // 🔧 首次进房:尝试在初始化就绪后触发按房间UID的CP拉取(幂等) + [self tryRequestInitialCpByRoomUidIfNeeded]; //上报进房 if (self.roomInfo != nil) { @@ -1887,6 +1951,18 @@ XPCandyTreeInsufficientBalanceViewDelegate> } } +/// 幂等触发:按房间UID拉取全量CP,仅在初始化完成后触发一次 +- (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]; @@ -4221,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 调用"); + // 初始化完成后,幂等尝试按房间UID拉取一次全量CP + [self tryRequestInitialCpByRoomUidIfNeeded]; }); }