API  0.9.8
 All Classes Files Functions Variables Typedefs Macros Groups Pages
CPCache.j
Go to the documentation of this file.
1 /*
2  * CPCache.j
3  * Foundation
4  *
5  * Created by William Mura.
6  * Copyright 2015, William Mura.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
25 
26 
27 /*
28  * Delegate CPCacheDelegate
29  *
30  * - cache:willEvictObject: is called when a object is going to be removed
31  * When the total cost or the count exceeds the total cost limit or the count limit
32  * And also when removeObjectForKey or removeAllObjects are called
33  */
35 
36 @optional
37 - (void)cache:(CPCache)cache willEvictObject:(id)obj;
38 
39 @end
40 
42 
43 
58 @implementation CPCache : CPObject
59 {
60  CPDictionary _items;
61  int _currentPosition;
62  BOOL _totalCostCache;
63  unsigned _implementedDelegateMethods;
64 
65  CPString _name;
66  int _countLimit;
67  int _totalCostLimit;
68  id <CPCacheDelegate> _delegate @accessors(property=delegate);
69 }
70 
71 
72 #pragma mark -
73 #pragma mark Initialization
74 
79 - (id)init
80 {
81  if (self = [super init])
82  {
83  _items = [[CPDictionary alloc] init];
84  _currentPosition = 0;
85  _totalCostCache = -1;
86  _implementedDelegateMethods = 0;
87 
88  _name = @"";
89  _countLimit = 0;
90  _totalCostLimit = 0;
91  _delegate = nil;
92  }
93 
94  return self;
95 }
96 
97 
98 #pragma mark -
99 #pragma mark Managing cache
100 
106 - (id)objectForKey:(id)aKey
107 {
108  return [[_items objectForKey:aKey] object];
109 }
110 
116 - (void)setObject:(id)anObject forKey:(id)aKey
117 {
118  [self setObject:anObject forKey:aKey cost:0];
119 }
120 
127 - (void)setObject:(id)anObject forKey:(id)aKey cost:(int)aCost
128 {
129  // Check if the key already exist
130  if ([_items objectForKey:aKey])
131  [self removeObjectForKey:aKey];
132 
133  // Add object
134  [_items setObject:[_CPCacheItem cacheItemWithObject:anObject cost:aCost position:++_currentPosition] forKey:aKey];
135 
136  // Invalid cost cache
137  _totalCostCache = -1;
138 
139  // Clean cache to satisfy condition (< totalCostLimit & < countLimit) if necessary
140  [self _cleanCache];
141 }
142 
147 - (void)removeObjectForKey:(id)aKey
148 {
149  // Call delegate method to warn that the object is going to be removed
150  [self _sendDelegateWillEvictObjectForKey:aKey];
151 
152  // Remove object
153  [_items removeObjectForKey:aKey];
154 
155  // Invalid cost cache
156  _totalCostCache = -1;
157 }
158 
162 - (void)removeAllObjects
163 {
164  // Call delegate method to warn that the objects are going to be removed
165  var enumerator = [_items keyEnumerator],
166  key;
167 
168  while (key = [enumerator nextObject])
169  [self _sendDelegateWillEvictObjectForKey:key]
170 
171  // Remove all objects
172  [_items removeAllObjects];
173 
174  // Invalid cost cache and reset position counter
175  _totalCostCache = -1;
176  _currentPosition = 0;
177 }
178 
179 
180 #pragma mark -
181 #pragma mark Setters
182 
188 - (void)setCountLimit:(int)aCountLimit
189 {
190  _countLimit = aCountLimit;
191  [self _cleanCache];
192 }
193 
199 - (void)setTotalCostLimit:(int)aTotalCostLimit
200 {
201  _totalCostLimit = aTotalCostLimit;
202  [self _cleanCache];
203 }
204 
209 - (void)setDelegate:(id)aDelegate
210 {
211  if (_delegate === aDelegate)
212  return;
213 
214  _delegate = aDelegate;
215  _implementedDelegateMethods = 0;
216 
217  if ([_delegate respondsToSelector:@selector(cache:willEvictObject:)])
218  _implementedDelegateMethods |= CPCacheDelegate_cache_willEvictObject_
219 }
220 
221 
222 #pragma mark -
223 #pragma mark Privates
224 
225 /*
226  * This method return the number of objects in the cache
227  */
228 - (int)_count
229 {
230  return [_items count];
231 }
232 
233 /*
234  * This method return the total cost (addition of all object's cost in the cache)
235  */
236 - (int)_totalCost
237 {
238  if (_totalCostCache >= 0)
239  return _totalCostCache;
240 
241  var enumerator = [_items objectEnumerator],
242  value;
243 
244  _totalCostCache = 0;
245 
246  while (value = [enumerator nextObject])
247  _totalCostCache += [value cost];
248 
249  return _totalCostCache;
250 }
251 
252 /*
253  * This method resequence the position of objects
254  * Otherwise the position value could rise until to cause problem
255  */
256 - (void)_resequencePosition
257 {
258  _currentPosition = 1;
259 
260  // Sort keys by position
261  var sortedKeys = [[_items allKeys] sortedArrayUsingFunction:
262  function(k1, k2) {
263  return [[[_items objectForKey:k1] position] compare:[[_items objectForKey:k2] position]]; }];
264 
265  // Affect new positions
266  for (var i = 0; i < sortedKeys.length; ++i)
267  [[_items objectForKey:sortedKeys[i]] setPosition:_currentPosition++];
268 }
269 
270 /*
271  * Check if the totalCostLimit is exceeded
272  */
273 - (BOOL)_isTotalCostLimitExceeded
274 {
275  return ([self _totalCost] > _totalCostLimit && _totalCostLimit > 0);
276 }
277 
278 /*
279  * Check if the countLimit is exceeded
280  */
281 - (BOOL)_isCountLimitExceeded
282 {
283  return ([self _count] > _countLimit && _countLimit > 0);
284 }
285 
286 /*
287  * This method clean the cache if the totalCost or the count exceeds the totalCostLimit or the countLimit
288  * until to satisfy condition (totalCost < totalCostLimit and count < countLimit)
289  */
290 - (void)_cleanCache
291 {
292  // Check if the condition is satisfied
293  if (![self _isTotalCostLimitExceeded] && ![self _isCountLimitExceeded])
294  return;
295 
296  // Sort keys by position
297  var sortedKeys = [[_items allKeys] sortedArrayUsingFunction:
298  function(k1, k2) {
299  return [[[_items objectForKey:k1] position] compare:[[_items objectForKey:k2] position]]; }];
300 
301  // Remove oldest objects until to satisfy the break condition
302  for (var i = 0; i < sortedKeys.length; ++i)
303  {
304  if (![self _isTotalCostLimitExceeded] && ![self _isCountLimitExceeded])
305  break;
306 
307  // Call delegate method to warn that the object is going to be removed
308  [self _sendDelegateWillEvictObjectForKey:sortedKeys[i]];
309 
310  // Remove object
311  [_items removeObjectForKey: sortedKeys[i]];
312 
313  // Invalid cost cache
314  _totalCostCache = -1;
315  }
316 
317  // Resequence position of all objects
318  [self _resequencePosition];
319 }
320 
321 @end
322 
323 
325 
326 - (void)_sendDelegateWillEvictObjectForKey:(id)aKey
327 {
328  if (_implementedDelegateMethods & CPCacheDelegate_cache_willEvictObject_)
329  [_delegate cache:self willEvictObject:[[_items objectForKey:aKey] object]];
330 }
331 
332 @end
333 
334 
335 /*
336  * Class _CPCacheItem
337  * Represent an item of CPCache
338  * This class allow to associate a cost and a position to an object
339  *
340  * Attributes:
341  * - object: the stored object
342  * - cost: represent the cost (of memory) of the object
343  * - position: represent the insertion order to determine the oldest object
344  */
345 @implementation _CPCacheItem : CPObject
346 {
347  CPObject _object;
348  int _cost;
349  int _position;
350 }
351 
352 + (id)cacheItemWithObject:(CPObject)anObject cost:(int)aCost position:(int)aPosition
353 {
354  var cacheItem = [[self alloc] init];
355 
356  if (cacheItem)
357  {
358  [cacheItem setObject:anObject];
359  [cacheItem setCost:aCost];
360  [cacheItem setPosition:aPosition];
361  }
362 
363  return cacheItem;
364 }
365 
366 @end
367 
369 
374 {
375  return _name;
376 }
377 
381 - (void)setName:(CPString)aValue
382 {
383  _name = aValue;
384 }
385 
389 - (int)countLimit
390 {
391  return _countLimit;
392 }
393 
397 - (void)setCountLimit:(int)aValue
398 {
399  _countLimit = aValue;
400 }
401 
405 - (int)totalCostLimit
406 {
407  return _totalCostLimit;
408 }
409 
413 - (void)setTotalCostLimit:(int)aValue
414 {
415  _totalCostLimit = aValue;
416 }
417 
418 @end