前言
在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, ), ^{
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
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 > ) { _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 > ))
{
bRet = YES; [self cancelRequest];
downloadProgressDelegate = progress; __block NSString * guid = [strGUID copy];
__weak typeof(self) blockSelf = self;
int messagetype = [DataHelper getIntegerValue:dict[@"messagetype"] defaultValue:];
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] == ) { 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: withParems:dict];
}
break;
case messageType_map:
{
//image map
[self setuploadRemoteFile:guid FromURL:strSrcURL fileType: withParems:dict];
}
break;
case messageType_audio:
{
// file
[self setuploadRemoteFile:guid FromURL:strSrcURL fileType: 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 :
{
// 图片压缩处理
UIImage * image = [UIImage imageWithContentsOfFile:strSrcURL];
int Wasy = image.size.width/APP_SCREEN_WIDTH;
int Hasy = image.size.height/APP_SCREEN_HEIGHT;
int quality = Wasy/;
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 :
{
FileData = [NSData dataWithContentsOfFile:strSrcURL];
[formData appendPartWithFileData:FileData name:@"file" fileName:@"file" mimeType:@"audio/amr-wb"]; //录音
}
break;
case :
{
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] == ) {
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