Question:
1.刷新直播間消息機(jī)制該用哪種方法?哪一種更加合適?
2.聊天室該如何圖文混排?
3.聊天室出現(xiàn)特殊字符臨界點(diǎn)不換行?高度計(jì)算錯(cuò)誤?
4.聊天室該如何加載網(wǎng)絡(luò)圖片?
5.聊天室如何優(yōu)化?
6.交互時(shí)刷新消息經(jīng)常出現(xiàn)越界情況,導(dǎo)致崩潰?
7.聊天室出現(xiàn)阿拉伯文&中文&數(shù)字&英文等類似情況如何處理?
...
以上這些問(wèn)題我相信做過(guò)聊天室直播間的肯定或多或少遇到過(guò),那么本編文章就為了解決這些問(wèn)題而來(lái),底部提供Demo??。
1.刷新直播間消息機(jī)制該用哪種方法?哪一種更加合適?
聊天室刷新分為兩種:
一:采用來(lái)一條消息刷新一條消息,適用于小主播和人氣較少的直播間。優(yōu)點(diǎn)就是可以快速的查看到發(fā)言記錄,給人絲滑流暢感,缺點(diǎn)就是刷新過(guò)于頻繁,消耗性能。


二:定時(shí)刷新,適用于大主播或某段活動(dòng)時(shí)間內(nèi)用戶發(fā)言特別頻繁時(shí)間段,假設(shè)一秒鐘接收到幾十條消息時(shí)你直接一條條刷新是毫無(wú)意義的,不僅影響性能而且用戶也看不清。所以這種情況可以選擇弄個(gè)定時(shí)器,每0.5秒刷新一次。至于多久刷新一次,你可以根據(jù)主播人氣,觀看人數(shù),發(fā)言速度來(lái)調(diào)整。


通過(guò)對(duì)比,二者都是每秒接收到30條消息時(shí),一條條刷新和定時(shí)刷新所消耗的性能非常明顯。
如何選擇,各位自行斟酌。
2.聊天室該如何圖文混排?
如果在子線程生成我們的富文本,這個(gè)時(shí)候你肯定不可以在子線程創(chuàng)建UIimageView這些,然后轉(zhuǎn)UIimage,我個(gè)人有幾個(gè)技巧:
一:聊天肯定會(huì)帶有等級(jí)系列,拿我的直播來(lái)說(shuō),現(xiàn)在有0-100級(jí),我個(gè)人寫了一個(gè)等級(jí)生成器,通過(guò)業(yè)務(wù)生成每個(gè)等級(jí)段所對(duì)應(yīng)文字+圖片+等級(jí)+漸變色形成view,然后我們啟動(dòng)APP時(shí),通過(guò)view轉(zhuǎn)image,內(nèi)存就會(huì)保存有0-100張圖片,所占300k左右而已。那么下次用的時(shí)候,直接傳入等級(jí)獲取對(duì)應(yīng)的UIimage。
+ (instancetype)sharedInstance {
static EWLevelManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[EWLevelManager alloc] init];
instance.data = [NSMutableDictionary dictionary];
});
return instance;
}
- (void)setup {
[self.data removeAllObjects];
for (NSInteger i = 0; i <= 100; i++) {
// NDLeveBgView就是我的等級(jí)生成器,返回view
// 啟動(dòng)app我們調(diào)用一次這個(gè)方法,然后內(nèi)存就有生成0-100等級(jí)圖片
NDLeveBgView *view = [[NDLeveBgView alloc] init];
view.frame = CGRectMake(0, 0, 30.0, 14.0);
view.layer.cornerRadius = 2;
view.layer.masksToBounds = YES;
view.isShadeLv = YES;
view.level = i;
[self.data setObject:[self convertCreateImageWithUIView:view] forKey:[NSString stringWithFormat:@"%li", (long)i]];
}
// NSMutableData *data = [[NSMutableData alloc]init];
// NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data];
// [archiver encodeObject:self.data forKey:@"talkData"];
// [archiver finishEncoding];
// NSLog(@"查看byte = %lu", (unsigned long)data.length);
}
- (UIImage *)imageForLevel:(NSInteger)Level {
return [self.data objectForKey:[NSString stringWithFormat:@"%li", (long)Level]];
}
/** 將 UIView 轉(zhuǎn)換成 UIImage */
- (UIImage *)convertCreateImageWithUIView:(UIView *)view {
//UIGraphicsBeginImageContext(view.bounds.size);
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, [UIScreen mainScreen].scale);
CGContextRef ctx = UIGraphicsGetCurrentContext();
[view.layer renderInContext:ctx];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
至于圖片如何轉(zhuǎn)富文本我就不多贅述了,這種沒(méi)啥可說(shuō)的,都在demo中。
3.聊天室出現(xiàn)特殊字符臨界點(diǎn)不換行?高度計(jì)算錯(cuò)誤?
我想肯定有人遇到過(guò)這個(gè)問(wèn)題,當(dāng)我們使用UILabel時(shí),根據(jù)消息生成的富文本,高度計(jì)算得明明很正確,但就是不換行!!當(dāng)我們?cè)跍y(cè)試的時(shí)候明明運(yùn)行一切都正常,但是到了線上就出現(xiàn)不換行,高度計(jì)算錯(cuò)誤等等系列問(wèn)題。
我可以很明確的告訴大家,這就是UILabel的問(wèn)題,他在不同系統(tǒng)版本表現(xiàn)不一樣,經(jīng)過(guò)我多次測(cè)試,曾經(jīng)一度讓我懷疑是不是我的問(wèn)題,但是后來(lái)我換了YYLabel后,一切都清凈了,所以拋棄UILabel吧,它在表達(dá)復(fù)雜的文本時(shí),總是不那么如意。
跟我說(shuō)一句:YYKit牛逼???。。?/p>
可能有同學(xué)反駁,其實(shí)我也想證明是我的問(wèn)題,如果有同學(xué)用UILabel做的直播間聊天室,希望不吝賜教。
4.聊天室該如何加載網(wǎng)絡(luò)圖片?
這個(gè)問(wèn)題其實(shí)也好解決。
當(dāng)我們生成禮物消息富文本時(shí),先用禮物縮略的占位圖替代,同時(shí)使用SD下載該圖片,下載完了以后重新生成該富文本,接著通過(guò)代理回調(diào)去刷新該cell。
case NDSubMsgType_Gift_Text: { // 禮物彈幕(文本)消息
// 下載標(biāo)簽圖片
[self downloadTagImage];
// 下載禮物圖片
[self downloadGiftImage];
self.bgColor = NormalBgColor;
[self Gift_Text];
}
break;
/** 下載禮物縮略圖 */
- (void)downloadGiftImage {
NSString *urlStr = self.msgModel.giftModel.thumbnailUrl;
if (!urlStr || urlStr.length < 1) {
return;
}
if (self.finishDownloadGiftImg) {
return;
}
self.finishDownloadGiftImg = YES;
// 1. 如果本地有圖片
self.giftImage = [self cacheImage:urlStr];
if (self.giftImage) {
return;
}
// 2. 下載遠(yuǎn)程圖片
NSURL *url = [NSURL URLWithString:urlStr];
EWWeakSelf
id sdLoad = [[SDWebImageManager sharedManager] loadImageWithURL:url options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
} completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
if (image){
// 刷新UI
weakSelf.giftImage = image;
// 更新屬性文字
[weakSelf downloadTagImageFinish];
}
}];
[self.tempLoads addObject:sdLoad];
}
- (void)downloadTagImageFinish {
// 更新屬性文字
[self msgUpdateAttribute];
// 通知代理刷新屬性文字
if (self.delegate && [self.delegate respondsToSelector:@selector(attributeUpdated:)]) {
[self.delegate attributeUpdated:self];
}
}
/** 消息屬性文字發(fā)生變化(更新對(duì)應(yīng)cell) */
- (void)msgAttrbuiteUpdated:(NDMsgModel *)msgModel {
NSInteger row = [self.msgArray indexOfObject:msgModel];
if (row >= 0) {
[self.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:row inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
if (row == self.msgArray.count - 1) {
[self scrollToBottom:YES];
}
}
}
以上代碼都是關(guān)鍵部分,更詳細(xì)可以對(duì)照demo。
5.聊天室如何優(yōu)化?
聊天室的優(yōu)化其實(shí)也就是UITableView的優(yōu)化:
一:我們所有的cell通過(guò)復(fù)用,每一種消息類型對(duì)應(yīng)不同cell。

二:所有的消息所需要的背景顏色,富文本,高度等等都用模型記錄。

三:cell高度緩存,每次都從我們的模型讀取,更加高效快捷。
6.交互時(shí)刷新消息經(jīng)常出現(xiàn)越界情況,導(dǎo)致崩潰?
崩潰的原因大多數(shù)都在同時(shí)對(duì)同一個(gè)數(shù)組操作、插入indexPaths出現(xiàn)問(wèn)題等。
一:如何保證同一時(shí)間數(shù)組只執(zhí)行一種操作?
加鎖
鎖有好多種,有自旋鎖、信號(hào)量、遞歸鎖、互斥鎖等等
自旋鎖性能最高,但是經(jīng)過(guò)蘋果確認(rèn)是有問(wèn)題存在的,所以你可以選擇其他類型。
這里我選擇的是互斥鎖:
#pragma mark - 消息追加
- (void)addNewMsg:(NDMsgModel *)msgModel {
if (!msgModel) return;
pthread_mutex_lock(&_mutex);
// 消息不直接加入到數(shù)據(jù)源
[self.tempMsgArray addObject:msgModel];
pthread_mutex_unlock(&_mutex);
if (_reloadType == NDReloadLiveMsgRoom_Direct) {
[self tryToappendAndScrollToBottom];
}
}
/** 追加數(shù)據(jù)源 */
- (void)appendAndScrollToBottom {
if (self.tempMsgArray.count < 1) {
return;
}
pthread_mutex_lock(&_mutex);
// 執(zhí)行插入
.....代碼塊
pthread_mutex_unlock(&_mutex);
...代碼塊
//清空消息重置
- (void)reset {
pthread_mutex_lock(&_mutex);
...代碼塊
pthread_mutex_unlock(&_mutex);
}
}
更多詳見(jiàn)demo。
二:插入indexPaths出現(xiàn)越界問(wèn)題,其實(shí)這個(gè)問(wèn)題的產(chǎn)生也是因?yàn)槲覀儗?duì)數(shù)組同時(shí)操作而導(dǎo)致的,如果你解決了數(shù)組的問(wèn)題,那么這個(gè)問(wèn)題也迎刃而解。
7.聊天室出現(xiàn)阿拉伯文&中文&數(shù)字&英文等類似情況如何處理?
這個(gè)問(wèn)題非常有意思!具體可參考這篇文章
因?yàn)榘⒗?、希臘文等系列語(yǔ)言是強(qiáng)語(yǔ)言,并且從右向左排列,而我們的中文也屬于強(qiáng)語(yǔ)言,但是從左向右排列。當(dāng)這二者碰撞到一起會(huì)怎么樣呢?
到底是遵從我們的規(guī)則還是遵從阿拉伯文的規(guī)則?
我這里做的是強(qiáng)制按照中文規(guī)則排版。
// 強(qiáng)制排版(從左到右)
attribute.yy_baseWritingDirection = NSWritingDirectionLeftToRight;
attribute.yy_writingDirection = @[@(NSWritingDirectionLeftToRight | NSWritingDirectionOverride)];
// 強(qiáng)制排版(從左到右)
paraStyle.alignment = NSTextAlignmentLeft;
paraStyle.baseWritingDirection = NSWritingDirectionLeftToRight;
如果大家覺(jué)得有什么問(wèn)題的,可以評(píng)論指出。
最后就是我們的demo了,如果覺(jué)得可以的希望點(diǎn)個(gè)star哦,Github