中小型公司/初創型: 建議使用第三方. 好處: 快, 符合快速開發的需求, 本身和後臺人員不須要作什麼操做 缺點: 你的數據會通過人家的服務器, 可能會不安全
好處: 源碼開源, 能夠自行拓展功能, 網上也有不少案例 缺點: 本身和後臺人員須要作不少的操做(後臺須要額外提供一些接口), 聊天服務器的穩定性可能不夠好(看公司本身的運維人員技術是否夠好), XML會耗流量
好處: 接口能夠自定義, 可使用低流量的傳輸格式 缺點: 須要必定的自定義協議的經驗, 包括對數據處理的經驗, 對技術能力有必定的要求
環信V3版本使用了自定義協議
環信以前的版本是基於XMPP封裝的數據庫
環信只是即時通信的消息通道。環信自己不提供用戶體系,環信既不保存任何 APP 業務數據,也不保存任何 APP 的用戶信息。好比說,你的 APP 是一個婚戀交友 APP,那麼你的 APP 用戶的頭像、暱稱、身高、體重、三圍、電話號碼等信息是保存在你本身的 APP 業務服務器上,這些信息不須要告訴環信,環信也不想知道。api
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //AppKey:註冊的AppKey,詳細見下面註釋。 //apnsCertName:推送證書名(不須要加後綴),詳細見下面註釋。 EMOptions *options = [EMOptions optionsWithAppkey:@"czbk#hmwechat"]; options.apnsCertName = nil; [[EMClient sharedClient] initializeSDKWithOptions:options]; return YES; } // APP進入後臺 - (void)applicationDidEnterBackground:(UIApplication *)application { //以後的消息, 應該發送遠程推送 [[EMClient sharedClient] applicationDidEnterBackground:application]; } // APP將要從後臺返回 - (void)applicationWillEnterForeground:(UIApplication *)application { //以後的消息, 取消遠程推送 [[EMClient sharedClient] applicationWillEnterForeground:application]; }
@ #warning 未來註冊和登陸, 應該調用服務器的接口, 這裏只是爲了方便測試, 使用的環信接口緩存
- (IBAction)loginClick:(id)sender { EMError *error = [[EMClient sharedClient] loginWithUsername:self.usernameTF.text password:self.passwordTF.text]; if (!error) { NSLog(@"登陸成功"); //跳轉根控制器 UIStoryboard *mainSB = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; HMTabBarController *tabBarC = [mainSB instantiateViewControllerWithIdentifier:@"HMTabBar"]; [UIApplication sharedApplication].keyWindow.rootViewController = tabBarC; } } - (IBAction)registerClick:(id)sender { /* 註冊模式分兩種,開放註冊和受權註冊。 只有開放註冊時,才能夠客戶端註冊。開放註冊是爲了測試使用,正式環境中不推薦使用該方式註冊環信帳號。 受權註冊的流程應該是您服務器經過環信提供的 REST API 註冊,以後保存到您的服務器或返回給客戶端。 */ EMError *error = [[EMClient sharedClient] registerWithUsername:self.usernameTF.text password:self.passwordTF.text]; if (error==nil) { NSLog(@"註冊成功"); } }
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == 3) { /* 退出登陸分兩種類型:主動退出登陸和被動退出登陸。 主動退出登陸:調用 SDK 的退出接口; 被動退出登陸:1. 正在登陸的帳號在另外一臺設備上登陸;2. 正在登陸的帳號被從服務器端刪除。 logout:YES: (遠程推送)是否解除 device token 的綁定,在被動退出時 SDK 內部處理,須要調用退出方法。 */ EMError *error = [[EMClient sharedClient] logout:YES]; if (!error) { NSLog(@"退出成功"); //切換到登錄界面 UIStoryboard *mainSB = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; HMLoginViewController *loginVC = [mainSB instantiateViewControllerWithIdentifier:@"HMLogin"]; [UIApplication sharedApplication].keyWindow.rootViewController = loginVC; } } } //添加回調監聽代理: [[EMClient sharedClient] addDelegate:self delegateQueue:nil]; #pragma mark - 被動退出 /*! * 當前登陸帳號在其它設備登陸時會接收到該回調 */ - (void)userAccountDidLoginFromOtherDevice { EMError *error = [[EMClient sharedClient] logout:NO]; if (!error) { NSLog(@"退出成功"); //切換到登錄界面 UIStoryboard *mainSB = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; HMLoginViewController *loginVC = [mainSB instantiateViewControllerWithIdentifier:@"HMLogin"]; [UIApplication sharedApplication].keyWindow.rootViewController = loginVC; } } /*! * 當前登陸帳號已經被從服務器端刪除時會收到該回調 */ - (void)userAccountDidRemoveFromServer { EMError *error = [[EMClient sharedClient] logout:NO]; if (!error) { NSLog(@"退出成功"); //切換到登錄界面 UIStoryboard *mainSB = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; HMLoginViewController *loginVC = [mainSB instantiateViewControllerWithIdentifier:@"HMLogin"]; [UIApplication sharedApplication].keyWindow.rootViewController = loginVC; } }
@ #warning 未來註冊和登陸, 應該調用服務器的接口, 這裏只是爲了方便測試, 使用的環信接口安全
- (IBAction)loginClick:(id)sender { EMError *error = [[EMClient sharedClient] loginWithUsername:self.usernameTF.text password:self.passwordTF.text]; if (!error) { NSLog(@"登陸成功"); //設置自動登陸 [[EMClient sharedClient].options setIsAutoLogin:YES]; } } /* 自動登陸在如下幾種狀況下會被取消: 用戶調用了 SDK 的登出動做; 用戶在別的設備上更改了密碼,致使此設備上自動登陸失敗; 用戶的帳號被從服務器端刪除; 用戶從另外一個設備登陸,把當前設備上登陸的用戶踢出。 因此,在您調用登陸方法前,應該先判斷是否設置了自動登陸,若是設置了,則不須要您再調用。 */ //判斷是否有自動登陸 BOOL isAutoLogin = [EMClient sharedClient].options.isAutoLogin; if (!isAutoLogin) { //跳轉登陸界面 UIStoryboard *mainSB = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; HMLoginViewController *loginVC = [mainSB instantiateViewControllerWithIdentifier:@"HMLogin"]; //若是是AppDelegate裏, 切換控制器應該使用self.window self.window.rootViewController = loginVC; }
@ #pragma mark - 自動重連服務器
/*! * SDK鏈接服務器的狀態變化時會接收到該回調 * * 有如下幾種狀況,會引發該方法的調用: * 1. 登陸成功後,手機沒法上網時,會調用該回調 * 2. 登陸成功後,網絡狀態變化時,會調用該回調 * * @param aConnectionState 當前狀態 */ - (void)connectionStateDidChange:(EMConnectionState)aConnectionState { NSLog(@"重連狀態: %zd", aConnectionState); //未來斷網了能夠彈出一些提示/或者UI發生一些改變, 讓用戶知道斷網了 }
注:環信不是好友也能夠聊天,不推薦使用環信的好友機制。若是你有本身的服務器或好友關係,請本身維護好友關係。網絡
未來開發中, 記得使用公司的接口實現app
#pragma mark - UITextFieldDelegate - (BOOL)textFieldShouldReturn:(UITextField *)textField { EMError *error = [[EMClient sharedClient].contactManager addContact:textField.text message:@"我想加您爲好友"]; if (!error) { NSLog(@"添加成功"); [self.navigationController popViewControllerAnimated:YES]; } return YES; } //註冊好友回調 [[EMClient sharedClient].contactManager addDelegate:self delegateQueue:nil];
#pragma mark - 好友添加 /*! * 用戶A發送加用戶B爲好友的申請,用戶B會收到這個回調 * * @param aUsername 用戶名 * @param aMessage 附屬信息 */ - (void)friendRequestDidReceiveFromUser:(NSString *)aUsername message:(NSString *)aMessage { //這裏暫時彈出一個提示框, 讓用戶選擇贊成, 或者拒絕 //贊成好友請求 EMError *error = [[EMClient sharedClient].contactManager acceptInvitationForUsername:aUsername]; if (!error) { NSLog(@"贊成請求"); } //拒絕好友請求 EMError *error = [[EMClient sharedClient].contactManager declineInvitationForUsername:aUsername]; if (!error) { NSLog(@"拒絕請求"); } } /*! @method @brief 用戶A發送加用戶B爲好友的申請,用戶B贊成後,用戶A會收到這個回調 */ - (void)friendRequestDidApproveByUser:(NSString *)aUsername { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"好友通知" message:[NSString stringWithFormat:@"%@已經成爲您的好友", aUsername] preferredStyle:UIAlertControllerStyleAlert]; [self.window.rootViewController presentViewController:alertController animated:YES completion:nil]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [alertController dismissViewControllerAnimated:YES completion:nil]; }); } /*! @method @brief 用戶A發送加用戶B爲好友的申請,用戶B拒絕後,用戶A會收到這個回調 */ - (void)friendRequestDidDeclineByUser:(NSString *)aUsername { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"好友通知" message:[NSString stringWithFormat:@"%@拒絕和您成爲好友", aUsername] preferredStyle:UIAlertControllerStyleAlert]; [self.window.rootViewController presentViewController:alertController animated:YES completion:nil]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [alertController dismissViewControllerAnimated:YES completion:nil]; }); }
//暫時模擬實時刷新 - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; //已進入就刷新好友數據 [self reloadContactData]; } #pragma mark - 從服務器獲取全部的好友 - (void)reloadContactData { EMError *error = nil; self.contactArray = [[EMClient sharedClient].contactManager getContactsFromServerWithError:&error]; [self.tableView reloadData]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *cellID = @"contactCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath]; cell.textLabel.text = self.contactArray[indexPath.row]; return cell; }
//發送通知, 讓通信錄界面刷新運維
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { // 刪除好友 //deleteContact: 要刪除的用戶 //isDeleteConversation: 是否刪除對應的會話和消息 刪除緩存 EMError *error = [[EMClient sharedClient].contactManager deleteContact:self.contactArray[indexPath.row] isDeleteConversation:YES]; if (!error) { NSLog(@"刪除成功"); [self reloadContactData]; } } }
@ #pragma mark 發送消息post
- (BOOL)textFieldShouldReturn:(UITextField *)textField { //1. 構造文字消息 EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithText:textField.text]; //來自於誰--我 NSString *from = [[EMClient sharedClient] currentUsername]; //生成Message //ConversationID: 會話ID, 用於表示和某人的聊天記錄, 不傳, 默認就會使用to的內容 EMMessage *message = [[EMMessage alloc] initWithConversationID:nil from:from to:self.userName body:body ext:nil]; message.chatType = EMChatTypeChat;// 設置爲單聊消息 //message.chatType = EMChatTypeGroupChat;// 設置爲羣聊消息 //message.chatType = EMChatTypeChatRoom;// 設置爲聊天室消息
//2. 發送消息 [[EMClient sharedClient].chatManager sendMessage:message progress:nil completion:^(EMMessage *message, EMError *error) { if (error) { NSLog(@"error: %@", error); } else { NSLog(@"發送成功"); } }]; //3.清空文本框 textField.text = @""; [textField resignFirstResponder]; return YES; }
- (void)viewDidLoad { [super viewDidLoad]; self.tableView.estimatedRowHeight = 90; self.tableView.rowHeight = UITableViewAutomaticDimension; self.messageArrayM = [NSMutableArray array]; [self reloadMessageData:YES]; } - (void)reloadMessageData:(BOOL)isFirst { /* 會話:操做聊天消息 EMMessage 的容器,在 SDK 中對應的類型是 EMConversation。 新建/獲取一個會話 根據 conversationId 建立一個 conversation。 getConversation:建立與8001的會話 type:會話類型 createIfNotExist:不存在是否建立 EMConversationTypeChat 單聊會話 EMConversationTypeGroupChat 羣聊會話 EMConversationTypeChatRoom 聊天室會話 */ EMConversation *conversation = [[EMClient sharedClient].chatManager getConversation:self.userName type:EMConversationTypeChat createIfNotExist:YES]; /*! * 從數據庫獲取指定數量的消息,取到的消息按時間排序,而且不包含參考的消息,若是參考消息的ID爲空,則從最新消息取 * * @param aMessageId 參考消息的ID * @param count 獲取的條數 * @param aDirection 消息搜索方向 * @param aCompletionBlock 完成的回調 */ [conversation loadMessagesStartFromId:nil count:isFirst ? 20 : 1 searchDirection:EMMessageSearchDirectionUp completion:^(NSArray *aMessages, EMError *aError) { if (aError) { NSLog(@"數據庫查詢消息有問題: %@", aError); } else { [self.messageArrayM addObjectsFromArray:aMessages]; NSLog(@"self.messageArray: %@", self.messageArrayM); [self.tableView reloadData]; } }]; }
//2. 發送消息 [[EMClient sharedClient].chatManager sendMessage:message progress:nil completion:^(EMMessage *message, EMError *error) { if (error) { NSLog(@"error: %@", error); } else { NSLog(@"發送成功"); //從新讀取並刷新 [self reloadMessageData:NO]; } }]; - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *leftID = @"leftCell"; static NSString *rightID = @"rightCell"; EMMessage *message = self.messageArrayM[indexPath.row]; UITableViewCell *cell; //消息的方向能夠去分, 是誰發的 if (message.direction) { //接收的消息 cell = [tableView dequeueReusableCellWithIdentifier:leftID forIndexPath:indexPath]; } else { //發送的消息 cell = [tableView dequeueReusableCellWithIdentifier:rightID forIndexPath:indexPath]; } EMMessageBody *msgBody = message.body; switch (msgBody.type) { case EMMessageBodyTypeText: { // 收到的文字消息 EMTextMessageBody *textBody = (EMTextMessageBody *)msgBody; UILabel *label = [cell viewWithTag:1002]; label.text = textBody.text; } break; default: break; } return cell; }
//註冊消息回調 [[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil]; #pragma mark - 接收普通消息 /*! @method @brief 接收到一條及以上非cmd消息 */ - (void)messagesDidReceive:(NSArray *)aMessages { //須要發送通知, 告訴正在聊天的控制器進行數據刷新 //通知時, 能夠傳遞消息的數量 --> 7 [[NSNotificationCenter defaultCenter] postNotificationName:@"HMMessagesDidReceiveNotification" object:nil userInfo:@{@"messageCount": @(aMessages.count)}]; NSLog(@"aMessages: %@", aMessages); } - (void)viewDidLoad { [super viewDidLoad]; self.tableView.estimatedRowHeight = 90; self.tableView.rowHeight = UITableViewAutomaticDimension; self.messageArrayM = [NSMutableArray array]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(messagesDidReceiveNotification:) name:@"HMMessagesDidReceiveNotification" object:nil]; //首次刷新抓20條數據 [self reloadMessageDataWithCount:20]; } //這裏還有一個通知的方法, 通知的方法中須要根據消息的個數, 從新讀取數據reloadMessageDataWithCount:7 - (void)messagesDidReceiveNotification:(NSNotification *)notification{ int count = [notification.userInfo[@"messageCount"] intValue]; [self reloadMessageDataWithCount:count];