某天收到個(gè)問題反饋瞭空,由于群組新增紅包功能揪阿,搶紅包時(shí)點(diǎn)擊紅包偶現(xiàn)卡頓問題,現(xiàn)象是點(diǎn)擊屏幕無響應(yīng)咆畏。
首先分析場(chǎng)景南捂,搶紅包的同時(shí)不斷的接收到新消息,大家可能體會(huì)過旧找,眼看一個(gè)大紅包被新消息頂出屏幕溺健,等到點(diǎn)開的時(shí)候就被搶完了。
查看業(yè)務(wù)邏輯钦讳,接受到新消息后會(huì)自動(dòng)滾屏到最新的一條消息矿瘦。于是不斷的收到消息,不斷的滾屏愿卒。這個(gè)過程中用戶觸摸屏幕缚去,就無響應(yīng)了。
相關(guān)核心代碼如下:
/// 實(shí)現(xiàn)該方法來監(jiān)控消息的接收
- (void)receiveNewMessage
{
[self.tableView scrollToBottomAnimated:YES];
}
/// scrollToBottomAnimated: 方法實(shí)現(xiàn)如下
- (void)scrollToBottomAnimated:(BOOL)animated {
NSUInteger finalRow = MAX(0, [self numberOfRowsInSection:0] - 1);
NSIndexPath *finalIndexPath = [NSIndexPath indexPathForRow:finalRow inSection:0];
[self scrollToRowAtIndexPath:finalIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:animated];
}
問題來了琼开,是什么原因造成無響應(yīng)的易结?
一開始以為是滾屏的動(dòng)作大量占用CPU時(shí)間,造成無法響應(yīng)用戶的觸控事件柜候。用Mock代碼模擬了下不斷收到新消息時(shí)點(diǎn)擊紅包的場(chǎng)景搞动,發(fā)現(xiàn)除了觸摸 tableView 不能響應(yīng)外,其它非tableView的區(qū)域還是可以響應(yīng)觸控事件的渣刷○兄祝可見并非是這個(gè)原因。
由于收到消息滾屏到底部的過程是有動(dòng)畫效果的辅柴,不斷的滾屏箩溃,動(dòng)畫是持續(xù)生效的。UIView默認(rèn)執(zhí)行動(dòng)畫時(shí)不響應(yīng)觸控事件的碌嘀。試著去掉動(dòng)畫效果涣旨,直接滑動(dòng)底部,就能響應(yīng)用戶的觸控事件了股冗。
- (void)scrollToBottomAnimated:(BOOL)animated {
NSUInteger finalRow = MAX(0, [self numberOfRowsInSection:0] - 1);
NSIndexPath *finalIndexPath = [NSIndexPath indexPathForRow:finalRow inSection:0];
[UIView animateKeyframesWithDuration:animated?0.3:0 delay:0 options:UIViewKeyframeAnimationOptionAllowUserInteraction | UIViewKeyframeAnimationOptionBeginFromCurrentState animations:^{
[self scrollToRowAtIndexPath:finalIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
} completion:^(BOOL finished) {
}];
}
按上述滾屏邏輯即可響應(yīng)用戶的觸控事件霹陡,可見是這個(gè)原因。
修復(fù)了這個(gè)問題止状,測(cè)試中發(fā)現(xiàn)偶爾還是點(diǎn)擊后無響應(yīng)烹棉。用戶的觸控事件應(yīng)該已經(jīng)傳給了UIWindow,難道是沒有傳給應(yīng)該響應(yīng)事件的View怯疤?
Hook下UIWindow的sendEvent:
方法峦耘,觀察下事件的傳遞:
@implementation UIWindow (KeyWindow)
+ (void)load
{
[self hookSwizzleSelector:@selector(sendEvent:) withSelector:@selector(y_sendEvent:)];
}
- (void)y_sendEvent:(UIEvent *)event
{
[self y_sendEvent:event];
}
@end
點(diǎn)擊后event如上圖所示,可見事件有傳遞給View旅薄,觀察到同時(shí)有Tap和Long事件辅髓,猜測(cè)是由于Long事件致使Tap事件失效導(dǎo)致的。查看代碼后發(fā)現(xiàn)在父類中有添加Long事件:
UILongPressGestureRecognizer *contentLongPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
contentLongPress.minimumPressDuration = 0.5;
[self.contentView addGestureRecognizer:contentLongPress];
修改后如下:
// 添加單擊手勢(shì):
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureResponse:)];
tapGesture.delegate = self;
[[self.contentView gestureRecognizers] enumerateObjectsUsingBlock:^(__kindof UIGestureRecognizer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:[UILongPressGestureRecognizer class]]) {
[tapGesture requireGestureRecognizerToFail:obj];
}
}];
[self.bubbleImageView addGestureRecognizer:tapGesture];
// 處理手勢(shì)沖突少梁,響應(yīng)單擊手勢(shì)時(shí)洛口,不響應(yīng)其它手勢(shì)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
return YES;
}
return NO;
}
還有優(yōu)化的空間嗎?指定執(zhí)行滾屏?xí)r的runloop為NSDefaultRunLoopModel凯沪,用戶觸摸屏幕時(shí)runloop會(huì)切換為EventTracking第焰,理論上這樣應(yīng)該會(huì)優(yōu)先響應(yīng)觸控事件。但測(cè)試中發(fā)現(xiàn)區(qū)別不大妨马,歡迎大家討論 這樣做是否能優(yōu)先響應(yīng)觸控事件挺举?
- (void)receiveNewMessage
{
[self performSelector:@selector(receiveOnlineMessageScrollToBottom) withObject:nil afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
}
- (void)receiveOnlineMessageScrollToBottom
{
[self.tableView scrollToBottomAnimated:YES];
}