网易云信IM自定义消息实现

IM流程(网易云信SDK)

1.正常账号登录流程登录成功,获取IMToken
2.然后调用IM登录接口进行登录
3.具体配置(自定义消息的配置)

IM实现

在.pch或者.h中引入

#import "NIMKit.h"

在appdelete的didFinishLaunchingWithOptions方法中添加

[self registNIMConfig];

方法:

/// 注册 NIM appkey & custom message Decoder
- (void)registNIMConfig {
      // addition settings
    //配置额外配置信息 (需要在注册 appkey 前完成) 一系列的配置 可选
    self.sdkConfigDelegate = [[NTESSDKConfigDelegate alloc] init];
    [[NIMSDKConfig sharedConfig] setDelegate:self.sdkConfigDelegate];
    // 是否需要多端同步未读数
    [[NIMSDKConfig sharedConfig] setShouldSyncUnreadCount:YES];
    // 自动登录重试次数
    [[NIMSDKConfig sharedConfig] setMaxAutoLoginRetryTimes:10];
    // 本地 log 存活期
    [[NIMSDKConfig sharedConfig] setMaximumLogDays:[[NTESBundleSetting sharedConfig] maximumLogDays]];
    // 是否将群通知计入未读
    [[NIMSDKConfig sharedConfig] setShouldCountTeamNotification:[[NTESBundleSetting sharedConfig] countTeamNotification]];
    // 是否支持动图缩略
    [[NIMSDKConfig sharedConfig] setAnimatedImageThumbnailEnabled:[[NTESBundleSetting sharedConfig] animatedImageThumbnailEnabled]];    
    // mainsettings(划重点)
    //  设置appKey && cerName
    NSString *appKey = [[HZNIMConfig sharedConfig] appKey];
    NSString *cerName= [[HZNIMConfig sharedConfig] cerName];
    // IM注册
    [[NIMSDK sharedSDK] registerWithAppID:appKey
                                  cerName:cerName];
    // 注册消息解析器(自定义消息需要做这个处理)                              
    [NIMCustomObject registerCustomDecoder:[[HZAttachmentDecoder alloc] init]];
    // 注册布局的layout
    [[NIMKit sharedKit] registerLayoutConfig:[HZCustomCellLayoutConfig class]];
    // 收到全局消息的处理包括 收到消息声音提示以及收到别的端消息撤回以及其他消息的通知回调
    [[HZNotificationCenter sharedCenter] start];
}

// 更新deviceToken 到云信用于接收推送消息

- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    [[NIMSDK sharedSDK] updateApnsToken:deviceToken];
}

重点一 :自定义消息解析器

消息解析器的目的是:接收到自定义消息之后,把消息解析为已知的消息体(也就是Model) 我们这里自定义消息解析器的类是HZAttachmentDecoder 继承自NSObject 遵守NIMCustomAttachmentCoding 协议 实现- (id<NIMCustomAttachment>)decodeAttachment:(NSString *)content方法。

- (id<NIMCustomAttachment>)decodeAttachment:(NSString *)content {
    id<NIMCustomAttachment> attachment;
    NSData * data = [content dataUsingEncoding:NSUTF8StringEncoding];
    // 解析自定义消息的content
    if  (data) {
        NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        if ([dict isKindOfClass:[NSDictionary class]]) {
            if ([dict[@"type"] isEqualToString:@"customermMsg"]) {
            //转化为指定的自定义消息解析器
                HZCustomerAttachment * consultationCaseAttachment = [HZCustomerAttachment customerAttachmentWithDict:dict];
                attachment = consultationCaseAttachment;
            }
    }
    return attachment;
}

重点二 :自定义消息布局Layout

自定义消息布局Layout的作用是:接收到自定义消息之后,把消息解析为已知的消息体(也就是Model)之后,按照一定的UI对其进行布局。这里用到的自定义布局的layout的类是HZCustomCellLayoutConfig 继承自NIMCellLayoutConfig如果只是普通消息(包括消息体联系人头像),那么这个类不需要修改,继承就行了。如果需要修改,重写协议的方法就行。

/**
 * @return 返回message的内容大小(根据此计算出自定义消息视图的大小)
 */
- (CGSize)contentSize:(NIMMessageModel *)model cellWidth:(CGFloat)width;
/**
 *  需要构造的cellContent类名(根据model的不同(自定义消息类型的不同)选择对应的view)
 */
- (NSString *)cellContent:(NIMMessageModel *)model;
/**
 *  左对齐的气泡,cell气泡距离整个cell的内间距(设置间距)
 */
- (UIEdgeInsets)cellInsets:(NIMMessageModel *)model;
/**
 *  左对齐的气泡,cell内容距离气泡的内间距(设置间距)
 */
- (UIEdgeInsets)contentViewInsets:(NIMMessageModel *)model;
/**
 *  是否显示头像(有一些自定义消息可能不显示头像,重载次方法可以自由操作)
 */
- (BOOL)shouldShowAvatar:(NIMMessageModel *)model;
/**
 *  左对齐的气泡,头像到左边的距离(设置间距)
 */
- (CGFloat)avatarMargin:(NIMMessageModel *)model;

/**
 *  是否显示姓名
 */
- (BOOL)shouldShowNickName:(NIMMessageModel *)model;

/**
 *  左对齐的气泡,昵称到左边的距离(设置间距)
 */
- (CGFloat)nickNameMargin:(NIMMessageModel *)model;
/**
 *  消息显示在左边
 */
- (BOOL)shouldShowLeft:(NIMMessageModel *)model;
/**
 *  需要添加到Cell上的自定义视图
 */
- (NSArray *)customViews:(NIMMessageModel *)model;

系统本身的给出的消息体的config在NIMSessionContentConfigFactory类中,这里给出了文本消息、视频、音频、图像、文件、地址、提醒消息等的配置。

重点三:自定义消息的构造

构造自定义消息:自定义消息继承自NIMMessage,构造消息的时候需要用到的是消息构造器NIMMessageMaker 一个普通的文本消息构造如下:

+ (NIMMessage*)msgWithText:(NSString*)text
{
    NIMMessage *textMessage = [[NIMMessage alloc] init];
    textMessage.text        = text;
    textMessage.remoteExt = [self remoteExtMapperWithMessage:textMessage];
    return textMessage;
}

text 为普通文本消息类型remoteExt为服务端扩展字段,这个字段将在本地存储且发送至对端,上层需要保证 NSDictionary 可以转换为 JSON,长度限制 4K. 一般用于自定义消息中,保存一些业务信息. 我们在开发中会有一些需求,IM提供给我们的消息不能满业务的需求,这就需要我们构造自定义消息来解决. 一个自定义消息构造如下:

+ (NIMMessage *)messageOfRevoke:(HZDeleteMsgTipsAttachment *)deleteMsgTipsAttachment {

    NIMCustomObject *customObject     = [[NIMCustomObject alloc] init];
    customObject.attachment           = deleteMsgTipsAttachment;
    NIMMessage *message               = [[NIMMessage alloc] init];
    message.messageObject             = customObject;
    message.remoteExt = [self remoteExtMapperWithMessage:message];
    return message;
}

NIMCustomObject为自定义消息的对象,把自定义消息的消息体作为附件进行发送。attachment为自定义消息附件的对象。SDK负责将attachment通过encodeAttachment接口序列化后的结果进行透传. 这样就构造出了一个自定义消息。

NIMCustomAttachment协议

我们在刚开始的时候注册了消息解析器。遵循了协议:NIMCustomAttachmentCoding根据返回值的不同确定了不同的自定义消息解析的类型。那么,消息在解析的过程中发生了什么?下面我们一起由一个自定义消息例子来看.

@interface HZDeleteMsgTipsAttachment : NSObject<NIMCustomAttachment>
@property (nonatomic,copy) NSString *type;
@property (nonatomic, copy) NSString * messageId;
@property (nonatomic, copy) NSString * from;
+ (instancetype)deleteMsgTipsAttachmentWithDict:(NSDictionary *)dict;
@end

这里要简单讲一下NIMCustomAttachment协议。因为SDK提供几种消息类型不满足业务需求。用到自定义消息。SDK 只负责发送和收取由 NIMCustomObjectid<NIMCustomAttachment> attachment序列化和反序列化后的字节流,在发送端,SDK 获取 encodeAttachment 后得到的字节流发送至对面端 在接收端,SDK 读取字节流,并通过上层 APP 设置的反序列化对象进行解析 (registerCustomDecoder:) 也就是说,我们在接收端实现了+ (instancetype)deleteMsgTipsAttachmentWithDict:(NSDictionary *)dict;这个方法,在消息解析的时候使用,生成自定义消息的model。 我们在发送端实现- (NSString *)encodeAttachment;通过序列化,用于将自定义消息发送出去。

我们在自定义消息中,有时候可能会让我们进行文件的上传。NIMCustomAttachment这个协议为我们提供和了文件上传和下载的方法.只要 APP 实现上传相关的接口,资源的上传就可以由 SDK 自动完成. 需要上传资源需要实现的接口有:

- (BOOL)attachmentNeedsUpload
是否有文件需要上传,在有文件且文件没有上传成功时返回YES. - (NSString *)attachmentPathForUploading
返回需要上传的文件路径 - (void)updateAttachmentURL:(NSString *)urlString 上传成功后SDK会调用这个接口,APP 需要实现这个接口来保存上传后的URL - (NSString *)attachmentURLStringForDownloading; 需要下载的附件url - (NSString *)attachmentPathForDownloading 需要下载的附件本地路径

@implementation HZDeleteMsgTipsAttachment

+ (instancetype)deleteMsgTipsAttachmentWithDict:(NSDictionary *)dict {
    HZDeleteMsgTipsAttachment *deleteMsgTips = [[HZDeleteMsgTipsAttachment alloc]init];
    deleteMsgTips.type = dict[@"type"];

    if ([dict.allKeys containsObject:@"deleteMsg"]) {
        NSDictionary * tempDict = dict[@"deleteMsg"];
        if ([tempDict.allKeys containsObject:@"messageId"]) {
            deleteMsgTips.messageId = tempDict[@"messageId"];
        }
    }    
    if ([dict.allKeys containsObject:@"from"]) {
        deleteMsgTips.from = dict[@"data"][@"from"];
    }
    return deleteMsgTips;
}

- (NSString *)encodeAttachment {    
    NSMutableDictionary * mutableDict = [NSMutableDictionary dictionary];    
    [mutableDict setObject:@"deleteMsgTips" forKey:@"type"];    
    NSDictionary * tempDict = [NSDictionary dictionaryWithObject:(self.messageId ? self.messageId : @"") forKey:@"messageId"];  
    [mutableDict setObject:[NSDictionary
                            dictionaryWithObjects:@[
                                                    tempDict,
                                                    self.from
                                                    ]
                            forKeys:@[
                                      @"deleteMsg",
                                      @"from"
                                      ]] forKey:@"data"];

    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:mutableDict
                                                       options:0
                                                         error:nil];
    return [[NSString alloc] initWithData:jsonData
                                 encoding:NSUTF8StringEncoding];

}


#pragma mark - 上传相关接口
// 是否需要上传附件
- (BOOL)attachmentNeedsUpload {   
    return NO;
}
- (BOOL)attachmentNeedsDownload {
    return NO;
}

综上,我们可以看到一个自定义消息的发送和接收需要经历:构造,解析,布局 三个模块。

comments powered by Disqus