ios-异步消息同步问题-典型使用场景: 微信私信界面

前言

 在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


原文地址:https://www.cnblogs.com/tinkl/p/3573454.html