Added LRU cache to fix out of memory issues with color caching

This commit is contained in:
Nick Lockwood 2015-07-13 08:42:32 -07:00
parent 4beef5bec3
commit 01151f8c7a
17 changed files with 687 additions and 28 deletions

View File

@ -12,6 +12,8 @@
1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; };
134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1344545A1AAFCAAE003F0779 /* libRCTAdSupport.a */; };
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; };
138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A151B53CD440074A87E /* RCTCacheTests.m */; };
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */; };
139FDEDB1B0651FB00C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
@ -159,6 +161,8 @@
134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = "<group>"; };
134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = "<group>"; };
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; };
138D6A151B53CD440074A87E /* RCTCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCacheTests.m; sourceTree = "<group>"; };
138D6A161B53CD440074A87E /* RCTShadowViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowViewTests.m; sourceTree = "<group>"; };
139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* UIExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = UIExplorer/AppDelegate.h; sourceTree = "<group>"; };
@ -349,8 +353,10 @@
1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */,
1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */,
1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */,
138D6A161B53CD440074A87E /* RCTShadowViewTests.m */,
1497CFAA1B21F5E400C1F8F2 /* RCTSparseArrayTests.m */,
1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */,
138D6A151B53CD440074A87E /* RCTCacheTests.m */,
143BC57E1B21E18100462512 /* Info.plist */,
14D6D7101B220EB3001FB087 /* libOCMock.a */,
14D6D7011B220AE3001FB087 /* OCMock */,
@ -782,7 +788,9 @@
1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */,
1497CFB11B21F5E400C1F8F2 /* RCTEventDispatcherTests.m in Sources */,
1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */,
138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */,
1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */,
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -1,4 +1,16 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>

View File

@ -1,4 +1,16 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>

View File

@ -0,0 +1,168 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <XCTest/XCTest.h>
#import "RCTCache.h"
// Silence silly sign warnings when using int literals
#pragma clang diagnostic ignored "-Wsign-compare"
@interface RCTCache (Private)
- (void)cleanUpAllObjects;
- (void)resequence;
- (NSDictionary *)cache;
- (void)setSequenceNumber:(NSInteger)number;
@end
@interface RCTCacheTests : XCTestCase
@property (nonatomic, strong) RCTCache *cache;
@end
@implementation RCTCacheTests
- (void)setUp
{
self.cache = [[RCTCache alloc] init];
self.cache.countLimit = 3;
self.cache.totalCostLimit = 100;
}
- (void)tearDown
{
self.cache = nil;
}
- (void)testInsertion
{
[self.cache setObject:@1 forKey:@"foo" cost:1];
[self.cache setObject:@2 forKey:@"bar" cost:2];
[self.cache setObject:@3 forKey:@"baz" cost:3];
XCTAssertEqual([self.cache count], 3);
XCTAssertEqual([self.cache totalCost], 6);
}
- (void)testRemoval
{
[self.cache setObject:@1 forKey:@"foo" cost:1];
[self.cache setObject:@2 forKey:@"bar" cost:2];
[self.cache setObject:@3 forKey:@"baz" cost:3];
[self.cache removeObjectForKey:@"bar"];
XCTAssertEqual([self.cache count], 2);
XCTAssertNil([self.cache objectForKey:@"bar"]);
}
- (void)testCountEviction
{
[self.cache setObject:@1 forKey:@"foo"];
[self.cache setObject:@2 forKey:@"bar"];
[self.cache setObject:@3 forKey:@"baz"];
[self.cache setObject:@4 forKey:@"bam"];
XCTAssertEqual([self.cache count], 3);
XCTAssertNil([self.cache objectForKey:@"foo"]);
[self.cache setObject:@5 forKey:@"boo"];
XCTAssertEqual([self.cache count], 3);
XCTAssertNil([self.cache objectForKey:@"bar"]);
}
- (void)testCostEviction
{
[self.cache setObject:@1 forKey:@"foo" cost:99];
[self.cache setObject:@2 forKey:@"bar" cost:2];
XCTAssertEqual([self.cache count], 1);
XCTAssertEqual([self.cache totalCost], 2);
XCTAssertNil([self.cache objectForKey:@"foo"]);
[self.cache setObject:@3 forKey:@"baz" cost:999];
XCTAssertEqual([self.cache count], 0);
XCTAssertEqual([self.cache totalCost], 0);
}
- (void)testCleanup
{
[self.cache setObject:@1 forKey:@"foo"];
[self.cache setObject:@2 forKey:@"bar"];
[self.cache setObject:@3 forKey:@"baz"];
//simulate memory warning
[self.cache cleanUpAllObjects];
XCTAssertEqual([self.cache count], 0);
XCTAssertEqual([self.cache totalCost], 0);
}
- (void)testResequence
{
[self.cache setObject:@1 forKey:@"foo"];
[self.cache setObject:@2 forKey:@"bar"];
[self.cache setObject:@3 forKey:@"baz"];
[self.cache resequence];
NSDictionary *innerCache = [self.cache cache];
XCTAssertEqualObjects([innerCache[@"foo"] valueForKey:@"sequenceNumber"], @0);
XCTAssertEqualObjects([innerCache[@"bar"] valueForKey:@"sequenceNumber"], @1);
XCTAssertEqualObjects([innerCache[@"baz"] valueForKey:@"sequenceNumber"], @2);
[self.cache removeObjectForKey:@"foo"];
[self.cache resequence];
XCTAssertEqualObjects([innerCache[@"bar"] valueForKey:@"sequenceNumber"], @0);
XCTAssertEqualObjects([innerCache[@"baz"] valueForKey:@"sequenceNumber"], @1);
}
- (void)testResequenceTrigger
{
[self.cache setObject:@1 forKey:@"foo"];
[self.cache setObject:@2 forKey:@"bar"];
//first object should now be bar with sequence number of 1
[self.cache removeObjectForKey:@"foo"];
//should trigger resequence
[self.cache setSequenceNumber:NSIntegerMax];
[self.cache setObject:@3 forKey:@"baz"];
NSDictionary *innerCache = [self.cache cache];
XCTAssertEqualObjects([innerCache[@"bar"] valueForKey:@"sequenceNumber"], @0);
XCTAssertEqualObjects([innerCache[@"baz"] valueForKey:@"sequenceNumber"], @1);
//first object should now be baz with sequence number of 1
[self.cache removeObjectForKey:@"bar"];
//should also trigger resequence
[self.cache setSequenceNumber:NSIntegerMax];
[self.cache objectForKey:@"baz"];
XCTAssertEqualObjects([innerCache[@"baz"] valueForKey:@"sequenceNumber"], @0);
}
- (void)testName
{
self.cache.name = @"Hello";
XCTAssertEqualObjects(self.cache.name, @"Hello");
}
@end

View File

@ -1,4 +1,16 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <mach/mach_time.h>

View File

@ -1,4 +1,16 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <XCTest/XCTest.h>

View File

@ -1,4 +1,16 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <XCTest/XCTest.h>
@ -174,7 +186,7 @@
RCTAssertEqualFonts(expected, result);
}
{
UIFont *expected = [UIFont boldSystemFontOfSize:14];
UIFont *expected = [UIFont systemFontOfSize:14 weight:UIFontWeightBold];
UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"foobar", @"fontWeight": @"bold"}];
RCTAssertEqualFonts(expected, result);
}

View File

@ -1,12 +1,15 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <UIKit/UIKit.h>

View File

@ -1,4 +1,16 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <XCTest/XCTest.h>

View File

@ -1,4 +1,16 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <XCTest/XCTest.h>

View File

@ -1,4 +1,16 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <XCTest/XCTest.h>

View File

@ -62,7 +62,7 @@ static void *const RCTDownloadTaskWrapperProgressBlockKey = (void *)&RCTDownload
- (NSURLSessionDownloadTask *)downloadData:(NSURL *)url progressBlock:(RCTDataProgressBlock)progressBlock completionBlock:(RCTDataCompletionBlock)completionBlock
{
NSURLSessionDownloadTask *task = [_URLSession downloadTaskWithURL:url completionHandler:nil];
NSURLSessionDownloadTask *task = [_URLSession downloadTaskWithURL:url];
task.rct_completionBlock = completionBlock;
task.rct_progressBlock = progressBlock;

View File

@ -105,16 +105,15 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
}];
NSCachedURLResponse *cachedResponse = [_cache cachedResponseForRequest:request];
if (cancelled) {
return;
}
if (cachedResponse) {
runBlocks(YES, cachedResponse.data, nil);
} else {
[task resume];
}
if (cancelled) {
return;
}
if (cachedResponse) {
runBlocks(YES, cachedResponse.data, nil);
} else {
[task resume];
}
}
});

51
React/Base/RCTCache.h Normal file
View File

@ -0,0 +1,51 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
/**
* RCTCache is a simple LRU cache implementation, based on the API of NSCache,
* but with known, deterministic behavior. The cache will always remove items
* outside of the specified cost/count limits, and will be automatically
* cleared in the event of a memory warning.
*/
@interface RCTCache : NSCache
/**
* The total number of objects currently resident in the cache.
*/
@property (nonatomic, readonly) NSUInteger count;
/**
* The total cost of the objects currently resident in the cache.
*/
@property (nonatomic, readonly) NSUInteger totalCost;
/**
* Subscripting support
*/
- (id)objectForKeyedSubscript:(id<NSCopying>)key;
- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key;
@end
@protocol RCTCacheDelegate <NSCacheDelegate>
@optional
/**
* Should the specified object be evicted from the cache?
*/
- (BOOL)cache:(RCTCache *)cache shouldEvictObject:(id)entry;
/**
* The specified object is about to be evicted from the cache.
*/
- (void)cache:(RCTCache *)cache willEvictObject:(id)entry;
@end

326
React/Base/RCTCache.m Normal file
View File

@ -0,0 +1,326 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// Adapted from https://github.com/nicklockwood/OSCache
#import "RCTCache.h"
#import "RCTAssert.h"
#import <TargetConditionals.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#endif
@interface RCTCacheEntry : NSObject
@property (nonatomic, strong) NSObject *object;
@property (nonatomic, assign) NSUInteger cost;
@property (nonatomic, assign) NSInteger sequenceNumber;
@end
@implementation RCTCacheEntry
+ (instancetype)entryWithObject:(id)object cost:(NSUInteger)cost sequenceNumber:(NSInteger)sequenceNumber
{
RCTCacheEntry *entry = [[self alloc] init];
entry.object = object;
entry.cost = cost;
entry.sequenceNumber = sequenceNumber;
return entry;
}
@end
@interface RCTCache_Private : NSObject
@property (nonatomic, unsafe_unretained) id<RCTCacheDelegate> delegate;
@property (nonatomic, assign) NSUInteger countLimit;
@property (nonatomic, assign) NSUInteger totalCostLimit;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSUInteger totalCost;
@property (nonatomic, strong) NSMutableDictionary *cache;
@property (nonatomic, assign) BOOL delegateRespondsToWillEvictObject;
@property (nonatomic, assign) BOOL delegateRespondsToShouldEvictObject;
@property (nonatomic, assign) BOOL currentlyCleaning;
@property (nonatomic, assign) NSInteger sequenceNumber;
@property (nonatomic, strong) NSLock *lock;
@end
@implementation RCTCache_Private
- (instancetype)init
{
if ((self = [super init]))
{
//create storage
_cache = [[NSMutableDictionary alloc] init];
_lock = [[NSLock alloc] init];
_totalCost = 0;
#if TARGET_OS_IPHONE
//clean up in the event of a memory warning
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanUpAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)setDelegate:(id<RCTCacheDelegate>)delegate
{
_delegate = delegate;
_delegateRespondsToShouldEvictObject = [delegate respondsToSelector:@selector(cache:shouldEvictObject:)];
_delegateRespondsToWillEvictObject = [delegate respondsToSelector:@selector(cache:willEvictObject:)];
}
- (void)setCountLimit:(NSUInteger)countLimit
{
[_lock lock];
_countLimit = countLimit;
[_lock unlock];
[self cleanUp];
}
- (void)setTotalCostLimit:(NSUInteger)totalCostLimit
{
[_lock lock];
_totalCostLimit = totalCostLimit;
[_lock unlock];
[self cleanUp];
}
- (NSUInteger)count
{
return [_cache count];
}
- (void)cleanUp
{
[_lock lock];
NSUInteger maxCount = [self countLimit] ?: INT_MAX;
NSUInteger maxCost = [self totalCostLimit] ?: INT_MAX;
NSUInteger totalCount = [_cache count];
if (totalCount > maxCount || _totalCost > maxCost)
{
//sort, oldest first
NSArray *keys = [[_cache allKeys] sortedArrayUsingComparator:^NSComparisonResult(id key1, id key2) {
RCTCacheEntry *entry1 = self.cache[key1];
RCTCacheEntry *entry2 = self.cache[key2];
return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber));
}];
//remove oldest items until within limit
for (id key in keys)
{
if (totalCount <= maxCount && _totalCost <= maxCost)
{
break;
}
RCTCacheEntry *entry = _cache[key];
if (!_delegateRespondsToShouldEvictObject || [self.delegate cache:(RCTCache *)self shouldEvictObject:entry])
{
if (_delegateRespondsToWillEvictObject)
{
_currentlyCleaning = YES;
[self.delegate cache:(RCTCache *)self willEvictObject:entry];
_currentlyCleaning = NO;
}
[_cache removeObjectForKey:key];
_totalCost -= entry.cost;
totalCount --;
}
}
}
[_lock unlock];
}
- (void)cleanUpAllObjects
{
[_lock lock];
if (_delegateRespondsToShouldEvictObject || _delegateRespondsToWillEvictObject)
{
NSArray *keys = [_cache allKeys];
if (_delegateRespondsToShouldEvictObject)
{
//sort, oldest first (in case we want to use that information in our eviction test)
keys = [keys sortedArrayUsingComparator:^NSComparisonResult(id key1, id key2) {
RCTCacheEntry *entry1 = self.cache[key1];
RCTCacheEntry *entry2 = self.cache[key2];
return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber));
}];
}
//remove all items individually
for (id key in keys)
{
RCTCacheEntry *entry = _cache[key];
if (!_delegateRespondsToShouldEvictObject || [self.delegate cache:(RCTCache *)self shouldEvictObject:entry])
{
if (_delegateRespondsToWillEvictObject)
{
_currentlyCleaning = YES;
[self.delegate cache:(RCTCache *)self willEvictObject:entry];
_currentlyCleaning = NO;
}
[_cache removeObjectForKey:key];
_totalCost -= entry.cost;
}
}
}
else
{
_totalCost = 0;
[_cache removeAllObjects];
_sequenceNumber = 0;
}
[_lock unlock];
}
- (void)resequence
{
//sort, oldest first
NSArray *entries = [[_cache allValues] sortedArrayUsingComparator:^NSComparisonResult(RCTCacheEntry *entry1, RCTCacheEntry *entry2) {
return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber));
}];
//renumber items
NSInteger index = 0;
for (RCTCacheEntry *entry in entries)
{
entry.sequenceNumber = index++;
}
}
- (id)objectForKey:(id<NSCopying>)key
{
[_lock lock];
RCTCacheEntry *entry = _cache[key];
entry.sequenceNumber = _sequenceNumber++;
if (_sequenceNumber < 0)
{
[self resequence];
}
id object = entry.object;
[_lock unlock];
return object;
}
- (id)objectForKeyedSubscript:(id<NSCopying>)key
{
return [self objectForKey:key];
}
- (void)setObject:(id)obj forKey:(id<NSCopying>)key
{
[self setObject:obj forKey:key cost:0];
}
- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key
{
[self setObject:obj forKey:key cost:0];
}
- (void)setObject:(id)obj forKey:(id<NSCopying>)key cost:(NSUInteger)g
{
RCTAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
[_lock lock];
_totalCost -= [_cache[key] cost];
_totalCost += g;
_cache[key] = [RCTCacheEntry entryWithObject:obj cost:g sequenceNumber:_sequenceNumber++];
if (_sequenceNumber < 0)
{
[self resequence];
}
[_lock unlock];
[self cleanUp];
}
- (void)removeObjectForKey:(id<NSCopying>)key
{
RCTAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
[_lock lock];
_totalCost -= [_cache[key] cost];
[_cache removeObjectForKey:key];
[_lock unlock];
}
- (void)removeAllObjects
{
RCTAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
[_lock lock];
_totalCost = 0;
_sequenceNumber = 0;
[_cache removeAllObjects];
[_lock unlock];
}
//handle unimplemented methods
- (BOOL)isKindOfClass:(Class)cls
{
//pretend that we're an RCTCache if anyone asks
if (cls == [RCTCache class] || cls == [NSCache class])
{
return YES;
}
return [super isKindOfClass:cls];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
//protect against calls to unimplemented NSCache methods
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if (!signature)
{
signature = [NSCache instanceMethodSignatureForSelector:selector];
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
[invocation invokeWithTarget:nil];
#pragma clang diagnostic pop
}
@end
@implementation RCTCache
@dynamic count;
@dynamic totalCost;
+ (instancetype)alloc
{
return (RCTCache *)[RCTCache_Private alloc];
}
- (id)objectForKeyedSubscript:(__unused NSNumber *)key
{
return nil;
}
- (void)setObject:(__unused id)obj forKeyedSubscript:(__unused id<NSCopying>)key {}
@end

View File

@ -11,6 +11,7 @@
#import <objc/message.h>
#import "RCTCache.h"
#import "RCTDefines.h"
@implementation RCTConvert
@ -387,10 +388,11 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
+ (UIColor *)UIColor:(id)json
{
// Check color cache
static NSMutableDictionary *colorCache = nil;
static RCTCache *colorCache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
colorCache = [[NSMutableDictionary alloc] init];
colorCache = [[RCTCache alloc] init];
colorCache.countLimit = 1024;
});
UIColor *color = colorCache[json];
if (color) {

View File

@ -23,6 +23,7 @@
137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E21AA5CF210034F82E /* RCTTabBarItem.m */; };
137327E91AA5CF210034F82E /* RCTTabBarItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */; };
137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E61AA5CF210034F82E /* RCTTabBarManager.m */; };
138D6A141B53CD290074A87E /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A131B53CD290074A87E /* RCTCache.m */; };
13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */; };
13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AF20441AE707F9005F5298 /* RCTSlider.m */; };
13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FE81A69327A00A75B9A /* RCTAlertManager.m */; };
@ -125,6 +126,8 @@
137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarItemManager.m; sourceTree = "<group>"; };
137327E51AA5CF210034F82E /* RCTTabBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTabBarManager.h; sourceTree = "<group>"; };
137327E61AA5CF210034F82E /* RCTTabBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarManager.m; sourceTree = "<group>"; };
138D6A121B53CD290074A87E /* RCTCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCache.h; sourceTree = "<group>"; };
138D6A131B53CD290074A87E /* RCTCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCache.m; sourceTree = "<group>"; };
13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTKeyCommands.h; sourceTree = "<group>"; };
13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTKeyCommands.m; sourceTree = "<group>"; };
13AF1F851AE6E777005F5298 /* RCTDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDefines.h; sourceTree = "<group>"; };
@ -407,6 +410,8 @@
83CBBA491A601E3B00E9B192 /* Base */ = {
isa = PBXGroup;
children = (
138D6A121B53CD290074A87E /* RCTCache.h */,
138D6A131B53CD290074A87E /* RCTCache.m */,
83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */,
83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */,
14C2CA771B3ACB0400E6CBB2 /* RCTBatchedBridge.m */,
@ -599,6 +604,7 @@
1403F2B31B0AE60700C2A9A4 /* RCTPerfStats.m in Sources */,
83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */,
13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */,
138D6A141B53CD290074A87E /* RCTCache.m in Sources */,
13B0801B1A69489C00A75B9A /* RCTNavigatorManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;