一个不太完善的即时通讯流程架构图

即时通讯作为社交软件必不可少的功能,较好的通讯体验不仅提升用户的好感度,也增加用户对APP的粘度。

即时聊通讯功能应用广泛,例如:微信、QQ、微博等社交为主的应用。但是对于大部分并不是以社交为主体的小应用来说,更关心的是如何快速搭建并实现即时通讯功能,那么使用第三方SDK能满足基本需求,也能快速达到预期效果。常见的即时通讯SDK有:环信、融云、云通信、腾讯云、阿里云等等。无论集成还是使用都很方便,甚至还提供了配套的UI来帮助开发者,所以开发者只需要根据业务以及设计稿做一些调整就能完成即时通讯功能,大大地减少开发周期以及因为能力不足无法完成等问题。

本文介绍的不是如何使用SDK,也不是如何构建通讯架构,更不是如何实现具体的通讯功能。本文介绍的是应用中基于阿里云完成的即时通讯功能的实现流程图,围绕自己制作的一个流程图+解释,所以想要看完整的实现即时通讯功能的话,下面的内容就不适合看了,当然流程图只是个参考方向,功能不完全完善,看管自己把握,不喜勿喷。

整体流程架构图

image

功能模块区分

  • 处理好友差异模块
  • 阿里云管理模块
  • 处理离线消息模块
  • 处理消息模块
  • 处理发送消息模块
  • 获取历史消息模块
  • 删除消息模块
  • 转发消息模块
  • ….

模块功能介绍

处理好友差异模块

image

模块功能是获取好友、群、讨论组差异集合的,注意是差异集合,并不是全部。也就是好友有新增、删除的差异进行更新。获取服务器返回的差异集合进行处理,请求成功时删除本地的缓存,如果是新增好友并更新好友数据库,如果需要创建新的会话,就创建会话。如果是删除好友,删除好友、删除会话、删除消息。对于附带用户相关的设置信息,比如是否需要震动提醒,是否需要声音提醒等先关配置信息。其他就是根据实际的业务需要的数据了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// 伪代码

Network

[network getInitRelationOnSucceeded:^(NSDictionary *dic) {
if (setting) {
// 用户相关设置数据
}
if (friends.count > 0){
// 新增好友差异集合
[MessageTool proccessInitFriends:friends];
}
if (delete_friends.count > 0){
// 删除好友差异集合
[MessageTool proccessInitDeleteFriendByDeleteUids:delete_friends];
}
if (other) {
// 其他附带数据
}
dispatch_async(dispatch_get_main_queue(), ^{
// 获取离线消息
});
}
]

-------------------------------------------------------------------

MessageTool

/// 处理好友差异集合中新增好友
+ (void)proccessInitFriends:(NSArray *)friends{
if (friends.count <= 0) return;

// 删除好友缓存
[CacheManager clearNewFriendListCacheData];

for (NSDictionary * curDict in friends) {
@autoreleasepool {
// 保存新增的好友到数据库
FriendModel *friend = [[FriendModel alloc] initWithDictionary:curDict];
friend.friendJson = [friend modelToJson:friend];
FriendDB *friendDB = [[FriendDB alloc] init];
[friendDB saveModel:friend sqliteBlock:nil];

BOOL inSession = [[curDict objectForKey:@"in_session"] boolValue];
if(inSession){
// 需要新增会话,创建会话,保存到会话数据库中
BOOL sessionTop = [[curDict objectForKey:@"session_top"] boolValue];
long long time = [curDict[kParamV] longLongValue];
SessionModel *session = [SessionModel getSessionModelBySessionId:friend.uid sessionType:SessionTypeForMessageSingle sessionName:friend.name];
if(time > 0){
session.saveTime = time;
}
session.isTop = sessionTop;
SessionDB *sessionDB = [[SessionDB alloc] init];
[sessionDB saveModel:session sqliteBlock:nil];
}
}
}
}

/// 处理好友差异集合中删除好友
+ (void)proccessInitDeleteFriendByDeleteUids:(NSArray *)deleteUids{
if (friends.count <= 0) return;

// 删除好友缓存
[CacheManager clearNewFriendListCacheData];

for (NSNumber *uid in deleteUids.objectEnumerator) {
@autoreleasepool {
long long currentUid = [uid longLongValue];
FriendDB *friendDB = [[FriendDB alloc] init];
__block BOOL isSucceed = NO;
[friendDB removeModelByUid:currentUid sqliteBlock:^(BOOL isTrue) {
isSucceed = isTrue;
}];
if(isSucceed){
SessionDB *sessionDB = [[SessionDB alloc] init];
[sessionDB removeModelBySessionId:currentUid sessionType:SessionTypeForMessageSingle sqliteBlock:nil];

MessageDB *messageDB = [[MessageDB alloc] init];
[messageDB removeMessageBySessionId:currentUid sessionType:SessionTypeForMessageSingle sqliteBlock:nil];
}
}
}
}

阿里云管理模块

即时通讯是基于阿里云的,那么相比要对阿里云进行封装处理,阿里云管理类ALiPushManager被AppDelegate拥有,也就是全局性的。ALiPushManage类r主要分为三打功能,一是初始化,二是添加定时器用于定时获取离线消息,三是接收即时的通讯消息。

为什么有了接收即时消息功能了还需要添加定时获取离线消息呢?原因是为了保证消息的同步,谁也说不定即时消息能即时到达,不管是网络原因还是阿里云SDK原因还是各种原因,那么有了定时获取离线消息,能保证阿里云推送不到的时候可以通过获取离线消息来获取即时消息,接下来就是要处理重复消息即可。

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 伪代码

+ (id)share {
static ALiPushManager *aliPushManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
aliPushManager = [[ALiPushManager alloc] init];

//注册接收CloudChannel推送下来的消息(可用于实时消息聊天,或者及时消息指令)
[[NSNotificationCenter defaultCenter] addObserver:aliPushManager selector:@selector(receiveAliyunTcpMessage:) name:KNOTIF_ALIYUN_SOCKET_MESSAGE object:nil];

// 添加定时器
aliPushManager.timerForGetOffLineMsg = [NSTimer scheduledTimerWithTimeInterval:45 target:aliPushManager selector:@selector(isNeedGetOffLineMsg) userInfo:nil repeats:YES];
});
return aliPushManager;
}
  • 定时获取离线消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 伪代码
- (void)isNeedGetOffLineMsg{
if(_needTurnNextTime){
_needTurnNextTime = NO;
return;
}
if(KGLOBALINFOMANAGER.appEnterBackground){
return;
}
if(KGLOBALINFOMANAGER.isLockForRefreshUI){
return;
}
if(KGLOBALINFOMANAGER.isLogin == NO){
return;
}
//获取离线消息
MessageParamModel *paramModel = [MessageParamModel new];
// 此请求设置为心跳包
paramModel.isHeartbeat = YES;
[network getMessageOffLineByParamModel:paramModel];
}
  • 注册监听即时消息
1
2
3
4
5
6
7
8
9
10
11
// 伪代码
- (void)receiveAliyunTcpMessage:(NSNotification *)notification {
//接收推送下来的消息(可用于实时消息聊天,或者及时消息指令)
_needTurnNextTime = YES;
CCPSysMessage *data = [notification object];
if(KGLOBALINFOMANAGER.isLogin && !KGLOBALINFOMANAGER.isLockForRefreshUI){
// 1、组装为消息模型MessageModel

// 2、处理消息
}
}

处理离线消息模块

离线消息模块是请求服务器获取离线消息,进行一些列操作的模块。

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// 伪代码
-(void)getMessageOffLine {
// 如果是正在获取离线消息,直接return
if(KGLOBALINFOMANAGER.isGetOffLineMsg){
return;
}
KGLOBALINFOMANAGER.isGetOffLineMsg = YES;

[network opGetWithUrlPath:kPathOfflineMessage
params:dicParam
onSucceeded:^(NSDictionary *dic) {
NSDictionary *dicData = [dic objectForKey:kParamData];
OffLineMessageModel *model = [OffLineMessageModel getModelByDic:dicData];
model.isHeartbeat = paramModel.isHeartbeat;

// 处理获取的离线消息
[MessageTool proccessOffLineMessageByOffLineMessageModel:model];

} onError:^(Error *error) {
// 错误处理
}];

}

------------------------------------------------------------
OffLineMessageModel

/// 获取离线消息总数(包括单聊、群聊、讨论组)
- (NSArray *)getArrSocketMsg{
NSMutableArray *arrMsg = [NSMutableArray new];
self.notify.count > 0?[arrMsg addObjectsFromArray:self.notify]:@"";
self.singled.count > 0?[arrMsg addObjectsFromArray:self.singled]:@"";
self.multiple.count > 0?[arrMsg addObjectsFromArray:self.multiple]:@"";
NSArray *arrSocketMsg = [MessageModel getArrModelByArrServiceMsgs:arrMsg];
return arrSocketMsg;
}

------------------------------------------------------------
MessageTool

/// 根据服务器获取的字典消息数组转成msgModel数组
+ (NSArray *)getArrModelByArrServiceMsgs:(NSArray *)msgDictDataArray {
NSMutableArray * tmpArrays = nil;
if (msgDictDataArray && msgDictDataArray.count > 0) {
tmpArrays = [NSMutableArray new];
// 遍历会话数据msgDictDataArray
for (NSDictionary *dataDict in msgDictDataArray) {

// 获取会话总条数
NSInteger count = [dataDict getIntValueByKey:kParamCount];

// 相关参数。。。

if([dataDict objectForKey:kParamMsgs] && [dataDict objectForKey:kParamMsgs] != kNull){

// 获取会话消息数
NSArray *msgDictArray = [dataDict objectForKey:kParamMsgs];

// 相关参数。。。

if(count > msgDictArray.count){
// 清除当前会话缓存消息
MessageDB *messageDB = [[MessageDB alloc] init];
[messageDB removeMessageBySessionId:sessionId sessionType:sessionType sqliteBlock:nil];
}

for (NSDictionary *dicMsg in msgDictArray.objectEnumerator) {
// 组装为消息model

// 执行消息相关类型
// 如果是语音类型,下载语音

// 添加model
[tmpArrays addObject:model];
}
}
else{
// 组装为消息model

// 添加model
[tmpArrays addObject:model];
}
}
}
return tmpArrays;
}

/// 处理获取的离线消息
+ (void)proccessOffLineMessageByOffLineMessageModel:(OffLineMessageModel *)model{
// 获取消息model数组
NSArray *arrSocketMsg = [model getArrSocketMsg];
if(arrSocketMsg.count > 0){

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < arrSocketMsg.count; i++) {
MessageModel *msg = [arrSocketMsg objectAtIndex:i];
msg.isHeartbeat = model.isHeartbeat;

// 处理消息
[MessageTool receiveMessageFromUser:msg hasReadNotifyMessage:YES];

// 存储消息的提醒更新数

if(i == arrSocketMsg.count - 1){
//发送一个刷新通知
dispatch_async(dispatch_get_main_queue(), ^{
// 发送相关UI通知
[MessageTool proccessFinishOffLineByOffLineMessageModel:model hasMessage:YES];

});
}
}
});
}
else{
// 发送相关UI通知
[MessageTool proccessFinishOffLineByOffLineMessageModel:model hasMessage:NO];
}
}

/// 发送相关UI通知
+ (void)proccessFinishOffLineByOffLineMessageModel:(OffLineMessageModel *)model hasMessage:(BOOL)hasMessage{
if(!model.isHeartbeat){
// 请求相关接口
// 用户信息
// 好友列表等
}

if(model.type > 0){
// 请求服务器离线消息阅读状态
}

if(hasMessage){
// 发送消息列表更新通知

// 发送tabbar更新通知

// 发送离线消息完成更新通知
}

KGLOBALINFOMANAGER.isLockForRefreshUI = NO;
KGLOBALINFOMANAGER.isGetOffLineMsg = NO;

}

处理消息模块

处理消息模块是一个消息的汇总模块,包括聊天消息、好友相关消息、群聊相关消息等。通过传入一个消息,对消息进行一些组装、存储、更新到UI等功能。

image

由于模块功能比较多,这里以聊天聊天消息功能进行说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 伪代码
/// 处理聊天消息
- (void)proccessSessionMessageByMessageInfo:(MessageModel *)messageInfo{

// 根据消息类型,设置是单聊、群聊、还是小助手会话类型id
long long sessionId = [MessageNotifyProccessTool getSessionIdByMsg:messageInfo];
messageInfo.sessionId = sessionId;

//过滤重复的消息
if([self.messageDBPorxy checkDBHasMessage:messageInfo]){
return;
}

//写入消息数据库
[self.messageDBPorxy saveModel:messageInfo sqliteBlock:nil];


SessionModel *sessionModel = [MessageNotifyProccessTool getSessionModelByMsg:messageInfo];
//没有会话名称,则当错误消息处理
if(sessionModel.sessionName.length == 0){
return;
}
//写入最近会话数据库
[self.sessionsDBPorxy saveModel:sessionModel sqliteBlock:nil];

// 如果是UI锁住,不进行下面操作
if(KGLOBALINFOMANAGER.isLockForRefreshUI){
return;
}

// 更新消息提醒数
[MessageNotifyProccessTool updateNewCountBySessionId:messageInfo.sessionId sessionType:messageInfo.sessionType sessionSecondId:messageInfo.sessionSecondId];

//发送一个刷新通知
dispatch_async(dispatch_get_main_queue(), ^{

if(sessionModel.isReceive){
// 发送震动、声音提醒通知
[MessageNotifyProccessTool proccessReceiveMessageNeedQuakeOrSoundByMessage:messageInfo];
}
// 发送更新UI通知
[MessageNotifyProccessTool sendNotificationByMessage:messageInfo];

});
}

处理发送消息模块

发送消息模块负责处理发送聊天信息,需要注意的是图片、语音类型的消息如何处理。

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// 伪代码

///发送消息
- (void)sendMessage:(MessageModel *)msgModel {

// 发送消息到服务器
[network postMessageByParamModel:msgModel];

// 添加到当前数据数组,刷新列表
[_messageListArray addObject:msgModel];
_contentListView.messageArray = _messageListArray;
[self reloadData];
}

-----------------------------------------------------------------------
MessageTool

//发送消息前处理相关数据
+ (void)proccessSendMessageByMessageInfo:(MessageModel *)messageInfo
finishBlock:(JsonModelBlock)finishBlock
errorBlock:(ErrorBlock)errorBlock{

// 添加新的消息到消息DB
messageInfo.state = SocketMessageStateSubmit;
MessageDB *messageDB = [[MessageDB alloc] init];
[messageDB saveModel:messageInfo sqliteBlock:nil];

// 更新会话DB
SessionModel *sessionModel = [MessageNotifyProccessTool getSessionModelByMsg:messageInfo];
SessionDB *sessionsDB = [[SessionDB alloc] init];
[sessionsDB saveModel:sessionModel sqliteBlock:nil];

// 如果是图片、语音类型
switch (messageInfo.contentType) {
case ContentTypeForImage:
case ContentTypeForAudio:{
// 上传文件
[self proccessUploadFileByMessageInfo:messageInfo finishBlock:finishBlock errorBlock:errorBlock];
break;
}
default:{
[BlockTool executeModelBlock:finishBlock model:messageInfo];
}
break;
}
}

//处理文件消息
+ (void)proccessUploadFileByMessageInfo:(MessageModel *)messageInfo
finishBlock:(JsonModelBlock)finishBlock
errorBlock:(ErrorBlock)errorBlock{
// 文件名不能为空
if (!messageInfo.filePath || !messageInfo.fileName) {
Error *error = [[Error alloc] init];
error.errorCode = ERRCODE0002;
error.errorDescription = @"message filePath or fileName not nil";
[BlockTool executeErrorBlock:errorBlock error:error];
return;
}

// 检查是否已经上传过
if([MessageTool checkFileMessageHasUploadByMessageInfo:messageInfo finishBlock:finishBlock]){
return;
}
@weakify(self)
FileType fileType = [[MessageTool getDicFileType] getIntValueByKey:@(messageInfo.contentType)];
messageInfo.state = SocketMessageStateUpdateFile;
// 上传到服务器
[[FileNetwork shareEngine] postFileByName:messageInfo.fileName Path:messageInfo.filePath fileType:fileType uploadProgressBlock:^(NSProgress *uploadProgress) {

} onSucceeded:^(NSDictionary *dic) {
@strongify(self)
if (RESPONSESUCCESS) {
NSDictionary *dicData = [dic objectForKey:kParamData];
NSString *strUrl = [dicData getStrValueByKey:kParamFileId];
// 组装消息model,替换为新的路径
[self assembleFileMsgByMessageInfo:messageInfo fileUrl:strUrl finishBlock:finishBlock];
}
} onError:^(TGError *error) {
[BlockTool executeErrorBlock:errorBlock error:error];
}];
}

/// 发送消息过程中错误处理
+ (void)proccessSendMsgError:(Error *)error
messageInfo:(MessageModel *)messageInfo{

// 修改消息model
switch (messageInfo.contentType) {
case ContentTypeForImage:{
NSDictionary *dicContent = @{kParamFileName:ExistStringGet(messageInfo.fileName)
,kParamFilePath:ExistStringGet(messageInfo.filePath)};
messageInfo.content = [Tools getStrJsonByDic:dicContent];
}
break;
case ContentTypeForAudio:{
NSDictionary *dicContent = @{kParamFileName:ExistStringGet(messageInfo.fileName)
,kParamFilePath:ExistStringGet(messageInfo.filePath)
,kParamSecond:ExistStringGet(messageInfo.content)};
messageInfo.content = [Tools getStrJsonByDic:dicContent];
}
break;
default:
break;
}

// 更新消息DB
messageInfo.state = SocketMessageStateFail;
MessageDB *messageDB = [[MessageDB alloc] init];
[messageDB saveModel:messageInfo sqliteBlock:nil];

// 更新会话DB
SessionModel *sessionModel = [MessageNotifyProccessTool getSessionModelByMsg:messageInfo];
SessionDB *sessionsDB = [[SessionDB alloc] init];
[sessionsDB saveModel:sessionModel sqliteBlock:nil];

// 发送相关通知
// 发送发送消息失败通知
// 发送更新UI通知

}

-----------------------------------------------------------------------
Network

/// 发送消息
-(void)postMessageByParamModel:(id<MessageParamProtocol>)paramModel
onSucceeded:(JsonModelBlock)succeededBlock
onError:(ErrorBlock)errorBlock{

@weakify(self)
[MessageTool proccessSendMessageByMessageInfo:paramModel.messageInfo finishBlock:^(__kindof JsonModel *model) {
@strongify(self)
paramModel.messageInfo = model;
// 处理完之后请求服务器
[self proccessSendMessageByParamModel:paramModel onSucceeded:succeededBlock onError:errorBlock];
} errorBlock:^(TGError *error) {
[MessageTool proccessSendMsgError:error messageInfo:paramModel.messageInfo];
[BlockTool executeErrorBlock:errorBlock error:error];
}];

}


/// 处理完之后请求服务器
- (void)proccessSendMessageByParamModel:(id<MessageParamProtocol>)paramModel onSucceeded:(JsonModelBlock)succeededBlock onError:(ErrorBlock)errorBlock{

// 相关参数配置

[network opPostWithUrlPath:kPathMessage
params:dicParam
onSucceeded:^(NSDictionary *dic) {
DLog(@"message:%@",[dic objectForKey:kParamMessage]);
NSDictionary * dataDict = [dic getOBJCValueByKey:kParamData];
if (dataDict) {
[messageInfo setValueForHttpResponseByDicData:dataDict];
messageInfo.state = SocketMessageStateSent;

// 发送成功,转到处理消息
[[MessageNotifyProccessTool share] proccessSessionMessageByMessageInfo:messageInfo];
}
ExistActionDo(succeededBlock, succeededBlock(messageInfo));
} onError:^(TGError *error) {
// 发送失败处理
[MessageTool proccessSendMsgError:error messageInfo:paramModel.messageInfo];
[BlockTool executeErrorBlock:errorBlock error:error];
}];
}

获取历史消息模块

获取历史消息应用场景在于用户下拉查看以往消息的时候,过程其实跟获取离线消息过程相似。

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// 伪代码

//获取历史消息
- (void)getMessageListBySessionId:(long long)sessionId
sessionType:(SessionType)sessionType
sessionSecondId:(long long)sessionSecondId
start:(NSInteger)start
limit:(NSInteger)limit
timeStamp:(NSString *)timeStamp
sqliteBlock:(SqliteArrayBlock)sqliteBlock{

// 组装查询条件SQL语句
NSString * sessionTypeStr = [self getSessionTypeNameByType:sessionType];
NSString *strWhere = [NSString stringWithFormat:@"sessionId = %@ AND sessionType = '%@'",@(sessionId),sessionTypeStr];
NSString *strCondition = [NSString stringWithFormat:@"WHERE (%@) ORDER BY time DESC LIMIT %@,%@",strWhere,@(start),@(limit)];

// 从数据库中获取
[self.dbTool executeSelectByTablename:TABLE_NAME_MESSAGES arrField:nil dicCondition:nil conditionAndOrderBySql:strCondition completeBlock:^(BOOL bSuccess, id result) {
// 操作成功
if (bSuccess) {
NSMutableArray * messages = [NSMutableArray new];
// 组装为消息model数组
for (NSDictionary *dicMsg in result) {
@autoreleasepool {
MessageModel *msgModel = [[MessageModel alloc] initWithDictionary:dicMsg];
if (msgModel.contentType == ContentTypeForImage){
msgModel.filePath = msgModel.content;
}else if (msgModel.contentType == ContentTypeForAudio){
msgModel.filePath = msgModel.content;
}
[messages addObject:msgModel];
}
}

// 回调数据给UI
if(messages.count > 0){
ExistActionDo(sqliteBlock, sqliteBlock(NO,messages));
} else{
// 请求服务器获取历史消息
[network getHistoryMessageByParamModel:参数];
}
} else{
// 操作失败
Error *error = [[Error alloc] init];
error.errorCode = ERRCODE0002;
error.errorDescription = @"获取历史消息失败";
//回调给UI层
// 发送更UI通知
}
}];
}


------------------------------------------------------------
Network

-(void)getHistoryMessageByParamModel:(id<MessageParamProtocol>)paramModel
onSucceeded:(NSArrayBlock)succeededBlock
onError:(ErrorBlock)errorBlock{

NSDictionary *dicParam = [paramModel getParamDicForGetHistoryMessage];

[network opGetWithUrlPath:kPathMessage
params:dicParam
onSucceeded:^(NSDictionary *dic) {
if(RESPONSESUCCESS){
NSArray *arrHistoryMsg = [NSArray new];
NSArray * historyMessages = [dic getOBJCValueByKey:kParamData];
if (historyMessages && historyMessages.count > 0) {
arrHistoryMsg = [MessageTool getArrModelByArrServiceMsgs:historyMessages];
}

// 回调arrHistoryMsg数据给UI层
// 发送更新UI通知,附带相关信息
}
} onError:^(Error *error) {
// 发送更新UI通知,附带错误信息
// 回调error错误给UI
}];
}

------------------------------------------------------------
MessageTool

/// 根据服务器获取的字典消息数组转成msgModel数组
+ (NSArray *)getArrModelByArrServiceMsgs:(NSArray *)msgDictDataArray {
NSMutableArray * tmpArrays = nil;
if (msgDictDataArray && msgDictDataArray.count > 0) {
tmpArrays = [NSMutableArray new];
// 遍历会话数据msgDictDataArray
for (NSDictionary *dataDict in msgDictDataArray) {

// 获取会话总条数
NSInteger count = [dataDict getIntValueByKey:kParamCount];

// 相关参数。。。

if([dataDict objectForKey:kParamMsgs] && [dataDict objectForKey:kParamMsgs] != kNull){

// 获取会话消息数
NSArray *msgDictArray = [dataDict objectForKey:kParamMsgs];

// 相关参数。。。

if(count > msgDictArray.count){
// 清除当前会话缓存消息
MessageDB *messageDB = [[MessageDB alloc] init];
[messageDB removeMessageBySessionId:sessionId sessionType:sessionType sqliteBlock:nil];
}

for (NSDictionary *dicMsg in msgDictArray.objectEnumerator) {
// 组装为消息model

// 执行消息相关类型
// 如果是语音类型,下载语音

// 添加model
[tmpArrays addObject:model];
}
}
else{
// 组装为消息model

// 添加model
[tmpArrays addObject:model];
}
}
}
return tmpArrays;
}

删除消息模块

删除消息功能应用场景是在在消息列表中,一般用户长按消息出现UIMenuItem,选择删除item之后的操作。

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 伪代码

- (void)deleteMessage:(id)sender {
if (_curSelectMessageInfo) {
__weak ChatBaseView *weakSelf = self;
[AlertView showNormalWithTitle:nil
message:@"确定删除?"
cancel:kAlertCancel
cancelHandler:nil
submit:kAlertSure
submitHandler:^(AlertView *alertView) {

BOOL isLatest = NO;
isLatest = [[_messageListArray lastObject] isEqual:_curSelectMessageInfo];

// 列表删除数据,刷新消息列表
[_messageListArray removeObject:_curSelectMessageInfo];
[weakSelf reloadData];

// 删除消息DB中当前的消息
MessageDB *messageDB = [[MessageDB alloc] init];
[messageDB removeMessage:_curSelectMessageInfo];

// 判断message是不是最后一条数据
if (isLatest) {
SessionDB *sessionDB = [[SessionDB alloc] init];
if(_messageListArray.count > 0) {
MessageModel *lastMsg = [_messageListArray lastObject];
[self updateSessionLastMsgByMsg:lastMsg];
return;
}
_session.contentType = ContentTypeForText;
_session.lastMsg = @"";
[sessionDB saveModel:_session sqliteBlock:^(BOOL isTrue) {
}];
}

}];
}

}

转发消息模块

转发消息功能应用场景是在在消息列表中,一般用户长按消息出现UIMenuItem,选择转发item之后的操作。

1
2
3
4
5
6
7
8
9
10
11
12
// 伪代码

/// 转发消息
- (void)referenceMessage:(id)sender {
if (_curSelectMessageInfo) {
// 组装要转发的数据

// 跳转到好友列表,附带转发的数据
}
}

在好友列表中选中好友,跳转到与好友的聊天页面,判断是否有会话,没有就创建新的会话,保存到会话数据库中,走发送消息的流程(发送的消息就是转发的消息),接下来就是走发送消息的流程,具体查看发送消息模块

流程图流程说明

  • APP启动

app启动时创建阿里云管理类,上面已说明阿里云管理类的主要功能。当APP执行到applicationDidBecomeActive:方法时,全局参数isLockForRefreshUI进行锁住,为什么要锁住呢?这个参数是作为获取离线消息是否成功的标志,防止定时器获取离线消息时刷新UI,暂时不需要刷新UI的话就要对isLockForRefreshUI进行锁住。所以已启动就要将isLockForRefreshUI赋值为YES,除了获取好友差异集合->获取取离线消息成功之后isLockForRefreshUI才会设置为NO。

  • 好友差异集合

到了获取好友差异集合,那么如果失败会进行递归获取,直到获取成功。当成功之后获取离线消息。

  • 离线消息

这里有一个全局参数isGetOffLineMsg进行控制,isGetOffLineMsg表示如果正在获取的情况下return掉,不会进行请求。从好友接口到获取离线消息这过程不会设置为心跳包,而定时器获取则为是心跳包请求。

  • 处理离线消息

获取离线消息成功之后对一切数据进行处理,具体的功能查看处理消息模块即可。这个功能也适用于阿里云推送下来的消息以及发送消息成功之后对消息的一系列操作。

声明

转载请注明出处。