前言
在ios开发中常常会有聊天功能,一般简单聊天功能只传输文字,但是稍微复杂点儿会有图片发送功能了.最全而且可支持扩展的例如微信,qq 聊天功能了.
传输方式各有千秋,如get,post,websocket,xmpp...等等
但最终避免不了一个问题,消息在队列里怎么通知前台view层 处理各种动作 如(发送失败,发送中,已读,未读 等)
正文
正式文章之前,我希望各种看官提前了解并熟悉一些技术点:
1.GCD (Grand Central Dispath)
2.BLOCK
3.dispatch queue
4.sync / async
I. 消息队列里,每个消息都有唯一messageid ,用户标记整个消息处理.
II. 每个Cell 一定有 NSNotification 提供消息通知器.
III. 每个Cell 必须唯一.
IV.Cell不可以承载太多retain操作,不然会导致界面很卡
CODE:
一条"文本"消息发出去时, MODEL层只对消息做本地缓存/NSMutableArray里插入操作,并不会直接导致界面刷新. 只是给这段消息obj标记为sending message.. 当cell dequeueReusableCellWithIdentifier 时,会在
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
里调用执行相关消息处理(如 消息发送)
if([message.messageSendStatus intValue] == messageSendStatusWillBeSend)
{
.....
[cell SendMessageRemoteImgOper:_objImgListOper WithMessage:dictionary type:messageType_text];
}
MARK:
_objImgListOper 是后台消息队列 obj.
dictionary 是要传递的消息,以及消息类型,时间,发送者,messageGuid,消息是否读取状态,消息发送状态.
messageType_text 文字类型
TODO:
SendMessageRemoteImgOper 函数里一般做两种处理:
1.给当前cell 注册通知
2.把消息放入队列开始处理
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sendMessageSucc:) name:_strSuccNotificationName object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sendMessageFail:) name:_strFailedNotificationName object:nil];
__block NSMutableDictionary *blockDict = [dict mutableCopy]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_main_queue(), ^{ if (_objRemoteImgListOper) { [_objRemoteImgListOper sendMessageGUID:guid ByDict:blockDict withProgress:nil]; }else{ // local } }); }); });
注意:
[_objRemoteImgListOper sendMessageGUID:guid ByDict:blockDict withProgress:nil]; 这个函数内部主要做同步(dispatch_sync)消息处理.
至于why?? 看前言....
- (void) sendMessageGUID:(NSString *)guid ByDict:(NSMutableDictionary*) dict withProgress:(id)progress { if (guid && guid.length > 0) { _strSuccNotificationName = [NSString stringWithFormat:@"RemoteImgOperListSucc%@", guid]; _strFailedNotificationName = [NSString stringWithFormat:@"RemoteImgOperListFailed%@", guid]; __block NSString *strBlockURL = [guid copy]; __weak id progressBlock = progress; dispatch_sync(_queueRemoteImgOper, ^{ BOOL bIsRequesting = NO; for (NSDictionary *dicItem in _arrRemoteImgOper) { NSString *strElementURL = [dicItem objectForKey:STR_ListElementURL]; if (strElementURL && [strElementURL isEqualToString:strBlockURL]) { RemoteImgOperator *objImgOper = [dicItem objectForKey:STR_ListElementRequest]; if (progressBlock) { [objImgOper setProgressDelegate:progressBlock]; }else{} bIsRequesting = YES; break; // break loop }else{} } if (!bIsRequesting) { RemoteImgOperator *objImgOper = [[RemoteImgOperator alloc] init]; [objImgOper setDelegate:self]; NSMutableDictionary *dicElement = [[NSMutableDictionary alloc] init]; [dicElement setObject:[strBlockURL copy] forKey:STR_ListElementURL]; [dicElement setObject:objImgOper forKey:STR_ListElementRequest]; // [dicElement setObject:dict forKey:STR_ListElementDictary]; [_arrRemoteImgOper addObject:dicElement]; [objImgOper sendMessage:guid withDict:dict progressDelegate:progress]; if (_arrRemoteImgOper && _arrRemoteImgOper.count > _iListSize) { 列表满,取消第一个的下载并推出。 NSDictionary *dicFirst = [_arrRemoteImgOper objectAtIndex:0]; if (dicFirst) { RemoteImgOperator *objOper = [dicFirst objectForKey:STR_ListElementRequest]; if (objOper) { [objOper cancelRequest]; objOper = nil; }else{} }else{} [_arrRemoteImgOper removeObjectAtIndex:0]; }else{} }else{} }); } }
重要: 消息最终发送和处理处
- (BOOL)sendMessage:(NSString *)strGUID withDict:(NSMutableDictionary * ) dict progressDelegate:(id)progress { BOOL bRet = NO; [self cancelRequest]; if (strGUID && (strGUID.length > 0)) { bRet = YES; [self cancelRequest]; downloadProgressDelegate = progress; __block NSString * guid = [strGUID copy]; __weak typeof(self) blockSelf = self; int messagetype = [DataHelper getIntegerValue:dict[@"messagetype"] defaultValue:0]; NSString * userID = [DataHelper getStringValue:dict[@"userid"] defaultValue:@""]; NSString * content = [DataHelper getStringValue:dict[@"text"] defaultValue:@""]; NSString * strSrcURL = [DataHelper getStringValue:dict[@"fileSrc"] defaultValue:@""]; switch (messagetype) { case messageType_text: case messageType_emj: { NSDictionary * parames = @{@"uid":userID,@"content":content}; [[MLNetworkingManager sharedManager] sendWithAction:@"message.send" parameters:parames success:^(MLRequest *request, id responseObject) { // {"push": false, "errno": 1, "result": {}, "cdata": "MWUEM", "error": "session not found"} if ([responseObject[@"errno"] intValue] == 0) { NSDictionary * dic = responseObject[@"result"]; NSString * messageId = [tools getStringValue:dic[@"msgid"] defaultValue:nil]; if (messageId) { dict[@"messageId"] = messageId; if (blockSelf.delegate && [blockSelf.delegate respondsToSelector:@selector(sendMessage:sendMsgSuccess:fromGuid:)]) { // delegate 通知获取成功 [blockSelf.delegate sendMessage:blockSelf sendMsgSuccess:dict fromGuid:guid]; } } }else{ //error ..... session not found if (blockSelf.delegate && [blockSelf.delegate respondsToSelector:@selector(sendMessage:sendMsgFailed:fromGuid:)]) { // delegate 通知获取失败 [blockSelf.delegate sendMessage:blockSelf sendMsgFailed:dict fromGuid:guid]; } } } failure:^(MLRequest *request, NSError *error) { if (blockSelf.delegate && [blockSelf.delegate respondsToSelector:@selector(sendMessage:sendMsgFailed:fromGuid:)]) { // delegate 通知获取失败 [blockSelf.delegate sendMessage:blockSelf sendMsgFailed:dict fromGuid:guid]; } }]; } break; case messageType_image: { //image 1是图片,2是声音,3是视频 4 map [self setuploadRemoteFile:guid FromURL:strSrcURL fileType:1 withParems:dict]; } break; case messageType_map: { //image map [self setuploadRemoteFile:guid FromURL:strSrcURL fileType:1 withParems:dict]; } break; case messageType_audio: { // file [self setuploadRemoteFile:guid FromURL:strSrcURL fileType:2 withParems:dict]; } break; case messageType_contacts: { // object contacts } break; default: break; } } else { bRet = NO; } return bRet; }
/** * 所有类型文件上传 class * * @param strSrcURL URL */ -(void) setuploadRemoteFile:(NSString * ) guid FromURL:(NSString *)strSrcURL fileType:(int) typeindex withParems:(NSMutableDictionary* ) parems { __weak typeof(self) blockSelf = self; //获取上传token 有效时间 3600 S = 1 hour.... //MRAK: that can be upload every files AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; NSMutableDictionary *parameters=[[NSMutableDictionary alloc] init]; [parameters setValue:parems[@"token"] forKey:@"token"]; [parameters setValue:@(typeindex) forKey:@"x:filetype"]; [parameters setValue:parems[@"text"] forKey:@"x:content"]; [parameters setValue:parems[@"length"] forKey:@"x:length"]; [parameters setValue:parems[@"userid"] forKey:@"x:toid"]; __block NSData * FileData; AFHTTPRequestOperation * operation = [manager POST:@"http://up.qiniu.com/" parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> formData) { // 1是图片,2是声音,3是视频 switch (typeindex) { case 1: { // 图片压缩处理 UIImage * image = [UIImage imageWithContentsOfFile:strSrcURL]; int Wasy = image.size.width/APP_SCREEN_WIDTH; int Hasy = image.size.height/APP_SCREEN_HEIGHT; int quality = Wasy/2; UIImage * newimage = [image resizedImage:CGSizeMake(APP_SCREEN_WIDTH*Wasy/quality, APP_SCREEN_HEIGHT*Hasy/quality) interpolationQuality:kCGInterpolationDefault]; NSData * FileData = UIImageJPEGRepresentation(newimage, 0.5); if (!FileData) { FileData = UIImageJPEGRepresentation(image, 0.5); } // NSData *FileData = [UIImage imageToWebP:newimage quality:75.0]; [formData appendPartWithFileData:FileData name:@"file" fileName:@"file" mimeType:@"image/jpeg"]; } break; case 2: { FileData = [NSData dataWithContentsOfFile:strSrcURL]; [formData appendPartWithFileData:FileData name:@"file" fileName:@"file" mimeType:@"audio/amr-wb"]; //录音 } break; case 3: { FileData = [NSData dataWithContentsOfFile:strSrcURL]; [formData appendPartWithFileData:FileData name:@"file" fileName:@"file" mimeType:@"audio/mp4-wb"]; //视频 } break; default: break; } } success:^(AFHTTPRequestOperation *operation, id responseObject) { SLog(@"responseObject :%@",responseObject); if ([responseObject[@"errno"] intValue] == 0) { NSDictionary * dic = responseObject[@"result"]; NSString * messageId = [tools getStringValue:dic[@"msgid"] defaultValue:@""]; NSString *url = [tools getStringValue:dic[@"url"] defaultValue:@""]; parems[@"messageId"] = messageId; parems[@"url"] = url; if (blockSelf.delegate && [blockSelf.delegate respondsToSelector:@selector(sendMessage:sendMsgSuccess:fromGuid:)]) { // delegate 通知获取成功 [blockSelf.delegate sendMessage:blockSelf sendMsgSuccess:parems fromGuid:guid]; } }else{ if (blockSelf.delegate && [blockSelf.delegate respondsToSelector:@selector(sendMessage:sendMsgFailed:fromGuid:)]) { // delegate 通知获取失败 [blockSelf.delegate sendMessage:blockSelf sendMsgFailed:parems fromGuid:guid]; } } } failure:^(AFHTTPRequestOperation *operation, NSError *error) { if (blockSelf.delegate && [blockSelf.delegate respondsToSelector:@selector(sendMessage:sendMsgFailed:fromGuid:)]) { // delegate 通知获取失败 [blockSelf.delegate sendMessage:blockSelf sendMsgFailed:parems fromGuid:guid]; } }]; [operation start]; }
稍后将会给出相关消息发送处理代码,但这只是我解决私信聊天的一种解决方式而已,可能我的做法是错误的,但可以作为大家实践取交集.
githu url: git@github.com:nicolastinkl/RemoteMessageListOperator.git
如果大家有ios相关问题可以直接邮件我 : nicolastinkl@gmail.com