Skip to content

Commit fb8ba3f

Browse files
zhongwuzwfacebook-github-bot
authored andcommitted
Enhance image freshness check before stored into cache (#23226)
Summary: This is a re-submit of D13895627 which got landed but didn't include a fix to Instagram's code. The sheriffs were unsure how it got landed without running the build. Currently, before we store the image to cache, we only respect `Cache-Control`, actually, we also may need to check `Expires`、`Last-Modified`, refer to [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#Freshness), and [okhttp](https://github.com/square/okhttp/blob/568a91c44a118b2c2ba62d310a331582c567b24a/okhttp/src/main/java/okhttp3/internal/cache/CacheStrategy.java#L268) respect the `MDN`, so in iOS, we can also respect this. [iOS] [Fixed] - Respect `MDN` cache strategy before cache the image. Reviewed By: shergin Differential Revision: D13896822 Pulled By: cpojer fbshipit-source-id: 8c1714f4a17ad40496146806cff3e188a60be93c
1 parent a4840e7 commit fb8ba3f

File tree

3 files changed

+56
-47
lines changed

3 files changed

+56
-47
lines changed

Libraries/Image/RCTImageCache.m

+38-22
Original file line numberDiff line numberDiff line change
@@ -106,33 +106,49 @@ - (void)addImageToCache:(UIImage *)image
106106
size:(CGSize)size
107107
scale:(CGFloat)scale
108108
resizeMode:(RCTResizeMode)resizeMode
109-
responseDate:(NSString *)responseDate
110-
cacheControl:(NSString *)cacheControl
109+
response:(NSURLResponse *)response
111110
{
112-
NSString *cacheKey = RCTCacheKeyForImage(url, size, scale, resizeMode);
113-
BOOL shouldCache = YES;
114-
NSDate *staleTime;
115-
NSArray<NSString *> *components = [cacheControl componentsSeparatedByString:@","];
116-
for (NSString *component in components) {
117-
if ([component containsString:@"no-cache"] || [component containsString:@"no-store"] || [component hasSuffix:@"max-age=0"]) {
118-
shouldCache = NO;
119-
break;
120-
} else {
121-
NSRange range = [component rangeOfString:@"max-age="];
122-
if (range.location != NSNotFound) {
123-
NSInteger seconds = [[component substringFromIndex:range.location + range.length] integerValue];
124-
NSDate *originalDate = [self dateWithHeaderString:responseDate];
125-
staleTime = [originalDate dateByAddingTimeInterval:(NSTimeInterval)seconds];
111+
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
112+
NSString *cacheKey = RCTCacheKeyForImage(url, size, scale, resizeMode);
113+
BOOL shouldCache = YES;
114+
NSString *responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"];
115+
NSDate *originalDate = [self dateWithHeaderString:responseDate];
116+
NSString *cacheControl = ((NSHTTPURLResponse *)response).allHeaderFields[@"Cache-Control"];
117+
NSDate *staleTime;
118+
NSArray<NSString *> *components = [cacheControl componentsSeparatedByString:@","];
119+
for (NSString *component in components) {
120+
if ([component containsString:@"no-cache"] || [component containsString:@"no-store"] || [component hasSuffix:@"max-age=0"]) {
121+
shouldCache = NO;
122+
break;
123+
} else {
124+
NSRange range = [component rangeOfString:@"max-age="];
125+
if (range.location != NSNotFound) {
126+
NSInteger seconds = [[component substringFromIndex:range.location + range.length] integerValue];
127+
staleTime = [originalDate dateByAddingTimeInterval:(NSTimeInterval)seconds];
128+
}
126129
}
127130
}
128-
}
129-
if (shouldCache) {
130-
if (staleTime) {
131-
@synchronized(_cacheStaleTimes) {
132-
_cacheStaleTimes[cacheKey] = staleTime;
131+
if (shouldCache) {
132+
if (!staleTime && originalDate) {
133+
NSString *expires = ((NSHTTPURLResponse *)response).allHeaderFields[@"Expires"];
134+
NSString *lastModified = ((NSHTTPURLResponse *)response).allHeaderFields[@"Last-Modified"];
135+
if (expires) {
136+
staleTime = [self dateWithHeaderString:expires];
137+
} else if (lastModified) {
138+
NSDate *lastModifiedDate = [self dateWithHeaderString:lastModified];
139+
if (lastModifiedDate) {
140+
NSTimeInterval interval = [originalDate timeIntervalSinceDate:lastModifiedDate] / 10;
141+
staleTime = [originalDate dateByAddingTimeInterval:interval];
142+
}
143+
}
144+
}
145+
if (staleTime) {
146+
@synchronized(_cacheStaleTimes) {
147+
_cacheStaleTimes[cacheKey] = staleTime;
148+
}
133149
}
150+
return [self addImageToCache:image forKey:cacheKey];
134151
}
135-
return [self addImageToCache:image forKey:cacheKey];
136152
}
137153
}
138154

Libraries/Image/RCTImageLoader.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ typedef dispatch_block_t RCTImageLoaderCancellationBlock;
3131
size:(CGSize)size
3232
scale:(CGFloat)scale
3333
resizeMode:(RCTResizeMode)resizeMode
34-
responseDate:(NSString *)responseDate
35-
cacheControl:(NSString *)cacheControl;
34+
response:(NSURLResponse *)response;
3635

3736
@end
3837

Libraries/Image/RCTImageLoader.m

+17-23
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest
346346
resizeMode:(RCTResizeMode)resizeMode
347347
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
348348
partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadHandler
349-
completionBlock:(void (^)(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate, NSString *cacheControl))completionBlock
349+
completionBlock:(void (^)(NSError *error, id imageOrData, BOOL cacheResult, NSURLResponse *response))completionBlock
350350
{
351351
{
352352
NSMutableURLRequest *mutableRequest = [request mutableCopy];
@@ -375,7 +375,7 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest
375375
__block atomic_bool cancelled = ATOMIC_VAR_INIT(NO);
376376
// TODO: Protect this variable shared between threads.
377377
__block dispatch_block_t cancelLoad = nil;
378-
void (^completionHandler)(NSError *, id, NSString *, NSString *) = ^(NSError *error, id imageOrData, NSString *fetchDate, NSString *cacheControl) {
378+
void (^completionHandler)(NSError *, id, NSURLResponse *) = ^(NSError *error, id imageOrData, NSURLResponse *response) {
379379
cancelLoad = nil;
380380

381381
// If we've received an image, we should try to set it synchronously,
@@ -385,11 +385,11 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest
385385
// expecting it, and may do expensive post-processing in the callback
386386
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
387387
if (!atomic_load(&cancelled)) {
388-
completionBlock(error, imageOrData, cacheResult, fetchDate, cacheControl);
388+
completionBlock(error, imageOrData, cacheResult, response);
389389
}
390390
});
391391
} else if (!atomic_load(&cancelled)) {
392-
completionBlock(error, imageOrData, cacheResult, fetchDate, cacheControl);
392+
completionBlock(error, imageOrData, cacheResult, response);
393393
}
394394
};
395395

@@ -403,7 +403,7 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest
403403
progressHandler:progressHandler
404404
partialLoadHandler:partialLoadHandler
405405
completionHandler:^(NSError *error, UIImage *image){
406-
completionHandler(error, image, nil, nil);
406+
completionHandler(error, image, nil);
407407
}];
408408
}
409409

@@ -427,7 +427,7 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest
427427
progressHandler:progressHandler
428428
partialLoadHandler:partialLoadHandler
429429
completionHandler:^(NSError *error, UIImage *image) {
430-
completionHandler(error, image, nil, nil);
430+
completionHandler(error, image, nil);
431431
}];
432432
} else {
433433
UIImage *image;
@@ -439,7 +439,7 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest
439439
}
440440

441441
if (image) {
442-
completionHandler(nil, image, nil, nil);
442+
completionHandler(nil, image, nil);
443443
} else {
444444
// Use networking module to load image
445445
cancelLoad = [strongSelf _loadURLRequest:request
@@ -464,7 +464,7 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest
464464

465465
- (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request
466466
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
467-
completionBlock:(void (^)(NSError *error, id imageOrData, NSString *fetchDate, NSString *cacheControl))completionHandler
467+
completionBlock:(void (^)(NSError *error, id imageOrData, NSURLResponse *response))completionHandler
468468
{
469469
// Check if networking module is available
470470
if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]) {
@@ -486,36 +486,31 @@ - (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request
486486
RCTURLRequestCompletionBlock processResponse = ^(NSURLResponse *response, NSData *data, NSError *error) {
487487
// Check for system errors
488488
if (error) {
489-
completionHandler(error, nil, nil, nil);
489+
completionHandler(error, nil, response);
490490
return;
491491
} else if (!response) {
492-
completionHandler(RCTErrorWithMessage(@"Response metadata error"), nil, nil, nil);
492+
completionHandler(RCTErrorWithMessage(@"Response metadata error"), nil, response);
493493
return;
494494
} else if (!data) {
495-
completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil, nil, nil);
495+
completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil, response);
496496
return;
497497
}
498498

499499
// Check for http errors
500-
NSString *responseDate;
501-
NSString *cacheControl;
502500
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
503501
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
504502
if (statusCode != 200) {
505503
NSString *errorMessage = [NSString stringWithFormat:@"Failed to load %@", response.URL];
506504
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: errorMessage};
507505
completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain
508506
code:statusCode
509-
userInfo:userInfo], nil, nil, nil);
507+
userInfo:userInfo], nil, response);
510508
return;
511509
}
512-
513-
responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"];
514-
cacheControl = ((NSHTTPURLResponse *)response).allHeaderFields[@"Cache-Control"];
515510
}
516511

517512
// Call handler
518-
completionHandler(nil, data, responseDate, cacheControl);
513+
completionHandler(nil, data, response);
519514
};
520515

521516
// Download image
@@ -537,7 +532,7 @@ - (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request
537532
} else {
538533
someError = RCTErrorWithMessage(@"Unknown image download error");
539534
}
540-
completionHandler(someError, nil, nil, nil);
535+
completionHandler(someError, nil, response);
541536
[strongSelf dequeueTasks];
542537
return;
543538
}
@@ -603,7 +598,7 @@ - (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)image
603598
};
604599

605600
__weak RCTImageLoader *weakSelf = self;
606-
void (^completionHandler)(NSError *, id, BOOL, NSString *, NSString *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate, NSString *cacheControl) {
601+
void (^completionHandler)(NSError *, id, BOOL, NSURLResponse *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSURLResponse *response) {
607602
__typeof(self) strongSelf = weakSelf;
608603
if (atomic_load(&cancelled) || !strongSelf) {
609604
return;
@@ -623,8 +618,7 @@ - (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)image
623618
size:size
624619
scale:scale
625620
resizeMode:resizeMode
626-
responseDate:fetchDate
627-
cacheControl:cacheControl];
621+
response:response];
628622
}
629623

630624
cancelLoad = nil;
@@ -758,7 +752,7 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
758752
- (RCTImageLoaderCancellationBlock)getImageSizeForURLRequest:(NSURLRequest *)imageURLRequest
759753
block:(void(^)(NSError *error, CGSize size))callback
760754
{
761-
void (^completion)(NSError *, id, BOOL, NSString *, NSString *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate, NSString *cacheControl) {
755+
void (^completion)(NSError *, id, BOOL, NSURLResponse *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSURLResponse *response) {
762756
CGSize size;
763757
if ([imageOrData isKindOfClass:[NSData class]]) {
764758
NSDictionary *meta = RCTGetImageMetadata(imageOrData);

0 commit comments

Comments
 (0)