(慕课网)imooc iPhone3.3 接口数据缓存

本文详细介绍了慕课网iOS客户端在3.3版本中实现接口数据缓存的过程,包括定制的两种方案、数据存储方式、管理方法以及业务逻辑。重点讲述了如何通过缓存优化数据请求粒度、节省用户流量、减轻服务器压力,并在不同场景下的应用效果。同时,分享了在实现过程中遇到的技术挑战和解决方案。

我们开始一个比较重要的功能之前要好好考虑下这块功能是否能解决我们用户当前亟需解决的且是和公司产品大方向一致,比如这次公司选择在小版本解决缓存问题,也是从为下个大版本数据缓存做铺垫,这次主要讲一下慕课网iOS客户端,接口缓存方案。首先,看一下我们实现缓存后的效果(gif图压缩丢了很多帧,所以看起来有卡顿,视频全屏是由于QuickTime录像设备未旋转造成的错误效果)

接口数据缓存效果
接口数据缓存效果

imooc 慕课网iPhone3.3终于做了接口数据缓存,以前一直没时间处理这一块,等到开发缓存时,发现我们需要重构很多东西,而3.2~3.3的开发时间也就10天时间,方案定制,接口沟通,基本都是耗时的事情。
我们定制了以下几个方案:

方案1.每个接口拆成两个(v1,v2),加一个version=1~n。

客户端请求携带version的序号,让服务器增量的返回数据需要变化的部分,优化数据请求粒度,大大减少用户流量,提供良好的用户体验,减轻服务器压力。请求流程如下:

方案流程1
方案流程1
方案2.需要缓存的接口携带hashCode参数

服务器比对hashCode是否有更新,若更新了返回当前接口所有数据,未更新返回空。实现流程图如下:

方案流程2
方案流程2

其实从以上两个方案中可以看出,方案二是方案一的一个缩略版,我们最终选择的第二种方案(关键服务器目前无法对接口做到方案一的多版本(version)的cache,服务器只能做到一个cache)。其实也有客户端的一些考虑,时间成本,最优原则(最短时间内做到最优的功能)。
缓存数据怎么存储?数据关系怎么管理?怎么对接到以前的业务中去?头疼!!!好像第三方挺多的,怎么选型?
哎,开始动工:

1.选型:

a.数据存储用TMCache(对比了几款第三方,在数据存储这一TMCache还是表现挺不错的,TMCache是线程安全的)

b.数据管理方法

sqllite?CoreData? oh,No!!! 想来想去,用MD5一下数据接口名和参数存储,也省的写啰嗦的sql语句,繁复的去查表。(TMCache is a key/value store designed for persisting temporary objects that are expensive to reproduce)

c.业务逻辑

写一个公用的方法,set一个key,value进去(任何类型),get通过md5计算出接口的key取出,直接使用。TMCache在这一块表现不错,当然你存对象的时候请保证你的对象实现了NSCoding协议,当然许多项目采用比较牛逼的jastormantle作为model层的父类,他们很好地处理你子类的对象序列化问题,当然有其他n多好处。
注:jastor已经有1年多不维护了,如果用jastor用户请尽快切到mantle

缓存方案时序图:

在网络接口层增加MCdataInterfaceCache中间件,用于读写缓存数据,1-9是从本地读出缓存数据,刷新到UI,10-17是同步服务器数据/本地数据,保证本地数据是最新的。

缓存时序图
缓存时序图

这边我TMCache做了一些基本的测试(4个线程不停的读写一个序列化后64kb大小的对象),通过Instruments测试到Cpu,Memory的影响,基本都是很稳定的。

这个主要是在读写缓存时cpu,memory的占用比:

cpu,memory
cpu,memory
正常模式下
正常模式下
4个异步线程并行读写64kb对象时cpu占用
4个异步线程并行读写64kb对象时cpu占用
I/O读写能力,4个异步线程,红色部分是播放视频时产生的,跟AVPlayer有关
I/O读写能力,4个异步线程,红色部分是播放视频时产生的,跟AVPlayer有关
此版本对慕课网iPhone版的意义:

1.秒开列表,渐进式替换数据
2.节省用户流量,减少服务器成本
3.恶劣网络环境下,有很好的体验。
4.全部课程->课程支持离线数据展示

流程定了,技术选型也定了,代码实现:
/  MCDataInterfaceCache.h
//  imooc_ios
// 数据接口缓存类
//  Created by mac on 15/5/6.
//  Copyright (c) 2015年 Beijing Mooc Technology Center ltd. All rights reserved.
//

#import 
#import "MCDataCacheHeader.h"
/**
*  缓存层执行结果
*
*  @param result         结果信息
*  @param responseObject 成功-返回网络数据 失败-返回nil
*
*  @return void
*/

typedef void(^CacheBackBlock)(NSString *hashcode, id responseObject,MCServiceResult *result);
@interface MCDataInterfaceCache : NSObject
+(id)sharedInstance;
/**
 * 取出缓存数据
 */
-(BOOL)getCacheByCacheKey:(NSString*)cacheKey parameters:(NSMutableDictionary*)parameters uid:(MCLong)uid resultBack:(CacheBackBlock)resultBack;
/**
 * 保存缓存数据
 */
-(void)setCacheByCacheKey:(NSString*)cacheKey parameters:(NSMutableDictionary*)parameters uid:(MCLong)uid data:(id)data hashCode:(NSString*)hashCode;


@end
#import "MCDataInterfaceCache.h"
static MCDataInterfaceCache *dataInterfaceCache;
@implementation MCDataInterfaceCache
+(id)sharedInstance{
    if (nil==dataInterfaceCache) {
        dataInterfaceCache = [[MCDataInterfaceCache alloc] init];
    }
    return dataInterfaceCache;
}

-(BOOL)getCacheByCacheKey:(NSString *)cacheKey parameters:(NSMutableDictionary*)parameters uid:(MCLong)uid resultBack:(CacheBackBlock)resultBack{
    id data = nil;
    NSString *hashcode = nil;
    if (cacheKey==nil||cacheKey.length0)||(![data isKindOfClass:[NSArray class]]&&data!=nil)) {
        return YES;
    }
    return NO;
}

-(void)setCacheByCacheKey:(NSString *)cacheKey parameters:(NSMutableDictionary*)parameters uid:(MCLong)uid data:(id)data hashCode:(NSString *)hashCode{
    NSString *key = [dataInterfaceCache arrJoint2str2MD5Key:parameters key:cacheKey prestr:uid];
    //数据存储
    [MCCache setobject:data forKey:key];
    //hashcode存储(防止一些服务器返回空导致本地hashcode清除,本地数据需重新请求)
    if (hashCode) {
        [MCCache setobject:hashCode forKey:[NSString stringWithFormat:@"%@hash",key]];
    }
#if MCDCache
    NSLog(@"*****keys= %@",key);
#endif
}

#pragma mark - str connect
-(NSString*)arrJoint2str2MD5Key:(NSMutableDictionary*)parameters key:(NSString*)key prestr:(MCLong)uid{
    NSMutableString *endKey = [[NSMutableString alloc] initWithString:key];

    NSMutableDictionary *parmetersTM = [[NSMutableDictionary alloc] initWithDictionary:parameters];
    [parmetersTM removeObjectForKey:@"token"];
    [parmetersTM removeObjectForKey:@"IMid"]; //hashcode ?
    [parmetersTM removeObjectForKey:@"uid"];

    NSArray *keyArr = [parmetersTM allKeys];
    NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch|NSNumericSearch|
    NSWidthInsensitiveSearch|NSForcedOrderingSearch;
    NSComparator sort = ^(NSString *obj1,NSString *obj2){
        NSRange range = NSMakeRange(0,obj1.length);
        return [obj1 compare:obj2 options:comparisonOptions range:range];
    };
    NSArray *keyArrResult = [keyArr sortedArrayUsingComparator:sort];
    for(NSString *pKey in keyArrResult){
        id obj = parmetersTM[pKey];
        [endKey appendFormat:@"%@%@",pKey,obj];
    }
#if MCDCache
    NSLog(@"字符串数组排序结果%@",keyArrResult);
    NSLog(@"%@",endKey);
#endif
    NSString *convertkeyMD5 = [MCEncryption md5_16:endKey];
    NSString *lastkey = [NSString stringWithFormat:@"%ld_%@",uid,convertkeyMD5];
    return lastkey;

}
@end
———————一些代码中遇到的坑------------
1.万恶的有的对象没有实现NSCoding协议

(虽然项目用了jastor做位模型类的基类,但项目交接人手过多之后,会有很多程序员自己的代码风范,桀骜不驯,比如MCTime没有集成自jastor,没有序列话,正好当时debug模式报pods的一些错误,导致花了一段功夫找到这个脱离项目的模型类)这儿正好说一下pods编译的坑吧,pods虽然方便了第三方的管理,但也会给我们带来一些难以管理的编译错误,慕课网项目是封装了UI层,数据层,和pods管理的第三方,编译一个target项目时,并不会从pods到ui层,数据层全部重新编译一次,会仍然使用之前编译好的.a库,这样导致一些封装的一些库层和pods层的错误)(具体编译顺序没有亲测)。这边给出的解决方法是,分别clear.a类库,然后分别编译,再编译target项目,这样基本会正常。

2.万恶的token【参数时有时无】

在MD5key值时,传了一个带设备ID的字典过去,我们再生成设备标示是不稳定的,所以一些参数需要过滤掉,比如这个token。

4.万恶的参数命名

[mutableParams setObject:hashcode forKey:@"IMid”]; IMid你打死不会想到是回传的hashcode... 代码写多了,能看到各种笑话。

5.万恶的线程同步

主线程刷新数据到UI,异步线程去跑数据存储(有朋友会说你的数据存储没有开线程,嗯,我没开,TMCache有做,现在的项目想做真的比以前什么都自己写方便多了,或许过不了多长时间,我们写代码只是集成模块)

6.万恶的字典在其他地方被修改

【在MD5的时候我传了一个可变字典过来,可是这个可变字典我在后面是需要使用,我却在MD5时,过滤了一些乱七八糟的项,导致接口参数不全无法请求到数据】注:一般我们方法调用传参时,若后续多处需要的数据是不同的,尽量做数据的拷贝,而非多指针指向同一个对象。

各位若有好的建议,请联系我,若思路,流程有问题,请联系我,我会第一时间去修正,感谢!


转载:http://www.jianshu.com/p/8a4dc775c051


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值