Authored by Thomas Rasch

o Changed the tile and memory cache to use dispatch queues

@@ -30,10 +30,6 @@ @@ -30,10 +30,6 @@
30 #import "RMTileCache.h" 30 #import "RMTileCache.h"
31 31
32 @interface RMMemoryCache : NSObject <RMTileCache> 32 @interface RMMemoryCache : NSObject <RMTileCache>
33 -{  
34 - NSMutableDictionary *cache;  
35 - NSUInteger capacity;  
36 -}  
37 33
38 - (id)initWithCapacity:(NSUInteger)aCapacity; 34 - (id)initWithCapacity:(NSUInteger)aCapacity;
39 35
@@ -29,22 +29,28 @@ @@ -29,22 +29,28 @@
29 #import "RMTileImage.h" 29 #import "RMTileImage.h"
30 30
31 @implementation RMMemoryCache 31 @implementation RMMemoryCache
  32 +{
  33 + NSMutableDictionary *_memoryCache;
  34 + dispatch_queue_t _memoryCacheQueue;
  35 + NSUInteger _memoryCacheCapacity;
  36 +}
32 37
33 - (id)initWithCapacity:(NSUInteger)aCapacity 38 - (id)initWithCapacity:(NSUInteger)aCapacity
34 { 39 {
35 - if (!(self = [super init]))  
36 - return nil; 40 + if (!(self = [super init]))
  41 + return nil;
  42 +
  43 + RMLog(@"initializing memory cache %@ with capacity %d", self, aCapacity);
37 44
38 - RMLog(@"initializing memory cache %@ with capacity %d", self, aCapacity); 45 + _memoryCache = [[NSMutableDictionary alloc] initWithCapacity:aCapacity];
  46 + _memoryCacheQueue = dispatch_queue_create("routeme.memoryCacheQueue", DISPATCH_QUEUE_CONCURRENT);
39 47
40 - cache = [[NSMutableDictionary alloc] initWithCapacity:aCapacity];  
41 -  
42 - if (aCapacity < 1)  
43 - aCapacity = 1; 48 + if (aCapacity < 1)
  49 + aCapacity = 1;
44 50
45 - capacity = aCapacity; 51 + _memoryCacheCapacity = aCapacity;
46 52
47 - return self; 53 + return self;
48 } 54 }
49 55
50 - (id)init 56 - (id)init
@@ -54,11 +60,10 @@ @@ -54,11 +60,10 @@
54 60
55 - (void)dealloc 61 - (void)dealloc
56 { 62 {
57 - @synchronized (cache)  
58 - {  
59 - [cache removeAllObjects];  
60 - [cache release]; cache = nil;  
61 - } 63 + dispatch_barrier_sync(_memoryCacheQueue, ^{
  64 + [_memoryCache removeAllObjects];
  65 + [_memoryCache release]; _memoryCache = nil;
  66 + });
62 67
63 [super dealloc]; 68 [super dealloc];
64 } 69 }
@@ -67,60 +72,67 @@ @@ -67,60 +72,67 @@
67 { 72 {
68 LogMethod(); 73 LogMethod();
69 74
70 - @synchronized (cache)  
71 - {  
72 - [cache removeAllObjects];  
73 - } 75 + dispatch_barrier_async(_memoryCacheQueue, ^{
  76 + [_memoryCache removeAllObjects];
  77 + });
74 } 78 }
75 79
76 - (void)removeTile:(RMTile)tile 80 - (void)removeTile:(RMTile)tile
77 { 81 {
78 - @synchronized (cache)  
79 - {  
80 - [cache removeObjectForKey:[RMTileCache tileHash:tile]];  
81 - } 82 + dispatch_barrier_async(_memoryCacheQueue, ^{
  83 + [_memoryCache removeObjectForKey:[RMTileCache tileHash:tile]];
  84 + });
82 } 85 }
83 86
84 - (UIImage *)cachedImage:(RMTile)tile withCacheKey:(NSString *)aCacheKey 87 - (UIImage *)cachedImage:(RMTile)tile withCacheKey:(NSString *)aCacheKey
85 { 88 {
86 // RMLog(@"Memory cache check tile %d %d %d (%@)", tile.x, tile.y, tile.zoom, [RMTileCache tileHash:tile]); 89 // RMLog(@"Memory cache check tile %d %d %d (%@)", tile.x, tile.y, tile.zoom, [RMTileCache tileHash:tile]);
87 90
88 - RMCacheObject *cachedObject = nil; 91 + __block RMCacheObject *cachedObject = nil;
89 NSNumber *tileHash = [RMTileCache tileHash:tile]; 92 NSNumber *tileHash = [RMTileCache tileHash:tile];
90 93
91 - @synchronized (cache)  
92 - {  
93 - cachedObject = [cache objectForKey:tileHash];  
94 - if (!cachedObject)  
95 - return nil; 94 + dispatch_sync(_memoryCacheQueue, ^{
96 95
97 - if (![[cachedObject cacheKey] isEqualToString:aCacheKey]) 96 + cachedObject = [[_memoryCache objectForKey:tileHash] retain];
  97 +
  98 + if (cachedObject)
98 { 99 {
99 - [cache removeObjectForKey:tileHash];  
100 - return nil; 100 + if ([[cachedObject cacheKey] isEqualToString:aCacheKey])
  101 + {
  102 + [cachedObject touch];
  103 + }
  104 + else
  105 + {
  106 + dispatch_barrier_async(_memoryCacheQueue, ^{
  107 + [_memoryCache removeObjectForKey:tileHash];
  108 + });
  109 +
  110 + [cachedObject release]; cachedObject = nil;
  111 + }
101 } 112 }
102 113
103 - [cachedObject touch];  
104 - } 114 + });
105 115
106 // RMLog(@"Memory cache hit tile %d %d %d (%@)", tile.x, tile.y, tile.zoom, [RMTileCache tileHash:tile]); 116 // RMLog(@"Memory cache hit tile %d %d %d (%@)", tile.x, tile.y, tile.zoom, [RMTileCache tileHash:tile]);
107 117
  118 + [cachedObject autorelease];
  119 +
108 return [cachedObject cachedObject]; 120 return [cachedObject cachedObject];
109 } 121 }
110 122
111 /// Remove the least-recently used image from cache, if cache is at or over capacity. Removes only 1 image. 123 /// Remove the least-recently used image from cache, if cache is at or over capacity. Removes only 1 image.
112 - (void)makeSpaceInCache 124 - (void)makeSpaceInCache
113 { 125 {
114 - @synchronized (cache)  
115 - {  
116 - while ([cache count] >= capacity) 126 + dispatch_barrier_async(_memoryCacheQueue, ^{
  127 +
  128 + while ([_memoryCache count] >= _memoryCacheCapacity)
117 { 129 {
118 // Rather than scanning I would really like to be using a priority queue 130 // Rather than scanning I would really like to be using a priority queue
119 // backed by a heap here. 131 // backed by a heap here.
120 132
121 // Maybe deleting one random element would work as well. 133 // Maybe deleting one random element would work as well.
122 134
123 - NSEnumerator *enumerator = [cache objectEnumerator]; 135 + NSEnumerator *enumerator = [_memoryCache objectEnumerator];
124 RMCacheObject *image; 136 RMCacheObject *image;
125 137
126 NSDate *oldestDate = nil; 138 NSDate *oldestDate = nil;
@@ -138,10 +150,11 @@ @@ -138,10 +150,11 @@
138 if (oldestImage) 150 if (oldestImage)
139 { 151 {
140 // RMLog(@"Memory cache delete tile %d %d %d (%@)", oldestImage.tile.x, oldestImage.tile.y, oldestImage.tile.zoom, [RMTileCache tileHash:oldestImage.tile]); 152 // RMLog(@"Memory cache delete tile %d %d %d (%@)", oldestImage.tile.x, oldestImage.tile.y, oldestImage.tile.zoom, [RMTileCache tileHash:oldestImage.tile]);
141 - [cache removeObjectForKey:[RMTileCache tileHash:oldestImage.tile]]; 153 + [_memoryCache removeObjectForKey:[RMTileCache tileHash:oldestImage.tile]];
142 } 154 }
143 } 155 }
144 - } 156 +
  157 + });
145 } 158 }
146 159
147 - (void)addImage:(UIImage *)image forTile:(RMTile)tile withCacheKey:(NSString *)aCacheKey 160 - (void)addImage:(UIImage *)image forTile:(RMTile)tile withCacheKey:(NSString *)aCacheKey
@@ -150,20 +163,18 @@ @@ -150,20 +163,18 @@
150 163
151 [self makeSpaceInCache]; 164 [self makeSpaceInCache];
152 165
153 - @synchronized (cache)  
154 - {  
155 - [cache setObject:[RMCacheObject cacheObject:image forTile:tile withCacheKey:aCacheKey] forKey:[RMTileCache tileHash:tile]];  
156 - } 166 + dispatch_barrier_async(_memoryCacheQueue, ^{
  167 + [_memoryCache setObject:[RMCacheObject cacheObject:image forTile:tile withCacheKey:aCacheKey] forKey:[RMTileCache tileHash:tile]];
  168 + });
157 } 169 }
158 170
159 - (void)removeAllCachedImages 171 - (void)removeAllCachedImages
160 { 172 {
161 LogMethod(); 173 LogMethod();
162 174
163 - @synchronized (cache)  
164 - {  
165 - [cache removeAllObjects];  
166 - } 175 + dispatch_barrier_async(_memoryCacheQueue, ^{
  176 + [_memoryCache removeAllObjects];
  177 + });
167 } 178 }
168 179
169 @end 180 @end
@@ -58,15 +58,6 @@ typedef enum { @@ -58,15 +58,6 @@ typedef enum {
58 #pragma mark - 58 #pragma mark -
59 59
60 @interface RMTileCache : NSObject <RMTileCache> 60 @interface RMTileCache : NSObject <RMTileCache>
61 -{  
62 - NSMutableArray *caches;  
63 -  
64 - // The memory cache, if we have one  
65 - // This one has its own variable because we want to propagate cache hits down in  
66 - // the cache hierarchy up to the memory cache  
67 - RMMemoryCache *memoryCache;  
68 - NSTimeInterval expiryPeriod;  
69 -}  
70 61
71 - (id)initWithExpiryPeriod:(NSTimeInterval)period; 62 - (id)initWithExpiryPeriod:(NSTimeInterval)period;
72 63
@@ -40,52 +40,65 @@ @@ -40,52 +40,65 @@
40 @end 40 @end
41 41
42 @implementation RMTileCache 42 @implementation RMTileCache
  43 +{
  44 + NSMutableArray *_tileCaches;
  45 +
  46 + // The memory cache, if we have one
  47 + // This one has its own variable because we want to propagate cache hits down in
  48 + // the cache hierarchy up to the memory cache
  49 + RMMemoryCache *_memoryCache;
  50 + NSTimeInterval _expiryPeriod;
  51 +
  52 + dispatch_queue_t _tileCacheQueue;
  53 +}
43 54
44 - (id)initWithExpiryPeriod:(NSTimeInterval)period 55 - (id)initWithExpiryPeriod:(NSTimeInterval)period
45 { 56 {
46 - if (!(self = [super init]))  
47 - return nil; 57 + if (!(self = [super init]))
  58 + return nil;
48 59
49 - caches = [[NSMutableArray alloc] init];  
50 - memoryCache = nil;  
51 - expiryPeriod = period;  
52 -  
53 - id cacheCfg = [[RMConfiguration configuration] cacheConfiguration];  
54 - if (!cacheCfg)  
55 - cacheCfg = [NSArray arrayWithObjects: 60 + _tileCaches = [[NSMutableArray alloc] init];
  61 + _tileCacheQueue = dispatch_queue_create("routeme.tileCacheQueue", DISPATCH_QUEUE_CONCURRENT);
  62 +
  63 + _memoryCache = nil;
  64 + _expiryPeriod = period;
  65 +
  66 + id cacheCfg = [[RMConfiguration configuration] cacheConfiguration];
  67 + if (!cacheCfg)
  68 + cacheCfg = [NSArray arrayWithObjects:
56 [NSDictionary dictionaryWithObject: @"memory-cache" forKey: @"type"], 69 [NSDictionary dictionaryWithObject: @"memory-cache" forKey: @"type"],
57 [NSDictionary dictionaryWithObject: @"db-cache" forKey: @"type"], 70 [NSDictionary dictionaryWithObject: @"db-cache" forKey: @"type"],
58 nil]; 71 nil];
59 72
60 - for (id cfg in cacheCfg)  
61 - {  
62 - id <RMTileCache> newCache = nil; 73 + for (id cfg in cacheCfg)
  74 + {
  75 + id <RMTileCache> newCache = nil;
63 76
64 - @try { 77 + @try {
65 78
66 - NSString *type = [cfg valueForKey:@"type"]; 79 + NSString *type = [cfg valueForKey:@"type"];
67 80
68 - if ([@"memory-cache" isEqualToString:type]) 81 + if ([@"memory-cache" isEqualToString:type])
69 { 82 {
70 - memoryCache = [[self memoryCacheWithConfig:cfg] retain]; 83 + _memoryCache = [[self memoryCacheWithConfig:cfg] retain];
71 continue; 84 continue;
72 } 85 }
73 86
74 - if ([@"db-cache" isEqualToString:type])  
75 - newCache = [self databaseCacheWithConfig:cfg]; 87 + if ([@"db-cache" isEqualToString:type])
  88 + newCache = [self databaseCacheWithConfig:cfg];
76 89
77 - if (newCache)  
78 - [caches addObject:newCache];  
79 - else  
80 - RMLog(@"failed to create cache of type %@", type); 90 + if (newCache)
  91 + [_tileCaches addObject:newCache];
  92 + else
  93 + RMLog(@"failed to create cache of type %@", type);
81 94
82 - }  
83 - @catch (NSException * e) {  
84 - RMLog(@"*** configuration error: %@", [e reason]);  
85 - }  
86 - } 95 + }
  96 + @catch (NSException * e) {
  97 + RMLog(@"*** configuration error: %@", [e reason]);
  98 + }
  99 + }
87 100
88 - return self; 101 + return self;
89 } 102 }
90 103
91 - (id)init 104 - (id)init
@@ -98,17 +111,19 @@ @@ -98,17 +111,19 @@
98 111
99 - (void)dealloc 112 - (void)dealloc
100 { 113 {
101 - [memoryCache release]; memoryCache = nil;  
102 - [caches release]; caches = nil; 114 + dispatch_barrier_sync(_tileCacheQueue, ^{
  115 + [_memoryCache release]; _memoryCache = nil;
  116 + [_tileCaches release]; _tileCaches = nil;
  117 + });
  118 +
103 [super dealloc]; 119 [super dealloc];
104 } 120 }
105 121
106 - (void)addCache:(id <RMTileCache>)cache 122 - (void)addCache:(id <RMTileCache>)cache
107 { 123 {
108 - @synchronized (caches)  
109 - {  
110 - [caches addObject:cache];  
111 - } 124 + dispatch_barrier_async(_tileCacheQueue, ^{
  125 + [_tileCaches addObject:cache];
  126 + });
112 } 127 }
113 128
114 + (NSNumber *)tileHash:(RMTile)tile 129 + (NSNumber *)tileHash:(RMTile)tile
@@ -119,26 +134,27 @@ @@ -119,26 +134,27 @@
119 // Returns the cached image if it exists. nil otherwise. 134 // Returns the cached image if it exists. nil otherwise.
120 - (UIImage *)cachedImage:(RMTile)tile withCacheKey:(NSString *)aCacheKey 135 - (UIImage *)cachedImage:(RMTile)tile withCacheKey:(NSString *)aCacheKey
121 { 136 {
122 - UIImage *image = [memoryCache cachedImage:tile withCacheKey:aCacheKey]; 137 + __block UIImage *image = [_memoryCache cachedImage:tile withCacheKey:aCacheKey];
123 138
124 if (image) 139 if (image)
125 return image; 140 return image;
126 141
127 - @synchronized (caches)  
128 - {  
129 - for (id <RMTileCache> cache in caches) 142 + dispatch_sync(_tileCacheQueue, ^{
  143 +
  144 + for (id <RMTileCache> cache in _tileCaches)
130 { 145 {
131 - image = [cache cachedImage:tile withCacheKey:aCacheKey]; 146 + image = [[cache cachedImage:tile withCacheKey:aCacheKey] retain];
132 147
133 if (image != nil) 148 if (image != nil)
134 { 149 {
135 - [memoryCache addImage:image forTile:tile withCacheKey:aCacheKey];  
136 - return image; 150 + [_memoryCache addImage:image forTile:tile withCacheKey:aCacheKey];
  151 + break;
137 } 152 }
138 } 153 }
139 - }  
140 154
141 - return nil; 155 + });
  156 +
  157 + return [image autorelease];
142 } 158 }
143 159
144 - (void)addImage:(UIImage *)image forTile:(RMTile)tile withCacheKey:(NSString *)aCacheKey 160 - (void)addImage:(UIImage *)image forTile:(RMTile)tile withCacheKey:(NSString *)aCacheKey
@@ -146,43 +162,47 @@ @@ -146,43 +162,47 @@
146 if (!image || !aCacheKey) 162 if (!image || !aCacheKey)
147 return; 163 return;
148 164
149 - [memoryCache addImage:image forTile:tile withCacheKey:aCacheKey]; 165 + [_memoryCache addImage:image forTile:tile withCacheKey:aCacheKey];
150 166
151 - @synchronized (caches)  
152 - {  
153 - for (id <RMTileCache> cache in caches) 167 + dispatch_sync(_tileCacheQueue, ^{
  168 +
  169 + for (id <RMTileCache> cache in _tileCaches)
154 { 170 {
155 if ([cache respondsToSelector:@selector(addImage:forTile:withCacheKey:)]) 171 if ([cache respondsToSelector:@selector(addImage:forTile:withCacheKey:)])
156 [cache addImage:image forTile:tile withCacheKey:aCacheKey]; 172 [cache addImage:image forTile:tile withCacheKey:aCacheKey];
157 } 173 }
158 - } 174 +
  175 + });
159 } 176 }
160 177
161 - (void)didReceiveMemoryWarning 178 - (void)didReceiveMemoryWarning
162 { 179 {
163 LogMethod(); 180 LogMethod();
164 - [memoryCache didReceiveMemoryWarning];  
165 181
166 - @synchronized (caches)  
167 - {  
168 - for (id<RMTileCache> cache in caches) 182 + [_memoryCache didReceiveMemoryWarning];
  183 +
  184 + dispatch_sync(_tileCacheQueue, ^{
  185 +
  186 + for (id<RMTileCache> cache in _tileCaches)
169 { 187 {
170 [cache didReceiveMemoryWarning]; 188 [cache didReceiveMemoryWarning];
171 } 189 }
172 - } 190 +
  191 + });
173 } 192 }
174 193
175 - (void)removeAllCachedImages 194 - (void)removeAllCachedImages
176 { 195 {
177 - [memoryCache removeAllCachedImages]; 196 + [_memoryCache removeAllCachedImages];
178 197
179 - @synchronized (caches)  
180 - {  
181 - for (id<RMTileCache> cache in caches) 198 + dispatch_sync(_tileCacheQueue, ^{
  199 +
  200 + for (id<RMTileCache> cache in _tileCaches)
182 { 201 {
183 [cache removeAllCachedImages]; 202 [cache removeAllCachedImages];
184 } 203 }
185 - } 204 +
  205 + });
186 } 206 }
187 207
188 @end 208 @end
@@ -245,13 +265,13 @@ @@ -245,13 +265,13 @@
245 265
246 NSNumber *expiryPeriodNumber = [cfg objectForKey:@"expiryPeriod"]; 266 NSNumber *expiryPeriodNumber = [cfg objectForKey:@"expiryPeriod"];
247 if (expiryPeriodNumber != nil) 267 if (expiryPeriodNumber != nil)
248 - expiryPeriod = [expiryPeriodNumber intValue]; 268 + _expiryPeriod = [expiryPeriodNumber intValue];
249 269
250 RMDatabaseCache *dbCache = [[[RMDatabaseCache alloc] initUsingCacheDir:useCacheDir] autorelease]; 270 RMDatabaseCache *dbCache = [[[RMDatabaseCache alloc] initUsingCacheDir:useCacheDir] autorelease];
251 [dbCache setCapacity:capacity]; 271 [dbCache setCapacity:capacity];
252 [dbCache setPurgeStrategy:strategy]; 272 [dbCache setPurgeStrategy:strategy];
253 [dbCache setMinimalPurge:minimalPurge]; 273 [dbCache setMinimalPurge:minimalPurge];
254 - [dbCache setExpiryPeriod:expiryPeriod]; 274 + [dbCache setExpiryPeriod:_expiryPeriod];
255 275
256 return dbCache; 276 return dbCache;
257 } 277 }