今日刚上班,就收到了一个比较诡异的问题。
客户反馈,app上面天气界面显示的天气,和他本人从其他app得到的天气温度范围不一致而且差距较大。
获取天气的接口
(http://api.map.baidu.com/telematics/v3/weather) 。赶紧打开Xcode 亲自获取了一下发现我们这边获取的确实不一样。这就纳闷了,百度不该坑我啊,仔细一看,原来得到天气日期是昨天的。但是呢,我在浏览器打开url,获取到的天气又是正常的。这是咋回事呢,容我三思。三思之后,我觉得可能是缓存造成的,但获取天气这里,我们并没有主动缓存天气数据。
因为请求是用AFNetworking,所有就又点开了AFNetworking的源码。
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(method);
NSParameterAssert(URLString);
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
AFHTTPRequestSerializerObservedKeyPaths() 是什么呢?继续看
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
return _AFHTTPRequestSerializerObservedKeyPaths;
}
原来是一个方法的字符串数组。在这里,每一个mutableRequest都会设置一个缓存策略,且有一个默认值, NSURLRequestUseProtocolCachePolicy。
/**
The cache policy of created requests. `NSURLRequestUseProtocolCachePolicy` by default.
@see NSMutableURLRequest -setCachePolicy:
*/
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
NSURLRequestUseProtocolCachePolicy = 0,
NSURLRequestReloadIgnoringLocalCacheData = 1,
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
NSURLRequestReturnCacheDataElseLoad = 2,
NSURLRequestReturnCacheDataDontLoad = 3,
NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented
};
分别看看这些缓存代表什么意思呢?
- NSURLRequestUseProtocolCachePolicy: 对特定的 URL 请求使用网络协议中实现的缓存逻辑。这是默认的策略。
- NSURLRequestReloadIgnoringLocalCacheData:数据需要从原始地址加载。不使用现有缓存。
- NSURLRequestReloadIgnoringLocalAndRemoteCacheData:不仅忽略本地缓存,同时也忽略代理服务器或其他中间介质目前已有的、协议允许的缓存。
- NSURLRequestReturnCacheDataElseLoad:无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么从原始地址加载数据。
- NSURLRequestReturnCacheDataDontLoad:无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么放弃从原始地址加载数据,请求视为失败(即:“离线”模式)。
- NSURLRequestReloadRevalidatingCacheData:从原始地址确认缓存数据的合法性后,缓存数据就可以使用,否则从原始地址加载。
此前我一直还认为NSURLCache不就是内存缓存嘛,好办,app关掉重新打开就可以了吧。结果,涛声依旧,还是有缓存。
继续思考,我决定找找这个神秘的幕后黑手。
接下来,我就找了下NSURLCache 的属性,发现了currentDiskUsage,diskCapacity 这个2个属性,难道NSURLCache 还有磁盘缓存?
继续仔细探索,发现这段文字。
NSURLCache 为您的应用的 URL 请求提供了内存中以及磁盘上的综合缓存机制。 作为基础类库 URL 加载系统 的一部分,任何通过 NSURLConnection 加载的请求都将被 NSURLCache 处理。
真是既有内存缓存又有磁盘缓存。接下来,我就打印并观察currentDiskUsage、currentMemoryUsage 这2个值。
NSURLCache * cache =[NSURLCache sharedURLCache];
NSLog(@"cache.currentDiskUsage: %ld" ,(unsigned long)cache.currentDiskUsage);
NSLog(@"cache.currentMemoryUsage: %ld" ,(unsigned long)cache.currentMemoryUsage);
过程中,发现memoryCapacity 默认值512KB currentMemoryUsage 默认值10MB,可以自己设置大小。 app杀掉重新打开currentMemoryUsage的确会为0。使用过程中currentMemoryUsage 会慢慢增加,操作了好一阵才到10KB左右,而currentDiskUsage增加非常凶猛,已经到了100多KB。
接着我就想,currentDiskUsage这货在在哪呢,难道是在沙盒中那个Caches中某个黑暗的小角落。
⇡
果然,在这里看到了这个不起眼确似曾相识的货,Cache.db,然后顺便把这货砸开看看。
看着很有料吧,继续爆。
⇡
东西果然不少呢,其中的request_key就是你app中使用请求的url,还包括一些第三方库的请求。
⇡
请求返回的数据全在这里了。
最后,知道了问题出在哪里,就好改bug了。
afManager.requestSerializer.cachePolicy=NSURLRequestReloadIgnoringLocalCacheData;
改完以后,数据果然是最新的了,但是为毛和浏览器中的天气数据还不一样呢?
Safari->开发->清空缓存 重新加载 ,这下都一样了!
如果你的请求实时性相对较高,而且每次url一模一样(参数一成不变) 最好果断地禁止使用缓存。