iOS開發利用系統推送Notifaction和輪詢實現簡單聊天系統
話不多說,先看一下做好的聊天軟體介面:
首先在StoryBoard裡拖了一個UItableView和一個view用來輸入文字或者語音,右邊的按鈕用來切換文字和語音:
聊天裡有三種id:orderID :聊天idmessageID :每條訊息的IDsessionID :每個訂單的會話ID,如果為空白通過orderID請求。
然後在viewDidLoad裡做一些介面上的操作和一些初始化的操作:
1.設定一下tableview的headView
2.初始化錄音、帳戶圖片、擷取訂單詳情
//初始換錄音 [self initRecord]; //擷取帳戶圖片 [self getHeadImg]; //擷取訂單詳情 [self getOrderDetailInfo];
3.在viewWillAppear裡:a.註冊了一些鍵盤和聊天訊息的通知b.啟動了一個20秒的NSTimer輪詢擷取聊天訊息
c.self.chatArray讀取資料庫的聊天訊息,如果數組為空白返回,如果不為空白重新整理tableviewd .如果是從評價頁面過來,重新整理訂單狀態
4.看一下輪詢訊息的代碼:
#pragma mark - 輪詢訊息- (void)runLoopMessage { SpeakType speaker = [YCChatInfo getSpeakerPassengerBy:self.orderInfo.bigType]; [[YCReceiveMessageCenter defaultMessageCenter] getMessageListBySpeaker:speaker isRemote:NO andPushId:nil];}第一句代碼是擷取會話類型,這裡定義了一個枚舉值,主要有以下幾個角色:
//會話類型enum SpeakType { //Business YCDriverType = 11, YCPassenger = 12, YCSystem = 13, //v5.2.2 增加系統角色 YCLoctionAutoReply = 14, //v5.2.2添加本地自動回複 YCDriverAutoReply = 15, //v5.2.2添加司機自動回複 司機角色 YCLoctionUpdateVersionReply = 16, //v5.2.2 未知訊息類型回複 【提示不支援的訊息類型。請升級】};typedef NSInteger SpeakType;然後根據會話類型去請求聊天list介面,然後請求成功後對資料進行處理:
//isRemote 點擊推送欄訊息 if (response && [response[@"ret_code"] integerValue] == 200) { NSArray *array = response[@"result"]; id topVC = [[YCAppDelegate sharedDelegate] topViewController]; __block NSString *orderID = nil; __block NSString *dType = nil; [array enumerateObjectsUsingBlock:^(NSDictionary *result, NSUInteger idx, BOOL *stop) { NSString *type = [self controlMessageDispatch:result]; dType = type; //此處省略五百字 } } else { DLog(@"輪詢資料失敗 response = %@, error = %@", response, error); }
如果code == 200的時候證明請求成功,然後用數組取出所有的聊天訊息,然後用enumerateObjectsUsingBlock方法便利數組裡每個元素,每個元素即一條聊天訊息。然後通過controlMessageDispatch來擷取訊息的類型(dType):
typedef NS_ENUM(NSInteger, ClassType) { OrderClass = 1, ChatClass = 2, UserClass = 3,};第一個是訂單類型訊息,第二個是聊天類型訊息,第三個是賬戶訊息。如果是訂單訊息,根據type去判斷是什麼狀態,然後去發不同的Notification。如果是聊天類型把result傳入:
- (void)receiveChatMessage:(id)object
方法,然後有一個訊息狀態欄位kChatRepeatState,如果kChatRepeatState == 20,表示已讀訊息,直接返回。如果不是,用content初始化YCChatInfo:
NSDictionary *content = dic[@"content"];YCChatInfo *item;item = [[YCChatInfo alloc] initWithDictionary:content];
然後去判斷ChatType,有以下幾種:
//聊天 類型enum ChatType { ChatText = 1, ChatImage = 2, ChatAudio = 3, ChatPOI = 4, ChatMix = 5, //混合內容 ChatCard = 6,//v5.2.2卡片訊息 ChatUpdateHint = 701 //v5.2.2不支援類型升級提示 };typedef NSInteger ChatType;
如果是語音訊息,需要非同步先去請求下載語音訊息,下載完後先顯示到介面上同事置為未讀訊息然後再儲存到資料庫裡,然後回到主線程發NotifactionName:kChatMessageNotification通知聊天介面接收到聊天資訊,展示到介面後然後存到資料庫中:
[[NSNotificationCenter defaultCenter] postNotificationName:kChatMessageNotification object:nil userInfo:@{@"chatInfo" : chatInfo}];[self.chatStore insertChat:chatInfo];這裡上次有個bug,聊天訊息去重之後一直收不到語音訊息,就是因為我先把新聊天訊息先插入資料,然後再去發通知,導致往介面上顯示聊天訊息是總是顯示不上去。
如果是文字訊息,把state置為MessageRead已讀,然後發Notifaction通知聊天頁面,然後存到資料庫,如果是其他訊息類型,把content改為“不支援的訊息類型|您的目前的版本過低,點擊升級用戶端”,然後在發出通知,存到資料庫裡。
如果是第三種賬戶訊息,顯示小紅點,然後發Notifaction通知viewcontroller訊息中心有新訊息。接著擷取到dType後
//首先判斷是否是推送訊息, 且判斷該條點擊的推送id進入的 if (isRemote && [pushId isEqualToString:result[@"id"]]) { if (!orderID && ([type isEqualToString:@"new_chat"] || [type isEqualToString:@"DRIVER_ARRIVE"] || [type isEqualToString:@"RECEPTION_DRIVER"]|| [type isEqualToString:@"SERVICE_DONE"])) { if ([type isEqualToString:@"new_chat"]) { orderID = result[@"content"][@"topic"]; } else if (![topVC isKindOfClass:[YCSelectDriverViewController class]] && ([type isEqualToString:@"DRIVER_ARRIVE"] || [type isEqualToString:@"RECEPTION_DRIVER"] || [type isEqualToString:@"SERVICE_DONE"])) { orderID = result[@"content"][@"order_id"]; } } } }]; if (orderID) { if ([DefaultValueForKey(kShowWelcome) boolValue]) { if (![topVC isKindOfClass:[YCWelcomeVC class]]) { [[NSNotificationCenter defaultCenter] postNotificationName:kRemotePushVC object:nil userInfo:@{@"orderID" : orderID, @"type":dType}]; } } }
首先判斷是否是推送訊息, 且判斷該條點擊的推送id進入的,如果orderID不存在且如果type是新聊天訊息或司機已到達或者司機接單或者服務結束就進入if判斷裡,然後在判斷type是不是聊天,如果是聊天orderID是content裡的topic欄位,如果不是新聊天且當前最頂層ViewController不是YCSelectDriverViewController類且type是司機已到達或者司機接單或者服務結束就進入if判斷裡,orderID是content裡的order_id欄位。
如果orderID存在的情況下先判斷歡迎頁面是不是顯示過,然後再判斷當前最頂層ViewController不是歡迎頁面類,然後就發出Notifaction,然後通知view跳轉到指定的ViewController頁面。
當接收到聊天訊息,經過一系列資料處理後,發出通知,然後YCChatViewController裡會接到通知調用- (void)receiveMessage:(NSNotification *)notification方法:
- (void)receiveMessage:(NSNotification *)notification { YCChatInfo *chatInfo = notification.userInfo[@"chatInfo"]; //如果此時來的訊息不輸入當前會話 頁面不進行操作 NSString *string1 = [[NSString alloc] initWithFormat:@"%@", chatInfo.orderID]; NSString *string2 = [[NSString alloc] initWithFormat:@"%@", self.orderInfo.serverOrderId]; if (![string1 isEqualToString:string2]) { return ; } NSInteger messageID = chatInfo.messageID; YCChatStore *chatStore = [[YCChatStore alloc]init]; BOOL isNotRepeat = [chatStore selectDBwithMessageID:messageID]; if (!isNotRepeat) { return; } [self insertRowToTableViewByIndexPath:chatInfo isSend:NO];}第一句話是擷取通知裡傳的聊天訊息內容,然後拿chatinfo裡的orderID和通過擷取訂單詳情的介面裡獲得的orderID做比較,如果orderID不一致,直接return。如果一直就去YCChatStore裡的selectDBwithMessageID方法裡查詢資料庫是否有相同的messageID存在,如果存在直接return,如果不存在就插入介面
- (NSIndexPath *)insertRowToTableViewByIndexPath:(YCChatInfo *)chatInfo isSend:(BOOL)isSend { NSIndexPath *indexPath; [self.chatArray addObject:chatInfo]; indexPath = [NSIndexPath indexPathForRow:[self.chatArray count] - 1 inSection:0]; void(^ScrollBlock)() = ^{ DLog(@"%d",indexPath.row); [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }; [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:YCTableViewRowAnimationFromBottom completion:^{ if (isSend) { ScrollBlock(); } }]; if (!isSend) { ScrollBlock(); } return indexPath;}
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated這個方法是把第幾個indexpath滑動到tableview的最底部,- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths
withRowAnimation:(YCTableViewRowAnimation)animation
completion:(void(^)(void))animationCompletion
這是插入時候的一個動畫效果。
簡單介紹一下聊天系統裡邊所有model、view、controller相關類的名字和功能:
MODEL:
YCChatInfo: 主要定義了聊天介面裡所有用到欄位的定義,聊天相關的枚舉資訊和聊天訊息的一些BOOL判斷。
YCChatStore :主要是對聊天資料進行資料庫的建表、插入、刪除、查詢、去重、更新等操作。
YCChatRequest :封裝了聊天相關的網路請求。
YCChatRecord:主要是錄音相關的一些封裝,包括開始錄音、結束錄音、擷取錄音時間長度、音頻格式轉化、以及通過SessionID擷取音頻路徑。
YCReceiveMessageCenter:相當於NotifactionCenter,是聊天訊息處理的一個訊息中心,所有接受的聊天訊息和發送的聊天訊息都會經過MessageCenter處理。
Views:
YCChatTableView :繼承tableview的類,裡邊重寫了tableview的插入動畫,就是每次來新訊息時候的插入動畫。
YCChatBaseCell : 聊天裡所有的cell都繼承自此cell(文字、語音,未來還可能包括圖片、地理位置等等)裡邊主要定義了聊天時間的Label、背景圖片、頭像、司機名、發送時候的loading
YCChatAudioCell :語音交談cell,包括播放按鈕、小紅點提示、錄音時間label
YCChatImageCell :系統中暫時沒有用到,可能是為以後聊天可以發圖片做準備
YCChatTextCell : 發送文字交談cell
YCHeadView :頭像cell
YCRecordView : 聊天介面中按住說話的view
YCOrderRecordView :這個是下單中按住說話的view跟聊天系統沒關係
YCPlayButton :播放音頻按鈕
YCChatDriverAcceptCardCell :5.2.3版本新增預訂成功卡片
YCChatJourneyStartCell : 5.2.3版本新增司機已出發、司機已就位卡片
YCChatUpdatHintCell : 不支援類型,升級提示
Controllers:
YCChatViewController :聊天的主介面
YCChatMenuViewController:聊天右側的按鈕
YCShareJourneyCardVC:預訂成功卡片的詳細頁
YCChatMapVC:聊天卡片裡的地圖