iOS开发小记:关于环信Demo3.0的使用总结以及昵称和头像问题的研究与解决

  • Post author:
  • Post category:其他


我们要做的项目类似于微信,有联系人页面、聊天列表与聊天页面、设置页面,刚刚好包括了环信Demo3.0的全部内容,所以理论上应该把Demo直接嵌入工程。由于这个Demo是实现了一些IM的基本UI,但是因为环信的官方文档和视频比较欠缺,维护起来相当麻烦,所以在开发的过程中有好多次决定弃用这个Demo,自己构建UI。但是又考虑到,Demo的代码可以说相当健壮,各种机制都集成的非常好(比如好友申请和删除的回调处理),最终决定还是硬着头皮上,改Demo的UI部分以供自己使用。

环信的官方文档虽然比较欠缺,但是代码的注释和命名还是很好的,基本上可以一目了然每个变量、每个函数在做什么事情。笔者遇到的最大的问题就是好友头像和用户的显示问题,因为环信的服务器是不存储除了用户名和密码以外的其他任何数据的。解决这个问题的办法,就是将你要保存的其他用户信息保存在应用程序自己的服务器上。先来看一张官方图:

如图所示,你需要构建自己的应用服务器。环信的建议是,包括好友体系都不要用环信来维护,最好是由你APP自己的服务器来维护。但是笔者为了简化开发,并没有这样做,而是使用环信维护的好友体系,把昵称和头像存在自己的服务器上,每次从环信上获得当前用户的好友列表,再根据每个好友的用户名到自己的服务器上去获取。

需要显示昵称和头像的地方有以下几处:联系人列表、聊天页表、聊天页面、联系人和群的详情页面。环信Demo3.0中有一个叫做ContactListViewController的类正是联系人列表页面,笔者决定从这个类入手。这个类是一个常见的带Tableview的Controller,找到它的下拉刷新和tableview的几个代理方法进行尝试,发现无论如何也不能完美的实现想要的效果。于是开始研究Demo是怎样显示昵称和头像的,经研究发现,环信是使用国外的App数据储存商Parse来实现储存,于是找到Demo中存取Parse部分的代码:

在ViewDidLoad中,笔者找到了一个叫做

UserProfileManager

的类(绿色部分),看了看这个类的h和m文件,发现这个类就是管理存取Parse上数据的类,而且全局是一个单例。笔者认真分析了一下这个类的作用,根据它提供给外部使用的方法的名称,发现他的作用主要有以下三个,且是按照以下顺序的:


1—-获取当前用户在Parse服务器上的好友数据(头像、昵称),储存到内存中或者本地沙盒中:loadUserProfileInBackgroundWithBuddy:self.contactsSource saveToLoacal:YES completion:NULL

2—-根据好友的用户名(环信储存的用户名),返回内存或本地沙盒中保存的昵称:  – (NSString*)getNickNameWithUsername:(NSString*)username;

3—根据好友的用户名(环信储存的用户名),返回内存或本地沙盒中保存的头像Url:- (UserProfileEntity*)getUserProfileByUsername:(NSString*)username;


其中2和3其实可以合成一个,因为头像的url一般都是用一个用户的唯一标识(数据库表中的主键)来命名,而这个唯一标识刚好可以是环信服务器上所储存的用户名字段

举个例子,在ViewDidLoad方法中,先调用方法1,获取到昵称和头像,然后在tableview的cellForRowAtIndexPath,也就是给每个cell赋值的那个方法中,先调用方法2和3,再给cell赋值。

如图所示,(绿色部分),根据model.buddy.username属性,获取到环信服务器所保存的用户名,再调用23方法得到昵称和头像url的拼接。

于是根据这个类,笔者仿照它也写了一个单例,给单例加了一个保存所有好友昵称的NSDictionary属性,这个单例对象在APP全局是唯一的,所以该属性在内存中也是唯一的,每次调用获取它的方法,不用担心是空或者重新生成新对象的问题发生。而且最好将属性设置成nonatomic的,这样可以防止在一次网络请求构建改属性的过程中被访问,导致数据错误。但是笔者还是加上了判断。笔者的这个类叫做NickNameAndHeadImage,也就是上面两幅截图中绿色注释部分紧跟的代码。并且还加入了一些判断,比如,如果昵称为空,则显示环信服务器上保存的用户名。当然,如果你想让更新即时的话,你可以在这个类中实现一些发送和接受透传消息的方法,笔者暂时没有写。

大家可以全局搜索UserProfileManager的使用地方(比如按昵称搜索,首字母排序,详情等),发现基本上就是用到了笔者所提到那三种操作,所以,可以用自己仿制的这个单例类完美的代替UserProfileManager的功能,这样就可以在不修改环信Demo逻辑的前提下,接入自己的APP服务器,这样便保留了环信Demo的所有优良特性。

下面将NickName类的代码列出,供大家参考:

#import <Foundation/Foundation.h>

@interface NickNameAndHeadImage : NSObject

+(instancetype) shareInstance;

- (void)loadUserProfileInBackgroundWithBuddy:(NSArray*)buddyList;

- (NSString*)getNicknameByUserName:(NSString*)username;


#import "NickNameAndHeadImage.h"

@interface NickNameAndHeadImage()

@property (strong, nonatomic) NSMutableArray *UserNames;

@property (strong, nonatomic) NSMutableDictionary *NickNames;

@property (nonatomic) BOOL DownloadHasDone;

@property (nonatomic) BOOL LoadFromLocalDickDone;


@end

@implementation NickNameAndHeadImage
{
    
}
static NickNameAndHeadImage* _instance = nil;

+(instancetype) shareInstance
{
    static dispatch_once_t onceToken ;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init] ;
    }) ;
    
    return _instance ;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        _DownloadHasDone = NO;
        _LoadFromLocalDickDone = NO;
        _UserNames = [NSMutableArray array];
        NSMutableDictionary *dic = [[NSUserDefaults standardUserDefaults] objectForKey:dUserDefaults_Dic_NickName];
        if (dic == nil || [dic count] == 0) {
             _NickNames = [NSMutableDictionary dictionary];
            _LoadFromLocalDickDone = YES;
        }
        else
        {
            _LoadFromLocalDickDone = YES;
            _NickNames = [NSMutableDictionary dictionaryWithDictionary:dic];
        }
       
        
    }
    return self;
}


- (void)loadUserProfileInBackgroundWithBuddy:(NSArray*)buddyList
{
    _DownloadHasDone = NO;
    [_UserNames removeAllObjects];
    [_NickNames removeAllObjects];
    
    if (buddyList == nil || [buddyList count] == 0)
    {
        return;
    }
    else
    {
        for (EMBuddy *buddy in buddyList)
        {
            [_UserNames addObject:buddy.username];
        }
    }
    
    [self loadUserProfileInBackgroundWithUsernames];
}

- (void)loadUserProfileInBackgroundWithUsernames
{

    _DownloadHasDone = NO;
    
    //首先构造Json数组
    //1.头
    NSMutableString *jsonString = [[NSMutableString alloc] initWithString:@"{\"mobilelist\":["];
    
    for(NSString *mobile in _UserNames){
        
        //2. 遍历数组,取出键值对并按json格式存放
        NSString *string;
        
        string  = [NSString stringWithFormat:
                   @"{\"mobile\":\"%@\"},",mobile];
        
        [jsonString appendString:string];
        
    }
    // 3. 获取末尾逗号所在位置
    NSUInteger location = [jsonString length]-1;
    
    NSRange range       = NSMakeRange(location, 1);
    
    // 4. 将末尾逗号换成结束的]}
    [jsonString replaceCharactersInRange:range withString:@"]}"];
    
    NSLog(@"请求昵称时要发送的jsonString = %@",jsonString);
    
    NSString *token = [[NSUserDefaults standardUserDefaults] objectForKey:dUserDefaults_String_LoginToken];
    NSString *url = [NSString stringWithFormat:@"customer/contract?token=%@",token];
    NSDictionary *postdic = [NSDictionary dictionaryWithObjectsAndKeys:jsonString,@"mobilelist",nil];
    [HttpUtil POST_Path:url params:postdic completed:^(id JSON,NSString *str)
     {
         _LoadFromLocalDickDone = NO;
         NSLog(@"打印JSON数据:%@",str);//打印Json数据
         NSString *state = [[JSON objectForKey:@"json"] objectForKey:@"state"];
         if ([state isEqualToString:@"1"]) {//获得昵称成功
             
             //打印信息
             NSString *msg = [[JSON objectForKey:@"json"] objectForKey:@"msg"];
             NSLog(@"获得昵称成功:msg:%@",msg);
             
             NSArray *array = [[[JSON objectForKey:@"json"] objectForKey:@"data"] objectForKey:@"list"];
             
             
             for (NSDictionary *dic in array) {
                 [_NickNames setObject: [dic objectForKey:@"name"] forKey: [dic objectForKey:@"mobile"]];
             }
             
             NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
             [ud setObject:_NickNames forKey:dUserDefaults_Dic_NickName];
             [ud synchronize];
             _LoadFromLocalDickDone = YES;
             
             _DownloadHasDone = YES;
             
             
         }
         else//获得昵称失败
         {
             _DownloadHasDone = NO;
             //打印信息
             NSString *msg = [[JSON objectForKey:@"json"] objectForKey:@"msg"];
             NSLog(@"获得昵称失败:msg:%@",msg);
         }
     }
                 failed:^(NSError *err){
                     
                     _DownloadHasDone = NO;
                     [SVProgressHUD showSuccessWithStatus:@"登录失败"];
                     NSLog(@"获得昵称失败:%@",err);
                     
                 }];
}

- (NSString*)getNicknameByUserName:(NSString*)username
{
    if(_DownloadHasDone == YES)
    {
        NSString *string = [_NickNames objectForKey:username];
        if (string == nil || [string length] == 0) {
            return username;
        }
        return string;
    }
    
    else if(_LoadFromLocalDickDone == YES)
    {
        NSMutableDictionary *dic = [[NSUserDefaults standardUserDefaults] objectForKey:dUserDefaults_Dic_NickName];
        NSString *string = [dic objectForKey:username];
        if (string == nil || [string length] == 0) {
            return username;
        }
        return string;
    }
    return username;
}


附上截图

其中,第一行为没有上传头像而有昵称,第二行为有头像有昵称,第三行为有头像无昵称,则显示用户名。

希望可以和大家多交流,做出更完美的App。