IM——技術方案

一. 即時通信技術方案

1. 第三方SDK: 環信, 融雲, 網易雲信, 騰訊

中小型公司/初創型: 建議使用第三方. 
好處: 快, 符合快速開發的需求, 本身和後臺人員不須要作什麼操做
缺點: 你的數據會通過人家的服務器, 可能會不安全

2. 使用XMPP: XMPPFramework, 之前作即時通信, 基本都在使用XMPP

好處: 源碼開源, 能夠自行拓展功能, 網上也有不少案例
缺點: 本身和後臺人員須要作不少的操做(後臺須要額外提供一些接口), 聊天服務器的穩定性可能不夠好(看公司本身的運維人員技術是否夠好), XML會耗流量

3. 自定義協議: 大型公司/專業即時通信公司

好處: 接口能夠自定義, 可使用低流量的傳輸格式
缺點: 須要必定的自定義協議的經驗, 包括對數據處理的經驗, 對技術能力有必定的要求

二. 環信集成

1. 環信SDK介紹

環信V3版本使用了自定義協議
環信以前的版本是基於XMPP封裝的數據庫

APP 服務器與環信服務器的集成

環信只是即時通信的消息通道。環信自己不提供用戶體系,環信既不保存任何 APP 業務數據,也不保存任何 APP 的用戶信息。好比說,你的 APP 是一個婚戀交友 APP,那麼你的 APP 用戶的頭像、暱稱、身高、體重、三圍、電話號碼等信息是保存在你本身的 APP 業務服務器上,這些信息不須要告訴環信,環信也不想知道。api

環信這樣設計的目的有2個:

  1. 本身公司必定會有後臺服務器, 能夠存儲用戶的數據
  2. 用戶數據很是核心, 不該該保存, 也不太敢存到其餘地方

環信服務器提供了 REST API 服務用來集成用戶和好友體系:

  1. 環信提供API, 快速將公司本身的帳號體系, 轉換成環信帳號體系
  2. 環信也提供了好友體系(正常開發中, 不要使用.咱們目前爲了方便, 可使用)
  3. 集成SDK
  4. 環信初始化&UI搭建
- (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];
}

4. 註冊&登陸&退出

註冊&登陸

@ #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;
    }
}

5. 自動登陸&自動重連

@ #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

1. 添加好友

#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];

2. 接受好友

#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];
    });
}

3. 好友列表

//暫時模擬實時刷新
- (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;
}

添加好友

//發送通知, 讓通信錄界面刷新運維

4. 刪除好友

- (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];
        }
    }
}

四. 單聊

1. 發送消息

@ #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;
}

2. 讀取消息列表

- (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;
}

3. 接收消息

//註冊消息回調
[[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];
相關文章
相關標籤/搜索