目录
    1. 概念
    2. 集成融云
    3. 会话列表页(自定义CELL)
       聊天页(自定义消息、CELL)
    4. 用户信息、小角标、消息类型
1. 概念
融云SDK的系统架构
IMKit
  封装了各种界面对象。
  用于快速集成。
IMLib
  封装了通信能力和 Conversation,Message 等各种对象,提供基本通信能力库。
  用于高度自定义UI。
Protocol
  融云的核心协议栈,使用融云自定义的私有二进制协议

融云SDK的系统架构
相关名词
通知
  提示用户的方式,消息、角标、弹窗
推送
  从服务器发送消息到前端的技术
广播
  向所有客户端发送一条消息
应用切换到后台2分钟后,将自动切断与服务器的连接,变为离线状态。
用户A向用户B发送消息:
  首先,应用会将消息传递给融云服务器,如果用户B在线则将消息传给用户B,如果用户B不在线,则将推送消息给用户B绑定的设备。
2. 集成融云
第一步:
    苹果开发者中心创建Identify,打开推送功能,创建测试、发布推送证书,下载双击安装并导出为p12格式。
    融云开发者官网创建应用,上传2个p12证书,拿到Key和Sect
第二步:
    项目|CapBilities|打开通知功能,并勾选Background Mode下的Remote notifications
    cocoaPods 引入
      # 聊天
      pod 'RongCloudIM/IMLib'
      pod 'RongCloudIM/IMKit'
第三步:
  AppDelegate配置
    #import <RongIMKit/RongIMKit.h>
    didFinish方法中+注册融云、注册APNs(注册成功后将token传给融云)
        // 注册融云
        [[RCIM sharedRCIM] initWithAppKey:RongAppKeyStore];
        // 远程推送的内容(点击通知栏的通知,且当App被杀死时)
        NSDictionary *remoteNotificationUserInfo = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];    
    实现前台、后台收到消息后的回调(处于前台、处于后台点击通知会分别调用相应的方法,程序销毁点击通知会将通知消息存放在didFinishLaunchingWithOptions的launchOptions参数中)
登录融云
   // 链接服务器---昵称/头像/id
    YTAccount *acount=[YTAccountTool account];
    NSDictionary *paramDic=@{@"userId":acount.userId,@"portraitUri":acount.iconurl,@"name":acount.name};
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    // 下面是按融云的提示写的
    NSString * timestamp = [[NSString alloc] initWithFormat:@"%ld",(NSInteger)[NSDate timeIntervalSinceReferenceDate]];
    NSString * nonce = [NSString stringWithFormat:@"%d",arc4random()];
    NSString * appkey = RongAppKeyStore;
    NSString *SignatureWillMD5 = [NSString stringWithFormat:@"%@%@%@",appkey,nonce,timestamp];//这个要加密
    //    NSString *Signature = [self MD5String:@"aaaaa"];
    // 以下拼接请求内容
    [manager.requestSerializer setValue:appkey forHTTPHeaderField:@"App-Key"];
    [manager.requestSerializer setValue:nonce forHTTPHeaderField:@"Nonce"];
    [manager.requestSerializer setValue:timestamp forHTTPHeaderField:@"Timestamp"];
    [manager.requestSerializer setValue:SignatureWillMD5 forHTTPHeaderField:@"Signature"];
    [manager.requestSerializer setValue:RongAppSecStore forHTTPHeaderField:@"appSecret"];
    [manager.requestSerializer setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    
    // 开始请求
    [manager POST:kCustomerRCTOKEN parameters:paramDic progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        
        NSString *token=responseObject[@"token"];
        // success/error/tokenIncorrent三者只会调用一个且仅调用一次
        [[RCIM sharedRCIM]connectWithToken:token success:^(NSString *userId) {
            
            // success
            NSLog(@"登录融云成功,token:%@",token);
/*            
            // 设置当前登陆用户
            RCUserInfo *userInfo=[RCUserInfo new];
            [userInfo setName:@""];
            [userInfo setUserId:@""];
            [userInfo setPortraitUri:@""];
            [[RCIM sharedRCIM]setCurrentUserInfo:userInfo];
            [RCIM sharedRCIM].receiveMessageDelegate=self;
*/
        } error:^(RCConnectErrorCode status) {
            // 出错了
            NSLog(@"登录错误码%ld",(long)status);
            
            // 除了以下错误,融云都会自动重练
            // AppKey出错                            :RC_CONN_ID_REJECT = 31002
            // Token无效(前后台不一致,token过期)      :RC_CONN_TOKEN_INCORRECT = 31004
            // BundleID 不正确                       :RC_CONN_PACKAGE_NAME_INVALID = 31007
            // App Key 被封禁或已删除                  :RC_CONN_APP_BLOCKED_OR_DELETED = 31008
            // 用户被封禁                             :RC_CONN_USER_BLOCKED = 31009
            // 当前用户在其他设备上登录,此设备被踢下线     :RC_DISCONN_KICK = 31010
            // SDK 没有初始化                         :RC_CLIENT_NOT_INIT = 33001
            // 接口调用时传入的参数错误                  :RC_INVALID_PARAMETER = 33003,RC_INVALID_ARGUMENT = -1000
            
        } tokenIncorrect:^{
            
            // 获取token失败,再次获取一次
            NSLog(@"token error");
            //
            [[RCIM sharedRCIM]connectWithToken:token success:^(NSString *userId) {
                
                //
                NSLog(@"登录融云成功,token:%@",token);
                [RCIM sharedRCIM].receiveMessageDelegate=self;
            } error:^(RCConnectErrorCode status) {
                
                //
                NSLog(@"登录错误码%ld",(long)status);
            } tokenIncorrect:^{
                // 获取token失败,不用再次调用(避免循环调用)
                NSLog(@"token error");
            }];
        }];
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"获取融云TOKEN失败%@",error);
    }];
断开融云
一般不需要手动断开,因为:SDK在前后台切换或者网络出现异常都会自动断开或重连。
方式一:断开连接之后是否接收远程推送
    [[RCIM sharedRCIM]disconnect:true];
方式二:断开连接之后仍接收远程推送
    [[RCIM sharedRCIM]disconnect];
方式三:断开连接之后不接收远程推送
    [[RCIM sharedRCIM]logout];
会话列表页
继承RCConversationListViewController (类似tableViewController)
聊天内容页
继承RCConversationViewController (类似collectionViewController)
3. IMKit
会话列表页自定义UI样式:
    1、覆写willDisplayConversationTableCell方法(不能高度自定制)
    2、直接自定义CELL    (允许高度自定制)
3.1 会话列表页(自定义CELL)
1.会话列表页 YTConversationListController
YTConversationListController.h
#import <RongIMKit/RongIMKit.h>
@interface YTConversationListController : RCConversationListViewController
@end
YTConversationListController.m
#import "YTConversationListController.h"
#import "YTMessageViewController.h"
#import "YTRCListCell.h"
@interface YTConversationListController ()
@end
@implementation YTConversationListController
// 更新未读消息角标
-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];    
    [self.conversationListTableView reloadData];
}
- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupUI];
}
// UI
-(void)setupUI{
    self.navigationItem.title=@"消息";
    
    // 设置需要显示的会话类型
    [self setDisplayConversationTypes:@[@(ConversationType_PRIVATE)]];
            ConversationType_PRIVATE         单聊
            ConversationType_DISCUSSION      讨论组
            ConversationType_CHATROOM        聊天室
            ConversationType_GROUP           群组
            ConversationType_SYSTEM          系统会话(只能由服务器发起)
            ConversationType_APPSERVICE      应用内公众服务会话
            ConversationType_PUBLICSERVICE   跨应用公众服务会话
            ConversationType_PUSHSERVICE     推送服务会话
            ConversationType_CUSTOMERSERVICE 客服
      // 设置需要显示的聚合会话类型
      [self setCollectionConversationType:@[@(ConversationType_DISCUSSION),
                                            @(ConversationType_GROUP)]];
    // 是否显示 无网络时的提示(默认:true)
    [self setIsShowNetworkIndicatorView:false];
    // �没有消息时显示的bgView
    [self setEmptyConversationView:bgView];
    // cell bgColor
    [self setCellBackgroundColor:[UIColor blueColor]];
    // 置顶Cell bgColor
    [self setTopCellBackgroundColor:[UIColor redColor]];
    // conversationListTableView继承自UITableView    
    // 设置 头、尾视图
    [self.conversationListTableView setTableHeaderView:[UIView new]];
    [self.conversationListTableView setTableFooterView:[UIView new]];
    // 分割线
    [self.conversationListTableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
    // bgColor
    [self.conversationListTableView setBackgroundColor:[UIColor whiteColor]];
    // 内间距
    [self.conversationListTableView setContentInset:UIEdgeInsetsMake(10, 0, 0, 0)];
    // 自定义CELL
    [self.conversationListTableView registerClass:[YTRCListCell class] forCellReuseIdentifier:NSStringFromClass([YTRCListCell class])];
/*
    // 头像style (默认;矩形,圆形)
    [[RCIM sharedRCIM]setGlobalMessageAvatarStyle:RC_USER_AVATAR_CYCLE];
    // 头像size(默认:46*46,必须>36*36)
    [[RCIM sharedRCIM]setGlobalMessagePortraitSize:CGSizeMake(46, 46)];
    // 个人信息,自定义后不再有效。没自定义CELL时可使用,并实现getUserInfoWithUserId代理方法(详见聊天页)
    [[RCIM sharedRCIM]setUserInfoDataSource:self];
*/
    // 推送
    [self setupPush];
}
// 如果关闭推送,弹框去设置推送
-(void)setupPush{
    //
    if ([[UIDevice currentDevice].systemVersion floatValue]>=8.0f) {
        
        UIUserNotificationSettings *setting = [[UIApplication sharedApplication] currentUserNotificationSettings];
        if (UIUserNotificationTypeNone == setting.types) {
            
            //
            NSLog(@"推送关闭 8.0");
            YTRecommendView *recV=[YTRecommendView ViewWithTitle:@"您关闭了系统通知" withContent:@"开启系统通知,以免错过重要消息" withImgName:@"orderG" withButtonTitle:@"去开启"];
            recV.goBlock=^{
                
                // 跳手机系统的 通知设置
                [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
            };
            
        }else{
            NSLog(@"推送打开 8.0");
        }
    }else{
        //
        UIRemoteNotificationType type = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
        if(UIRemoteNotificationTypeNone == type){
            NSLog(@"推送关闭");
            YTRecommendView *recV=[YTRecommendView ViewWithTitle:@"您关闭了系统通知" withContent:@"开启系统通知,以免错过重要消息" withImgName:@"orderG" withButtonTitle:@"去开启"];
            recV.goBlock=^{
                
                // 跳手机系统的 通知设置
                [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
            };
            
        }else{
            NSLog(@"推送打开");
        }
    }
}
#pragma mark 自定义CELL 以下方法按需求去实现
// dataSource (修改数据源来修改UI)(在上方新增自定义cell)
// 要更换为RC_CONVERSATION_MODEL_TYPE_CUSTOMIZATION否则不调用覆写方法)
-(NSMutableArray *)willReloadTableData:(NSMutableArray *)dataSource {
    RCConversationModel *model=dataSource[0];
    [model setConversationModelType:RC_CONVERSATION_MODEL_TYPE_CUSTOMIZATION];
    // 修改model
    // ...
    return dataSource;
}
// height
- (CGFloat)rcConversationListTableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 95;
}
// 每行的编辑格式
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
    if(indexPath.row==0){
        return UITableViewCellEditingStyleNone;
    }else{
        return UITableViewCellEditingStyleDelete;
    }
}
// 编辑后调用
-(void)rcConversationListTableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
    // 服务器删除
    RCConversationModel *model = self.conversationListDataSource[indexPath.row];
    [[RCIMClient sharedRCIMClient] removeConversation:ConversationType_PRIVATE
                                             targetId:model.targetId];
    
    // UI本地删除
    [self.conversationListDataSource removeObjectAtIndex:indexPath.row];
    [self.conversationListTableView reloadData];
}
// cell
- (RCConversationBaseCell *)rcConversationListTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    // model-dataSource
    RCConversationModel *model = self.conversationListDataSource[indexPath.row];
    
    // 请求个人信息---自己的服务器
    __weak YTConversationListController *weakSelf = self;
    if(!model.extend && model.lastestMessage){    
        //
        [LHAFNetWork POST:YTBaseUrl(kCustomerMessage) params:@{@"userId":model.targetId} success:^(NSURLSessionDataTask *task, id responseObject) {
            
            if(SUCCESS){
                
                //
                NSDictionary *dic=(NSDictionary *)responseObject[@"data"];
                if([dic isEqual:[NSNull null]]){
                    //
                    return;
                }
                //
                YTAccount *acount=[YTAccountTool account];
                acount.userId=dic[@"id"];
                acount.name=dic[@"name"];
                acount.iconurl=YTImgBaseUrl(dic[@"photo"]);
                //
                RCUserInfo *userInfo = [[RCUserInfo alloc]init];
                userInfo.userId=acount.userId;
                userInfo.name=acount.name;
                userInfo.portraitUri=acount.iconurl;
                
                model.extend=userInfo;
                
                [weakSelf.conversationListTableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            }
        } fail:^(NSURLSessionDataTask *task, NSError *error) {
            NSLog(@"%@",error);
        }];
    }
    
    //
    YTRCListCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([YTRCListCell class])];
    [cell setConvM:model withIsFirst:indexPath.row==0?true:false withUserInfo:model.extend];
    
    return cell;
}
// will display
- (void)willDisplayConversationTableCell:(RCConversationBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath{
    // 选中不高亮(在cell中设置无效)
    [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
    // 获取Model会话类型,做其它处理
    RCConversationModel *model=cell.model;    
    if(model.conversationModelType != RC_CONVERSATION_MODEL_TYPE_CUSTOMIZATION){
        // 必须强制转换
        RCConversationCell *RCcell = (RCConversationCell *)cell;
        
        // 是否未读消息数(头像右上角,默认:true)
        [RCcell setIsShowNotificationNumber:true];
        
        RCcell.conversationTitle.font = [UIFont fontWithName:@"PingFangSC-Light" size:18];
        RCcell.messageContentLabel.font = [UIFont fontWithName:@"PingFangSC-Light" size:16];
        RCcell.messageCreatedTimeLabel.font = [UIFont fontWithName:@"PingFangSC-Light" size:14];
    }
}
// 点击cell跳-重写RCConversationListViewController的onSelectedTableRow事件
- (void)onSelectedTableRow:(RCConversationModelType)conversationModelType
         conversationModel:(RCConversationModel *)model
               atIndexPath:(NSIndexPath *)indexPath {
    
    // 跳转到 聊天内容页
    YTMessageViewController *conversationVC = [YTMessageViewController new];
    conversationVC.conversationType = model.conversationType;  // 数据源type:单聊...
    conversationVC.title = ((RCUserInfo *)model.extend).name;  // 标题:对方昵称
    conversationVC.targetId = model.targetId;                  // 对方ID    
    conversationVC.isNoHave=true;
    [self.navigationController pushViewController:conversationVC animated:YES];
}
// onRCIMReceiveMessage。收到消息---更新未读角标
-(void)onRCIMReceiveMessage:(RCMessage *)message left:(int)left{
    
    //
    [self.conversationListTableView reloadData];
}
@end
- 自定义CELL页 YTRCListCell
YTRCListCell.h
#import <RongIMKit/RongIMKit.h>
@class RCConversationModel;
@interface YTRCListCell : RCConversationBaseCell
-(void)setConvM:(RCConversationModel *)convM withIsFirst:(BOOL)isF withUserInfo:(RCUserInfo *)info;
@end
YTRCListCell.m
#import "YTRCListCell.h"
#import "YTConvertToTimeTool.h"
@interface YTRCListCell()
@property (nonatomic,strong) UILabel *nameL;
@property (nonatomic,strong) UILabel *contentL;
@property (nonatomic,strong) UILabel *timeL;
@property (nonatomic,strong) UIImageView *photoImgV;
@property (nonatomic,strong) UILabel *numL;
@end
@implementation YTRCListCell
//
-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
    //
    if(self=[super initWithStyle:style reuseIdentifier:reuseIdentifier]){
    
        //
        [self setSelectionStyle:UITableViewCellSelectionStyleNone];  // 此处设置无效,willdisplay中设置
        [self setupUI];
    }
    
    return self;
}
//
-(void)setupUI{
    
    // bg
    UIImageView *bgImgV=[UIImageView new];
    [bgImgV setImage:[UIImage imageNamed:@"messageList"]];
    [bgImgV setContentMode:UIViewContentModeTop];
    [self addSubview:bgImgV];
    [bgImgV autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsMake(0, 10, 10, 10)];
    
    
    // photo
    UIImageView *photoImgV=[UIImageView new];
    _photoImgV=photoImgV;
    [photoImgV.layer setMasksToBounds:true];
    [photoImgV.layer setCornerRadius:50/2];
    [bgImgV addSubview:photoImgV];
    [photoImgV autoAlignAxisToSuperviewAxis:ALAxisHorizontal];
    [photoImgV autoSetDimension:ALDimensionWidth toSize:50];
    [photoImgV autoSetDimension:ALDimensionHeight toSize:50];
    [photoImgV autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:5];
    
    
    // name
    UILabel *nameL=[UILabel new];
    _nameL=nameL;
    [nameL setFont:YTFONT_PF_S(15)];
    [nameL setTextColor:YTColorFromRGB(0x414141)];
    [bgImgV addSubview:nameL];
    [nameL autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:photoImgV];
    [nameL autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:photoImgV withOffset:5];
    [nameL autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:80];
    
    
    // content
    UILabel *contentL=[UILabel new];
    _contentL=contentL;
    [contentL setFont:YTFONT_PF(13)];
    [contentL setTextColor:YTColorFromRGB(0x414141)];
    [bgImgV addSubview:contentL];
    [contentL autoPinEdge:ALEdgeLeft toEdge:ALEdgeLeft ofView:nameL];
    [contentL autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:photoImgV];
    [contentL autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:36];
    
    
    // time
    UILabel *timeL=[UILabel new];
    _timeL=timeL;
    [timeL setFont:YTFONT_PF(15)];
    [timeL setTextColor:YTColorFromRGB(0x5d5d5d)];
    [bgImgV addSubview:timeL];
    [timeL autoAlignAxis:ALAxisHorizontal toSameAxisOfView:nameL];
    [timeL autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:15];
    
    //
    UILabel *numL=[UILabel new];
    _numL=numL;
    [numL setTextAlignment:NSTextAlignmentCenter];
    [numL setBackgroundColor:[UIColor redColor]];
    [numL.layer setCornerRadius:15/2];
    [numL.layer setMasksToBounds:true];
    [numL setFont:YTFONT_PF(12)];
    [numL setTextColor:YTColorFromRGB(0xffffff)];
    [bgImgV addSubview:numL];
    [numL autoPinEdge:ALEdgeRight toEdge:ALEdgeRight ofView:timeL];
    [numL autoSetDimension:ALDimensionWidth toSize:15];
    [numL autoSetDimension:ALDimensionHeight toSize:15];
    [numL autoAlignAxis:ALAxisHorizontal toSameAxisOfView:contentL];
}
-(void)setConvM:(RCConversationModel *)convM withIsFirst:(BOOL)isF withUserInfo:(RCUserInfo *)info{
    //
    [_timeL setText:[YTConvertToTimeTool ConvertChatMessageTime:(convM.receivedTime>convM.sentTime?convM.receivedTime:convM.sentTime)/1000]];
    [_numL setText:[NSString stringWithFormat:@"%ld",convM.unreadMessageCount]];
    if(convM.unreadMessageCount==0){
        [_numL setHidden:true];
    }else{
        [_numL setHidden:false];
    }
    
    
    if(isF){
        //
        NSString *titleStr=@"YOTO   官方   ";
        NSMutableAttributedString *muT=[[NSMutableAttributedString alloc]initWithString:titleStr attributes:@{NSForegroundColorAttributeName:YTColorFromRGB(0x414141),NSFontAttributeName:YTFONT_PF_S(15)}];
        [muT addAttributes:@{NSFontAttributeName:YTFONT_PF_S(12),NSForegroundColorAttributeName:[UIColor whiteColor],NSBackgroundColorAttributeName:YTMainColor} range:[titleStr rangeOfString:@" 官方 "]];
        [_nameL setAttributedText:muT];
        
        //
        [_photoImgV setImage:[UIImage imageNamed:@"official"]];
        
        if(convM.lastestMessage){
            [_contentL setText:((RCTextMessage *)convM.lastestMessage).content];
        }
    }else if(info){
        [_photoImgV sd_setImageWithURL:[NSURL URLWithString:info.portraitUri] placeholderImage:[UIImage imageNamed:@"userList"]];
        [_nameL setText:info.name];
        [_contentL setText:((RCTextMessage *)convM.lastestMessage).content];
    }
}
@end
3.2 聊天页
- 聊天内容页 YTMessageViewController
YTMessageViewController.h
#import <RongIMKit/RongIMKit.h>
@interface YTMessageViewController : RCConversationViewController
@end
YTMessageViewController.m
@interface YTMessageViewController ()<RCIMUserInfoDataSource,YTCollectionHeadReusableViewProtocol,YTDZHeadCollectionReusableViewProtocol>
@property (nonatomic, strong) RCUserInfo *userInfo2;
@end
@implementation YTMessageViewController
-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    [IQKeyboardManager sharedManager].enable=false;
}
-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    [IQKeyboardManager sharedManager].enable=true;
}
//
- (void)viewDidLoad {
    [super viewDidLoad];
/*
   conversationMessageCollectionView是继承自UICollectionView    
    //
    cell 为 RCMessageBaseCell,Model 为 RCMessageModel
    // 会话类型
    RCConversationType type=self.conversationType;
    // targetId
    NSString *targetId=self.targetId;
*/
    // bgColor
    [self.conversationMessageCollectionView setBackgroundColor:[UIColor whiteColor]];
    // 头视图
    if(_isCustome){
        [self.conversationMessageCollectionView registerClass:[YTDZHeadCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"YTDZHeadCollectionReusableView"];
    }else{
        [self.conversationMessageCollectionView registerClass:[YTCollectionHeadReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"YTCollectionHeadReusableView"];
    }
    // (用来设置 管家是否与用户沟通)
    if (!_isNoHave) {
        [self message];
    }
    // 如果没有历史消息,发送初始消息
    if(self.conversationDataRepository.count==0 && ![self.targetId isEqualToString:@"111111111111111"]){
        //
        [self sendInitMessage];
    }
    // 个人信息
    [[RCIM sharedRCIM]setUserInfoDataSource:self];
    
    // iconStyle 无效
//    [[RCIM sharedRCIM]setGlobalConversationAvatarStyle:RC_USER_AVATAR_CYCLE];
}
// cell样式
-(void)willDisplayMessageCell:(RCMessageBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath{
    //
    if([cell isMemberOfClass:[RCTextMessageCell class]]){
        // 内容
        RCTextMessageCell *textC=(RCTextMessageCell *)cell;
        UILabel *textL=(UILabel *)textC.textLabel;
        [textL setTextColor:YTColorFromRGB(0x414141)];
        [textL setFont:YTFONT_PF(15)];
        // 头像切圆
        UIImageView *portraitImageView = (UIImageView *)textC.portraitImageView;
        portraitImageView.layer.cornerRadius = CGRectGetHeight(portraitImageView.frame)/2;
        //
        CGRect frame=portraitImageView.frame;
        frame.origin.y=CGRectGetMaxY(textC.bubbleBackgroundView.frame)-CGRectGetHeight(portraitImageView.frame)+6;
        portraitImageView.frame=frame;
    }else if([cell isMemberOfClass:[RCRichContentMessageCell class]]){
        //
        RCRichContentMessageCell *rCell=(RCRichContentMessageCell *)cell;
        // 头像切圆
        UIImageView *portraitImageView = (UIImageView *)rCell.portraitImageView;
        portraitImageView.layer.cornerRadius = CGRectGetHeight(portraitImageView.frame)/2;
        //
        CGRect frame=portraitImageView.frame;
        frame.origin.y=CGRectGetMaxY(rCell.bubbleBackgroundView.frame)-CGRectGetHeight(portraitImageView.frame)+6;
        portraitImageView.frame=frame;
    }else if([cell isMemberOfClass:[RCVoiceMessageCell class]]){
    
        RCVoiceMessageCell *rCell=(RCVoiceMessageCell *)cell;
        
        // 头像切圆
        UIImageView *portraitImageView = (UIImageView *)rCell.portraitImageView;
        portraitImageView.layer.cornerRadius = CGRectGetHeight(portraitImageView.frame)/2;
        
        //
        CGRect frame=portraitImageView.frame;
        frame.origin.y=CGRectGetMaxY(rCell.bubbleBackgroundView.frame)-CGRectGetHeight(portraitImageView.frame)+6;
        portraitImageView.frame=frame;
    }else if([cell isMemberOfClass:[RCImageMessageCell class]]){
    
        RCImageMessageCell *rCell=(RCImageMessageCell *)cell;
        
        // 头像切圆
        UIImageView *portraitImageView = (UIImageView *)rCell.portraitImageView;
        portraitImageView.layer.cornerRadius = CGRectGetHeight(portraitImageView.frame)/2;
        
        //
        CGRect frame=portraitImageView.frame;
        frame.origin.y=CGRectGetMaxY(rCell.pictureView.frame)-CGRectGetHeight(portraitImageView.frame)+6;
        portraitImageView.frame=frame;
    }else if([cell isMemberOfClass:[RCLocationMessageCell class]]){
        RCLocationMessageCell *rCell=(RCLocationMessageCell *)cell;
        
        // 头像切圆
        UIImageView *portraitImageView = (UIImageView *)rCell.portraitImageView;
        portraitImageView.layer.cornerRadius = CGRectGetHeight(portraitImageView.frame)/2;
        
        //
        CGRect frame=portraitImageView.frame;
        frame.origin.y=CGRectGetMaxY(rCell.pictureView.frame)-CGRectGetHeight(portraitImageView.frame)+6;
        portraitImageView.frame=frame;
        
    }
}
// 点击链接CELL时跳转
-(void)didTapUrlInMessageCell:(NSString *)url model:(RCMessageModel *)model{
    /* 管家端
    // id-是否定制
    NSLog(@"%@",url);
    NSArray *arr=[url componentsSeparatedByString:@"-"];
    NSString *recId=arr[0];
    BOOL isDZ=[arr[1] boolValue];
    // 跳
    */
    //
        // 客户端跳
        YTRouteDetailViewController *routeC=[YTRouteDetailViewController new];
    if(_routeModel){
    
        
        routeC.themeId=_routeModel.themeId;
    }else{
        NSArray *arr=[url componentsSeparatedByString:@"-"];
        NSString *recId=arr[0];
        routeC.themeId=recId;
    }
        [self.navigationController pushViewController:routeC animated:true];
}
// headSize
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section{
    
    //
        //
        if(_isNoHave){
            return CGSizeZero;
        }else{
            
            if(_isCustome){
                return CGSizeMake(KScreenWidth, 229);
            }else{
                return CGSizeMake(KScreenWidth, 209);
            }
        }
}
// head foot
-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{
    
    //
    if(kind==UICollectionElementKindSectionHeader){
        
        //
        if(_isCustome){
            YTDZHeadCollectionReusableView *view=[collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"YTDZHeadCollectionReusableView" forIndexPath:indexPath];
            view.themeM=_routeModel;
            view.dele=self;
            view.tapG = ^{
                YTRouteDetailViewController *routeC=[YTRouteDetailViewController new];
                [routeC setThemeId:_routeModel.themeId];
                [self.navigationController pushViewController:routeC animated:true];
            };
        
            return view;
        }else{
            YTCollectionHeadReusableView *view=[collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"YTCollectionHeadReusableView" forIndexPath:indexPath];
            [view setThemeM:_routeModel];
            view.dele=self;
            view.tapG = ^{
              
                YTRouteDetailViewController *routeC=[YTRouteDetailViewController new];
                [routeC setThemeId:_routeModel.themeId];
                [self.navigationController pushViewController:routeC animated:true];
            };
            
            return view;
        }
    }
    
    return [UICollectionReusableView new];
}
#pragma mark dele
// 发送链接
-(void)goRoute{
    //
    RCRichContentMessage *msg=[RCRichContentMessage messageWithTitle:@"" digest:@"行程链接" imageURL:@"" url:[NSString stringWithFormat:@"%@-%@",_routeModel?_routeModel.themeId:self.targetId,[NSNumber numberWithBool:_isCustome]] extra:@""];
    [[RCIM sharedRCIM]sendMessage:ConversationType_PRIVATE targetId:self.targetId content:msg pushContent:@"" pushData:@"" success:^(long messageId) {
        //
        NSLog(@"发送链接成功");
        [self.conversationMessageCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:self.conversationDataRepository.count-1 inSection:0] atScrollPosition:UICollectionViewScrollPositionBottom animated:true];
    } error:^(RCErrorCode nErrorCode, long messageId) {
        [YTHUD showError:@"发送链接失败,请重新发送"];
    }];
}
// moreHistory
-(void)moreHistory{
    //
    YTMoreHistoryMessageViewController *moreHistoryC=[YTMoreHistoryMessageViewController new];
    moreHistoryC.themeId=_routeModel.themeId;
    moreHistoryC.targetId=self.targetId;
    moreHistoryC.conversationType = self.conversationType;
    [self.navigationController pushViewController:moreHistoryC animated:true];
}
// 发送初始消息
-(void)sendInitMessage{
    
    RCTextMessage *msg = [RCTextMessage messageWithContent:@"你好,看见你发的出行线路,感觉还不错,我挺感兴趣的,现在有时间可以具体聊聊吗?"];
    [[RCIM sharedRCIM]sendMessage:ConversationType_PRIVATE targetId:self.targetId content:msg pushContent:@"" pushData:@"" success:^(long messageId) {
        //
        NSLog(@"发送初始消息成功");
    } error:^(RCErrorCode nErrorCode, long messageId) {
        [YTHUD showError:@"发送初始消息失败,请重新发送"];
    }];
}
// (用来设置 管家是否与用户沟通)
-(void)message{
    //
    [AFNetWorkTool POST:YTBaseUrl(kCustomerMessagepush) params:@{@"Id":self.targetId,@"isChoosed":@"3"} success:^(NSURLSessionDataTask *task, id responseObject) {
        if(SUCCESS){
            NSLog(@"发送成功");
        }
    } fail:^(NSURLSessionDataTask *task, NSError *error) {
        NSLog(@"%@",error);
    }];
}
#pragma mark 获取个人信息
- (void)getUserInfoWithUserId:(NSString *)userId completion:(void (^)(RCUserInfo *))completion{
    YTAccount *acount=[YTAccountTool account];
    if ([userId isEqualToString:[NSString stringWithFormat:@"%@",acount.userId]]) {
dispatch_async(dispatch_get_main_queue(), ^{
        RCUserInfo *user = [[RCUserInfo alloc]init];
        user.userId=[NSString stringWithFormat:@"%@",acount.userId];
        user.name=acount.name;
        user.portraitUri=acount.iconurl?[NSString stringWithFormat:@"%@%@",AccessPhoto,acount.iconurl]:@"";
        completion(user);
});
    }else{
   [AFNetWorkTool POST:YTBaseUrl(kCustomerMessage) params:@{@"userId": userId} success:^(NSURLSessionDataTask *task, id responseObject) {
        if(SUCCESS){
            NSDictionary *dic=(NSDictionary *)responseObject[@"data"];
            YTAccount *acount=[YTAccountTool account];
            acount.userId=dic[@"id"];
            acount.name=dic[@"name"];
            acount.iconurl=YTImgBaseUrl(dic[@"photo"]);
            //
dispatch_async(dispatch_get_main_queue(), ^{
            RCUserInfo * userInfo2 = [[RCUserInfo alloc]init];
            userInfo2.userId=acount.userId;
            userInfo2.name=acount.name;
            userInfo2.portraitUri=YTImgBaseUrl(acount.iconurl);
            completion(userInfo2);
});
        }
    } fail:^(NSURLSessionDataTask *task, NSError *error) {
        NSLog(@"%@",error);
    }];     
    }
}
@end
- 点击、长按相关 覆写方法
// --------- 点击、长按事件 ---------
// 点击CELL的 消息调用
-(void)didTapMessageCell:(RCMessageModel *)model{}
// 点击CELL的 URL调用
-(void)didTapUrlInMessageCell:(NSString *)url model:(RCMessageModel *)model{}
// 点击CELL的 phone调用
-(void)didTapPhoneNumberInMessageCell:(NSString *)phoneNumber model:(RCMessageModel *)model{}
// 点击CELL的 头像调用
-(void)didTapCellPortrait:(NSString *)userId{}
// 长按CELL的头像调用
-(void)didLongPressCellPortrait:(NSString *)userId{}
// 长按CELL的消息调用
-(void)didLongPressCellPortrait:(NSString *)userId{}
- 消息相关
// --------- 发送/删除/插入 消息(可覆写,要调super) --------- 
    // 发送消息
    [self sendMessage:[RCMessageContent new] pushContent:@"接收方离线时显示的远程推送内容"];
    // 重新发送消息
    [self resendMessage:[RCMessageContent new]];
    // 插入消息(临时,退出再进就没了)
    [self appendAndDisplayMessage:[RCMessage new]];
    // 发送多媒体信息(除文本消息外都是)
    [self sendMediaMessage:[RCMessageContent new] pushContent:@"" appUpload:<#(BOOL)#>];
    // 删除消息
    [self deleteMessage:[RCMessageModel new]];
举例:插入
    // 是否保存到本地数据库,如果不保存,则下次进入聊天界面将不再显示。
    BOOL saveToDB = NO;
    
    RCMessage *insertMessage;
    RCInformationNotificationMessage *warningMessage = [RCInformationNotificationMessage notificationWithMessage:@"提醒消息" extra:nil];
    if (saveToDB) {
        // 如果保存到本地数据库,需要调用insertMessage生成消息实体并插入数据库。
        insertMessage = [[RCIMClient sharedRCIMClient] insertOutgoingMessage:self.conversationType
                                                                    targetId:self.targetId
                                                                  sentStatus:SentStatus_SENT
                                                                     content:warningMessage];
    } else {
        // 如果不保存到本地数据库,需要初始化消息实体并将messageId要设置为-1。
        insertMessage =[[RCMessage alloc] initWithType:self.conversationType
                                              targetId:self.targetId
                                             direction:MessageDirection_SEND
                                             messageId:-1
                                               content:warningMessage];
    }
    // 在当前聊天界面插入该消息
    [self appendAndDisplayMessage:insertMessage];
// --------- 消息发送前后、显示CELL前的回调 (覆写做额外处理) ---------
// 发送消息前调用
-(RCMessageContent *)willSendMessage:(RCMessageContent *)messageContent{    return messageContent; }
// 发送消息后调用 
-(void)didSendMessage:(NSInteger)status content:(RCMessageContent *)messageContent{}
// 插入消息前调用 
-(RCMessage *)willAppendAndDisplayMessage:(RCMessage *)message{    return message; }
// 显示cell前调用
-(void)willDisplayMessageCell:(RCMessageBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath{}
// --------- 未读消息数UI相关 --------- 
    // 导航栏左侧按钮 未读消息数(置为nil,不再显示)
    // 需要统计未读数的会话类型数组
    self.displayConversationTypeArray=nil;
    // 当未读消息超过一个屏幕时 在右上角显示未读消息数 默认:false不显示
    [self setEnableUnreadMessageIcon:true];
    // 当未处在最下方,收到消息时 右下角是否显示按钮-滚动到最下方 默认:false不显示
    [self setEnableNewComingMessageIcon:true];
- 输入工具栏
    // 输入框的默认输入模式
    [self setDefaultInputType:RCChatSessionInputBarInputText];
    /*
    RCChatSessionInputBarInputText,    文本(默认)
    RCChatSessionInputBarInputVoice,  语音
    RCChatSessionInputBarInputExtention  扩展
    */
    // 输入框的UI样式
    // 按照会话类型来设置的,不要随意设置
    [self.chatSessionInputBarControl setInputBarType:RCChatSessionInputBarControlDefaultType style:RC_CHAT_INPUT_BAR_STYLE_CONTAINER];
    /*
     type
        RCChatSessionInputBarControlDefaultType      非公众服务(默认)
        RCChatSessionInputBarControlPubType          公众服务
        RCChatSessionInputBarControlCSRobotType      客服机器人会话
        RCChatSessionInputBarControlNoAvailableType  客服机器人会话
     style
        RC_CHAT_INPUT_BAR_STYLE_SWITCH_CONTAINER_EXTENTION  语音/文本切换功能+内容输入功能+扩展功能
        RC_CHAT_INPUT_BAR_STYLE_EXTENTION_CONTAINER_SWITCH  扩展功能+内容输入功能+语音/文本切换功能
        RC_CHAT_INPUT_BAR_STYLE_CONTAINER_SWITCH_EXTENTION  内容输入功能+语音/文本切换功能+扩展功能
        RC_CHAT_INPUT_BAR_STYLE_CONTAINER_EXTENTION_SWITCH  内容输入功能+扩展功能+语音/文本切换功能
        RC_CHAT_INPUT_BAR_STYLE_SWITCH_CONTAINER    语音/文本切换功能+内容输入功能
        RC_CHAT_INPUT_BAR_STYLE_CONTAINER_SWITCH    内容输入功能+语音/文本切换功能
        RC_CHAT_INPUT_BAR_STYLE_EXTENTION_CONTAINER 扩展功能+内容输入功能
        RC_CHAT_INPUT_BAR_STYLE_CONTAINER_EXTENTION 内容输入功能+扩展功能
        RC_CHAT_INPUT_BAR_STYLE_CONTAINER   内容输入功能
        */
    // 自定义工具栏
    [self setChatSessionInputBarControl:[RCChatSessionInputBarControl new]];
- Emoji表情区域
    Emoji表情,可修改plist文件
    // 自定义EmojiView
    [self.chatSessionInputBarControl setEmojiBoardView:[RCEmojiBoardView new]];
- 扩展区域
    // 扩展View是否隐藏
    [self.extensionView setHidden:true];
    // 添加item  (tag不要1001~,那是系统预留的)
    [self.chatSessionInputBarControl.pluginBoardView insertItemWithImage:[UIImage imageWithNamed:@""] title:@"" tag:10];
    [self.chatSessionInputBarControl.pluginBoardView insertItemWithImage:[UIImage imageWithNamed:@""] title:@"" atIndex:0 tag:10];
    // 更新item
    [self.chatSessionInputBarControl.pluginBoardView updateItemAtIndex:0 image:[UIImage imageWithNamed:@""] title:@""];
    [self.chatSessionInputBarControl.pluginBoardView updateItemWithTag:10 image:[UIImage imageWithNamed:@""] title:@""];
    // 移除item
    [self.chatSessionInputBarControl.pluginBoardView removeItemAtIndex:0];
    [self.chatSessionInputBarControl.pluginBoardView removeItemWithTag:10];
    [self.chatSessionInputBarControl.pluginBoardView removeAllItems];
// 覆写,点击某项后调用
- (void)pluginBoardView:(RCPluginBoardView *)pluginBoardView clickedItemWithTag:(NSInteger)tag {
    [super pluginBoardView:pluginBoardView clickedItemWithTag:tag];
    switch (tag) {
        case 2001:
            [self navigateToPay];
            break;
        case 2002:
            [self navigateToPic];
            break;
        case 2003:
            [self navigateToSend];
            break;
        default:
            break;
    }
}
3.3 聊天页(自定义消息和CELL)
第一步:
    自定义消息,并注册(registerMessageType)
第二步:
    自定义cell ,并注册(registerClass:forMessageClass:,则不再走 cellForItem、 sizeForItem)
第三步:
    发送自定义消息
第一步:
自定义消息,并注册(registerMessageType)
    // 创建自定义消息类
    继承:RCMessageContent (所有消息的基类),并实现其遵守的协议(必须实现,见下)
    // 注册自定义消息类(必须注册)
    [[RCIM sharedRCIM]registerMessageType:[RCMessageContent class]];
:RCMessageContent
    该类遵守了RCMessageCoding,RCMessagePersistentCompatible,RCMessageContentView3个协议,如下
// ---------- 消息内容的编解码协议 ---------- 
@protocol RCMessageCoding <NSObject>
@required
// 返回消息唯一标识(不要以RC:开头)
+(NSString *)getObjectName;
// 消息内容->json
- (NSData *)encode;
// json->消息内容
- (void)decodeWithData:(NSData *)data;
/*
主要有三个功能:
    提供消息唯一标识符
    消息发送时将消息中的所有信息编码为 JSON 数据传输
    消息接收时将 JSON 数据解码还原为消息对象
*/
@end
// ---------- 消息内容的存储协议 ----------
@protocol RCMessagePersistentCompatible <NSObject>
@required
// 返回消息的存储策略(在本地是否存储、是否计入未读消息数)
+(RCMessagePersistent)persistentFlag;
/*
用于确定消息内容的存储策略,指明此消息类型在本地是否存储、是否计入未读消息数,RCMessagePersistent有4种:
    MessagePersistent_NONE          在本地不存储,不计入未读数。
    MessagePersistent_ISPERSISTED   表示客户端收到消息后,要进行未读消息计数(未读消息数增加 1),所有内容型消息都应该设置此值。非内容类消息暂不支持消息计数。
    MessagePersistent_ISCOUNTED     表示客户端收到消息后,要进行存储,并在之后可以通过接口查询。
    MessagePersistent_STATUS        在本地不存储,不计入未读数,并且如果对方不在线,服务器会直接丢弃该消息,对方如果之后再上线也不会再收到此消息(聊天室类型除外,此类消息聊天室会视为普通消息)。
*/
@end
// ---------- 消息内容摘要的协议 ---------- 
@protocol RCMessageContentView
@optional
// 返回在会话列表、本地通知中显示的消息内容摘要(最新消息的摘要)
-(NSString *)conversationDigest;
@end
实例
#define RCDeliverSentMessageTypeIdentifier @"RCC:XDispatchMsg"
#import <RongIMLib/RongIMLib.h>
#import "ChatModel.h"
@interface RCDeliverSentMessage : RCMessageContent
@property (nonatomic, strong) ChatModel *chatModel;
@end
#import "RCDeliverSentMessage.h"
#import "MJExtension.h"
@implementation RCDeliverSentMessage
#pragma mark RCMessagePersistentCompatible协议
///消息是否存储,是否计入未读数
+ (RCMessagePersistent)persistentFlag {
    return (MessagePersistent_ISPERSISTED | MessagePersistent_ISCOUNTED);
}
#pragma mark NSCoding协议
/// NSCoding
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    if (self) {
        self.chatModel = [aDecoder decodeObjectForKey:@"chatModel"];
    }
    return self;
}
/// NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.chatModel forKey:@"chatModel"];
}
#pragma mark RCMessageContentView协议
/// 会话列表中显示的最新消息内容
- (NSString *)conversationDigest {
    return @"[已发货]";
}
#pragma mark RCMessageCoding协议
///将消息内容编码成json
- (NSData *)encode {
    NSMutableDictionary *dataDict = [NSMutableDictionary dictionary];
    if (self.chatModel) {
        [dataDict setObject:self.chatModel.mj_keyValues forKey:@"chatModel"];
    }
    if (self.senderUserInfo) {
        NSMutableDictionary *userInfoDic = [[NSMutableDictionary alloc] init];
        if (self.senderUserInfo.name) {
            [userInfoDic setObject:self.senderUserInfo.name forKeyedSubscript:@"name"];
        }
        if (self.senderUserInfo.portraitUri) {
            [userInfoDic setObject:self.senderUserInfo.portraitUri forKeyedSubscript:@"portrait"];
        }
        if (self.senderUserInfo.userId) {
            [userInfoDic setObject:self.senderUserInfo.userId forKeyedSubscript:@"id"];
        }
        [dataDict setObject:userInfoDic forKey:@"user"];
    }
    NSData *data = [NSJSONSerialization dataWithJSONObject:dataDict options:kNilOptions error:nil];
    return data;
}
///将json解码生成消息内容
- (void)decodeWithData:(NSData *)data {
    if (data) {
        __autoreleasing NSError *error = nil;
        
        NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
        
        if (dictionary) {
            self.chatModel = [ChatModel mj_objectWithKeyValues:dictionary[@"chatModel"]];
            NSDictionary *userinfoDic = dictionary[@"user"];
            [self decodeUserInfo:userinfoDic];
        }
    }
}
///消息的类型名
+ (NSString *)getObjectName {
    return DeliverSentMessageTypeIdentifier;
}
-(instancetype)init{
    if (self = [super init]) {
        self.chatModel = [[ChatModel alloc]init];
    }
    return self;
}
@end
第二步:
自定义cell ,并注册(registerClass:forMessageClass:,则不再走 cellForItem、 sizeForItem)

QQ20171113-114916@2x.jpg
:RCMessageBaseCell
    在baseContentView添加自定义控件(建议在 baseContentView 上方预留 10)
    适合高度自定制
或
:RCMessageCell  
    在继承 RCMessageBaseCell 的基础上增加显示头像和昵称
    在messageContentView添加自定义控件。
    不适合高度自定制
    // 注册CELL
    [self registerClass:[RCMessageCell class] forMessageClass:[RCMessageContent class]];
// size cell中实现(必须实现,不会再走cellforItem、sizeForItem)
+(CGSize)sizeForMessageModel:(RCMessageModel *)model withCollectionViewWidth:(CGFloat)collectionViewWidth referenceExtraHeight:(CGFloat)extraHeight{
    // ExtraHeigh: cell内容区域之外的高度
    return CGSizeMake(100, 100);
}
4. 相关
- 用户信息
  为了信息安全和一致:存储在App服务器而不是融云服务器。
  融云提供了 用户信息提供者、群组信息提供者、群名片信息提供者,只需实现响应协议即可正确获取并显示信息。
 
 // --------- 群组信息提供者 --------- 
 <RCIMGroupInfoDataSource>  
[[RCIM sharedRCIM]setGroupInfoDataSource:self];
 - (void)getGroupInfoWithGroupId:(NSString *)groupId
 completion:(void (^)(RCGroup *groupInfo))completion{
 }
 
 
 // --------- 群名片信息提供者 --------- 
 <RCIMGroupUserInfoDataSource>  
[[RCIM sharedRCIM]setGroupUserInfoDataSource:self];
 - (void)getUserInfoWithUserId:(NSString *)userId
 inGroup:(NSString *)groupId
 completion:(void (^)(RCUserInfo *userInfo))completion{
 }
 
 
 // --------- 用户信息提供者 --------- 
 <RCIMUserInfoDataSource>
[[RCIM sharedRCIM]setUserInfoDataSource:self];
 // 融云在需要显示用户信息时调用
- (void)getUserInfoWithUserId:(NSString *)userId completion:(void (^)(RCUserInfo *))completion{
    
    // 判断是否是自己
    YTAccount *acount=[YTAccountTool account];
    if ([userId isEqualToString:[NSString stringWithFormat:@"%@",acount.userId]]) { // 是
        RCUserInfo *user = [[RCUserInfo alloc]init];
        user.userId=[NSString stringWithFormat:@"%@",acount.userId];
        user.name=acount.name;
        user.portraitUri=acount.iconurl?[NSString stringWithFormat:@"%@%@",AccessPhoto,acount.iconurl]:@"";
        return completion(user);
    }else{  // 别人
        
        // 获取信息
        [AFNetWorkTool POST:YTBaseUrl(kCustomerMessage) params:@{@"userId":userId} success:^(NSURLSessionDataTask *task, id responseObject) {
            if(SUCCESS){
                NSDictionary *dic=(NSDictionary *)responseObject[@"data"];
                //
                RCUserInfo *userInfo2 = [[RCUserInfo alloc]init];
                userInfo2.userId=dic[@"id"];
                userInfo2.name=dic[@"name"];
                userInfo2.portraitUri=YTImgBaseUrl(dic[@"photo"]);
                
                // 刷新UI
                [[RCIM sharedRCIM]refreshUserInfoCache:userInfo2 withUserId:userInfo2.userId];
            }
        } fail:^(NSURLSessionDataTask *task, NSError *error) {
            NSLog(@"%@",error);       
        }];
    }
    return completion(nil);
}
缓存
缓存---有:显示;无:通过信息提供者获取信息并缓存。
            
    // 是否持久化缓存到本地(默认:false,通过信息提供者获取数据后进行缓存,关闭App后删除缓存;true则不会删除,重新启动后使用缓存显示)
    [[RCIM sharedRCIM]setEnablePersistentUserInfoCache:true];
            
            
            // 清除缓存数据
            // 清除用户信息缓存
            [[RCIM sharedRCIM]clearUserInfoCache];
            // 清除群组信息缓存
            [[RCIM sharedRCIM]clearGroupInfoCache];
            // 清除群名片信息缓存
            [[RCIM sharedRCIM]clearGroupUserInfoCache];
            
            
            // 获取缓存
            // 获取缓存中的用户信息
            [[RCIM sharedRCIM]getUserInfoCache:@"userId"];
            // 获取缓存中的群组信息
            [[RCIM sharedRCIM]getGroupInfoCache:@"groupId"];
            // 获取缓存中的群名片信息
            [[RCIM sharedRCIM]getGroupUserInfoCache:@"userId" withGroupId:@"groupId"];
            
            
            // 刷新缓存,可能不会立即刷新UI,可通过reloadData强制刷新
            // 刷新用户缓存
            [[RCIM sharedRCIM]refreshUserInfoCache:[RCUserInfo new] withUserId:@""];
            // 刷新群组缓存
            [[RCIM sharedRCIM]refreshGroupInfoCache:[RCGroup new] withGroupId:@""];
            // 刷新群中的个人名片缓存
            [[RCIM sharedRCIM]refreshGroupUserInfoCache:[RCUserInfo new] withUserId:@"" withGroupId:@""];
- 会话类型
单聊 ConversationType_PRIVATE
    // 一个继承了RCConversationViewController的类
    YTMessageViewController *conversationVC = [YTMessageViewController new];
    conversationVC.conversationType = ConversationType_PRIVATE;    // 会话类型
    conversationVC.title = ((RCUserInfo *)model.extend).name;      // navTitle
    conversationVC.targetId = model.targetId;                      // 目标ID
    [self.navigationController pushViewController:conversationVC animated:YES];
群组 ConversationType_GROUP
    // 进入群组:和单聊相同,只是type不同
    所有群组操作都通过App后台交互,再由App后台和融云交互。
讨论组 ConversationType_DISCUSSION
    两个以上用户一起进行聊天时生成讨论组。
    用户可以自行添加好友生成一个讨论组聊天。
    成员关系由融云负责建立并保持。
    退出聊天界面或者离线后可以收到推送通知。
    同一个用户最多可加入 500 个讨论组。
    讨论组不能解散。
    // 进入讨论组:和单聊相同,只是type不同
    // 创建讨论组(用户ID)
    [[RCIM sharedRCIM]createDiscussion:@"讨论组名称" userIdList:@[] success:^(RCDiscussion *discussion) {
        // discussion讨论组对象
    } error:^(RCErrorCode status) {
        NSLog(@"创建讨论组出错,错误码:%d",(int)status);
    }];
    
    // 添加用户到讨论组(用户ID)
    [[RCIM sharedRCIM]addMemberToDiscussion:@"讨论组id" userIdList:@[] success:^(RCDiscussion *discussion) {
        // discussion讨论组对象
    } error:^(RCErrorCode status) {
        NSLog(@"加入讨论组出错,错误码:%d",(int)status);
    }];
    
    // 移除成员从讨论组
    [[RCIM sharedRCIM]removeMemberFromDiscussion:@"讨论组id" userId:@"用户id" success:^(RCDiscussion *discussion) {
        // discussion讨论组对象
    } error:^(RCErrorCode status) {
        NSLog(@"移除成员从讨论组出错,错误码:%d",(int)status);
    }];
    
    // 退出讨论组
    [[RCIM sharedRCIM]quitDiscussion:@"讨论组id" success:^(RCDiscussion *discussion) {
        // discussion讨论组对象
    } error:^(RCErrorCode status) {
        NSLog(@"退出讨论组出错,错误码:%d",(int)status);
    }];
    
    // 获取讨论组信息出错
    [[RCIM sharedRCIM]getDiscussion:@"讨论组id" success:^(RCDiscussion *discussion) {
        // discussion讨论组对象
    } error:^(RCErrorCode status) {
        NSLog(@"获取讨论组信息出错,错误码:%d",(int)status);
    }];
    
    // 设置讨论组名称
    [[RCIM sharedRCIM]setDiscussionName:@"讨论组id" name:@"讨论组名称" success:^{
        // discussion讨论组对象
    } error:^(RCErrorCode status) {
        NSLog(@"设置讨论组名称出错,错误码:%d",(int)status);
    }];
    
    // 设置讨论组加人权限(是否放开)
    [[RCIM sharedRCIM]setDiscussionInviteStatus:@"讨论组id" isOpen:true success:^{
        // discussion讨论组对象
    } error:^(RCErrorCode status) {
        NSLog(@"设置讨论组加人权限出错,错误码:%d",(int)status);
    }];
聊天室 ConversationType_CHATROOM
    // 加入聊天室(设置id,type,直接push就加入了。同单聊)
    聊天室的消息没有 Push 通知,没有人数限制,退出后不再接收消息并清空本地消息记录。
    融云默认一个用户同时只能加入一个聊天室。
    一个聊天室一个小时内没有人发送消息或者加入时会被自动销毁。
    // 设置 获取历史记录的条数(默认获取10条历史记录)(-1:不获取,0:默认10条,0<count<=50)
    [self setDefaultHistoryMessageCountOfChatRoom:-1];
    
    // 自定义
    // 加入聊天室(不存在则创建)
    [[RCIMClient sharedRCIMClient]joinChatRoom:@"聊天室ID" messageCount:10 success:^{
    } error:^(RCErrorCode status) {
        NSLog(@"加入聊天室出错,错误码:%d",(int)status);
    }];
    // 加入已存在的聊天室
    [[RCIMClient sharedRCIMClient]joinExistChatRoom:@"聊天室ID" messageCount:10 success:^{
    } error:^(RCErrorCode status) {
        NSLog(@"加入聊天室出错,错误码:%d",(int)status);
    }];
    // 退出聊天室
    [[RCIMClient sharedRCIMClient]quitChatRoom:@"聊天室ID" success:^{
    } error:^(RCErrorCode status) {
        NSLog(@"退出聊天室出错,错误码:%d",(int)status);
    }];
    // 获取聊天室信息
    [[RCIMClient sharedRCIMClient]getChatRoomInfo:@"聊天室ID" count:10 order:RC_ChatRoom_Member_Desc success:^(RCChatRoomInfo *chatRoomInfo) {
    } error:^(RCErrorCode status) {
        NSLog(@"获取聊天室信息出错,错误码:%d",(int)status);
    }];
系统会话 ConversationType_SYSTEM
  不能从App发起系统会话,只能通过App服务器来发起
客服 ConversationType_CUSTOMERSERVICE
    客服---收费  (融云Demo有个RCDCustomerServiceViewController)
    // 启动客服
    // type,title,客服id,csInfo(用于上传用户信息到客服后台,数据的nickName和portraitUrl必须填写),push即可
    // 退出客服
    // 点击返回按钮即可退出(默认),可通过改 来让退出页面不退出客服
    <key>CustomerService</key>
    <dict>
    <key>SuspendWhenLeave</key>
    <true/>     改为false
    </dict>
    // 评价
    // 在客服页面停留一分钟后退出会自动弹出客服评价,退出时弹出(人工和机器 界面不一样)
公众号
    // 跳转到 已关注的公众号列表页
    RCPublicServiceListViewController *publicServiceVC = [[RCPublicServiceListViewController alloc] init];
    [self.navigationController pushViewController:publicServiceVC  animated:YES];
    // 跳转到 搜索公众号页
    RCPublicServiceSearchViewController *searchFirendVC = [[RCPublicServiceSearchViewController alloc] init];
    [self.navigationController pushViewController:searchFirendVC  animated:YES];
    // 跳转到会话页(type,id,navTitle,userName)
- 消息类型
- 系统自带消息类型
文本消息
    RCTextMessage *txtMsg = [RCTextMessage messageWithContent:@""];
图文消息
    RCRichContentMessage *richMsg = [RCRichContentMessage messageWithTitle:@"title" digest:@"内容摘要" imageURL:@"imgURL" url:@"跳转url" extra:nil];
位置消息 (缩略图、坐标、地址名)
    RCLocationMessage *locationMessage = [RCLocationMessage messageWithLocationImage:[UIImage new] location:location locationName:@"locationName"];
语音消息
    RCVoiceMessage *rcVoiceMessage = [RCVoiceMessage messageWithAudio:[NSData new] duration:10];
    /*
   必须:wav格式、采样率必须是8000Hz,采样位数(精度)必须为16位,10s
     参考IMKit中的录音参数:
     NSDictionary *settings = @{AVFormatIDKey: @(kAudioFormatLinearPCM),
                            AVSampleRateKey: @8000.00f,
                            AVNumberOfChannelsKey: @1,
                            AVLinearPCMBitDepthKey: @16,
                            AVLinearPCMIsNonInterleaved: @NO,
                            AVLinearPCMIsFloatKey: @NO,
                            AVLinearPC'MIsBigEndianKey: @NO};
    */
    发送上述消息(会话类型,目标用户ID,文本消息,接收方离线时需要显示的内容,接收方离线时需要在远程推送中不显示的内容)
    [[RCIM sharedRCIM]sendMessage:ConversationType_PRIVATE targetId:@"目标ID" content:txtMsg pushContent:@"" pushData:@"" success:^(long messageId) {
    } error:^(RCErrorCode nErrorCode, long messageId) {
    }];
图片消息 (缩略图:240*240,大图尺寸:960*960)
    RCImageMessage *imageMessage = [RCImageMessage messageWithImage:[UIImage new]];
    RCImageMessage *imageMessage = [RCImageMessage messageWithImageURI:@"imgURL"];
    RCImageMessage *imageMessage = [RCImageMessage messageWithImageData:[NSData data]];
    /*
    缩略图尺寸为:240 x 240 像素,以宽度和高度中较长的边不超过 240 像素等比压缩。
    大图尺寸为:960 x 960 像素,以宽度和高度中较长的边不超过 960 像素等比压缩
    */
文件消息
    RCFileMessage *fileMessage = [RCFileMessage messageWithFile:@"filePath"];
    发送上述消息,存储有效期为 6 个月
    [[RCIM sharedRCIM]sendMediaMessage:ConversationType_PRIVATE targetId:@"目标ID" content: imageMessage pushContent:@"" pushData:@"" progress:^(int progress, long messageId) {  // 0~100
    } success:^(long messageId) {
    } error:^(RCErrorCode errorCode, long messageId) {
    } cancel:^(long messageId) {
    }];
- 消息接收监听
    // 消息接收监听 <RCIMReceiveMessageDelegate>  放在AppDelegate中
    [RCIM sharedRCIM].receiveMessageDelegate=self;
    
// 收到消息时调用(此时message已被存储数据库,详见下图)
-(void)onRCIMReceiveMessage:(RCMessage *)message left:(int)left{
    // 发送通知、播放音效、震动。。。
    // AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);  // 震动
}
// 撤回消息时回调
-(void)onRCIMMessageRecalled:(long)messageId{
}
// 接收到消息准备播放方提示音前调用(默认:false,播放默认提示音)
-(BOOL)onRCIMCustomAlertSound:(RCMessage *)message{
    return true;    // 不播放默认提示音
}
// 应用处于后台时,接收到消息时弹出本地通知时调用(默认:false,默认提示框)
-(BOOL)onRCIMCustomLocalNotification:(RCMessage *)message withSenderName:(NSString *)senderName{
    // true则不弹通知
    return false;
}

- 消息提醒
    // 设置 是否屏蔽消息提醒
    [[RCIMClient sharedRCIMClient]setConversationNotificationStatus:ConversationType_PRIVATE targetId:@"id" isBlocked:true success:^(RCConversationNotificationStatus nStatus) {
    } error:^(RCErrorCode status) {
        NSLog(@"屏蔽消息提醒出错,错误码:%d",(int)status);
    }];
    // 获取 是否屏蔽消息提醒
    [[RCIMClient sharedRCIMClient]getConversationNotificationStatus:ConversationType_PRIVATE targetId:@"" success:^(RCConversationNotificationStatus nStatus) {
    } error:^(RCErrorCode status) {
        NSLog(@"获取是否屏蔽消息提醒出错,错误码:%d",(int)status);
    }];
时间段屏蔽
    // 设置 时间段屏蔽,(开始屏蔽消息提醒的时间,持续时间0~1440min)
    [[RCIMClient sharedRCIMClient]setNotificationQuietHours:@"HH:MM:SS s" spanMins:10 success:^{
    } error:^(RCErrorCode status) {
        NSLog(@"时间段屏蔽出错,错误码:%d",(int)status);
    }];
    
    
    // 删除 时间段屏蔽
    [[RCIMClient sharedRCIMClient]removeNotificationQuietHours:^{
    } error:^(RCErrorCode status) {
        NSLog(@"删除时间段屏蔽出错,错误码:%d",(int)status);
    }];
    
    // 获取 时间段屏蔽
    [[RCIMClient sharedRCIMClient]getNotificationQuietHours:^(NSString *startTime, int spansMin){
} error:^(RCErrorCode status) {
        NSLog(@"获取时间段屏蔽出错,错误码:%d",(int)status);
    }];
通知
    可通过RCIMReceiveMessageDelegate的onRCIMCustomAlertSound、onRCIMCustomLocalNotification来选择性关闭
    // 是否关闭提示音(默认:false)
    [[RCIM sharedRCIM]setDisableMessageAlertSound:true];
    // 是否关闭通知
    [[RCIM sharedRCIM]setDisableMessageNotificaiton:true];
- 小角标
获取所有未读消息数
#import <RongIMKit/RongIMKit.h>
// 获取所有未读消息数
-(NSInteger)getUnreadCount{
    int unreadMsgCount = [[RCIMClient sharedRCIMClient] getUnreadCount:@[
                                                                         @(ConversationType_PRIVATE),
                                                                         @(ConversationType_DISCUSSION),
                                                                         @(ConversationType_APPSERVICE),
                                                                         @(ConversationType_PUBLICSERVICE),
                                                                         @(ConversationType_GROUP)
                                                                         ]];
    return unreadMsgCount ;
}
// 设置角标
-(void)setBadageNum{
    
    NSInteger unreadMessageCount = [self getUnreadCount];
    
    // 设置tabbar 的icon
    UITabBarController *tabbar = (UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController ;
    if ([tabbar isKindOfClass:[UITabBarController class]]) {
        
        UITabBarItem *item = [tabbar.tabBar.items objectAtIndex:1];
        
// 如果没有未读消息返回值为nil
        if (unreadMessageCount == 0 || unreadMessageCount == nil) {
            item.badgeValue = nil ;           
            return ;
        }
        item.badgeValue = [NSString stringWithFormat:@"%d",unreadMessageCount];
    }
}
收到消息时设置小角标
    // <RCIMReceiveMessageDelegate>  
    [RCIM sharedRCIM].receiveMessageDelegate=self;
    // 收到消息时调用(更新)
    -(void)onRCIMReceiveMessage:(RCMessage *)message left:(int)left{
       dispatch_async(dispatch_get_main_queue(), ^{
                   [self setBadageNum];
        });
    }
进入聊天页设置小角标
功能进阶部分(红包、动态表情、群组@) 待续