├── .gitignore ├── LICENSE ├── PFCloud+Cache.h ├── README.md └── PFCloud+Cache.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | 20 | #CocoaPods 21 | Pods 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 martinrybak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /PFCloud+Cache.h: -------------------------------------------------------------------------------- 1 | // 2 | // PFCloud+Cache.h 3 | // PFCloud+Cache 4 | // 5 | // Created by Martin Rybak on 1/23/14. 6 | // Copyright (c) 2014 UpdateZen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PFCloud (Cache) 12 | 13 | /* 14 | Calls the given cloud function with the parameters provided asynchronously and runs the callback when it is done. 15 | @param function The function name to call. 16 | @param parameters The parameters to send to the function. 17 | @param cachePolicy The cache policy to use for the function. 18 | @param block The block to execute. The block should have the following argument signature:(id result, NSError *error). 19 | */ 20 | + (void)callFunctionInBackground:(NSString*)function withParameters:(NSDictionary*)parameters cachePolicy:(PFCachePolicy)cachePolicy block:(PFIdResultBlock)block; 21 | 22 | /* 23 | Calls the given cloud function with the parameters provided asynchronously and runs the callback when it is done. 24 | @param function The function name to call. 25 | @param parameters The parameters to send to the function. 26 | @param cachePolicy The cache policy to use for the function. 27 | @param target The object to call the selector on. 28 | @param selector The selector to call. It should have the following signature: (void)callbackWithResult:(id) result error:(NSError *)error. result will be nil if error is set and vice versa. 29 | */ 30 | + (void)callFunctionInBackground:(NSString*)function withParameters:(NSDictionary*)parameters cachePolicy:(PFCachePolicy)cachePolicy target:(id)target selector:(SEL)selector; 31 | 32 | /* 33 | Clears the cache for the given cloud function and parameters. 34 | @param function The function name to call. 35 | @param parameters The parameters to send to the function. 36 | */ 37 | + (void)clearCachedResult:(NSString*)function withParameters:(NSDictionary*)parameters; 38 | 39 | /* 40 | Clears the cache for the all cloud functions and parameters. 41 | */ 42 | + (void)clearAllCachedResults; 43 | 44 | /* 45 | Returns whether a cached result exists for the given cloud function and parameters. 46 | @param function The function name to call. 47 | @param parameters The parameters to send to the function. 48 | @result Whether or not the cached result exists. 49 | */ 50 | + (BOOL)hasCachedResult:(NSString*)function params:(NSDictionary*)parameters; 51 | 52 | /* 53 | Sets the maximum age for all items in the cache. 54 | @param maxCacheAge The cache age in seconds. 55 | */ 56 | + (void)setMaxCacheAge:(NSTimeInterval)maxCacheAge; 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PFCloud-Cache 2 | ============= 3 | 4 | **NOTE: This repo is no longer maintained. We ended up abandoning Parse because of performance and security issues.** 5 | 6 | 7 | This category on **PFCloud** adds an additional **cachePolicy** parameter to Parse's existing asynchronous (background) cloud function call methods. It exactly replicates the [existing caching behavior] used with the **PFQuery** object. It creates a record in the cache for every unique combination of function name + parameters. 8 | 9 | ``` 10 | + (void)callFunctionInBackground:(NSString*)function 11 | withParameters:(NSDictionary*)parameters 12 | cachePolicy:(PFCachePolicy)cachePolicy 13 | block:(PFIdResultBlock)block; 14 | ``` 15 | ``` 16 | + (void)callFunctionInBackground:(NSString*)function 17 | withParameters:(NSDictionary*)parameters 18 | cachePolicy:(PFCachePolicy)cachePolicy 19 | target:(id)target 20 | selector:(SEL)selector; 21 | ``` 22 | 23 | ##Sample Usage 24 | 25 | ``` 26 | #import "PFCloud+Cache.h" 27 | 28 | [PFCloud callFunctionInBackground:function 29 | withParameters:params 30 | cachePolicy:kPFCachePolicyCacheThenNetwork 31 | block:^(id object, NSError* error) { 32 | //Because we are using kPFCachePolicyCacheThenNetwork as our cache policy, 33 | //this block will be invoked twice (if a cached result exists). 34 | }]; 35 | ``` 36 | 37 | ##NSCoding 38 | 39 | Both the contents of the **parameters** dictionary and the **cloud function return value** must conform to the [NSCoding] protocol. Note that PFObject **does not** conform to NSCoding by default. If your cloud function returns one or more **PFObjects**, you must implement this protocol yourself or use my other [Parse+NSCoding] library. 40 | 41 | ##Cache Management 42 | 43 | Cached objects are persisted to disk in the following folder on the user's device: 44 | 45 | ``` 46 | ~/Library/Caches/com.tumblr.TMDiskCache/TMDiskCacheShared/ 47 | ``` 48 | 49 | Cached objects persist between app restarts until they expire. By default they never expire. To impose a maximum cache age use the ```setMaxCacheAge:``` method. The cache can also be explicity cleared for a particular cloud function call using the ```clearCachedResult:``` method, or for all calls using the ```clearAllCachedResults``` method. 50 | 51 | ##How it Works 52 | 53 | The trick is to create a unique key in the cache for every unique combination of function + parameters. This library serializes the function name along with the contents of the parameters dictionary. It then calculates a MD5 hex digest using the [NSData-MD5] library to create a unique string key for the cache. Actual caching is performed using Tumblr's [TMCache] library. Thanks to those libraries for their great work! 54 | 55 | ##Installation 56 | 57 | Easiest installation is using CocoaPods to resolve all dependencies: 58 | 59 | ```pod 'PFCloud+Cache', '~> 0.0.2'``` 60 | 61 | Otherwise you must manually copy the .h and .m files from this repo as well as from [NSData-MD5] and [TMCache]. Obviously you must also have the [Parse SDK] installed. Enjoy! 62 | 63 | [existing caching behavior]:https://parse.com/docs/ios_guide#queries-caching/iOS 64 | [NSCoding]:https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Protocols/NSCoding_Protocol/Reference/Reference.html 65 | [Parse+NSCoding]:https://github.com/martinrybak/Parse-NSCoding/ 66 | [NSData-MD5]:https://github.com/siuying/NSData-MD5 67 | [TMCache]:https://github.com/tumblr/TMCache 68 | [Parse SDK]:https://parse.com/downloads/ios/parse-library/latest 69 | -------------------------------------------------------------------------------- /PFCloud+Cache.m: -------------------------------------------------------------------------------- 1 | // 2 | // PFCloud+Cache.m 3 | // PFCloud+Cache 4 | // 5 | // Created by Martin Rybak on 1/23/14. 6 | // Copyright (c) 2014 UpdateZen. All rights reserved. 7 | // 8 | 9 | #import "PFCloud+Cache.h" 10 | #import "TMCache.h" 11 | #import "NSData+MD5Digest.h" 12 | 13 | @implementation PFCloud (Cache) 14 | 15 | #pragma mark - Public 16 | 17 | + (void)callFunctionInBackground:(NSString*)function withParameters:(NSDictionary*)parameters cachePolicy:(PFCachePolicy)cachePolicy block:(PFIdResultBlock)block 18 | { 19 | NSParameterAssert(block); 20 | 21 | switch (cachePolicy) 22 | { 23 | //The call does not load from the cache or save results to the cache. 24 | case kPFCachePolicyIgnoreCache: 25 | { 26 | [self callFunctionInBackground:function withParameters:parameters block:block]; 27 | break; 28 | } 29 | //The call only loads from the cache, ignoring the network. If there are no cached results, that causes a PFError. 30 | case kPFCachePolicyCacheOnly: 31 | { 32 | id cachedResponse = [self fetchFromCache:function params:parameters]; 33 | if (cachedResponse) { 34 | block(cachedResponse, nil); 35 | } else { 36 | block(nil, [self noCacheError]); 37 | } 38 | break; 39 | } 40 | //The call first tries to load from the cache, but if that fails, it loads results from the network. If neither cache nor network succeed, there is a PFError. 41 | case kPFCachePolicyCacheElseNetwork: 42 | { 43 | id cachedResponse = [self fetchFromCache:function params:parameters]; 44 | if (cachedResponse) 45 | block(cachedResponse, nil); 46 | else { 47 | [self callFunctionInBackgroundAndCache:function withParameters:parameters block:block]; 48 | } 49 | break; 50 | } 51 | //The call does not load from the cache, but it will save results to the cache. 52 | case kPFCachePolicyNetworkOnly: 53 | { 54 | [self callFunctionInBackgroundAndCache:function withParameters:parameters block:block]; 55 | break; 56 | } 57 | //The call first tries to load from the network, but if that fails, it loads results from the cache. If neither network nor cache succeed, there is a PFError. 58 | case kPFCachePolicyNetworkElseCache: 59 | { 60 | [self callFunctionInBackgroundAndCache:function withParameters:parameters block:^(id object, NSError* error) { 61 | if (error) { 62 | id cachedResponse = [self fetchFromCache:function params:parameters]; 63 | if (cachedResponse) { 64 | block(cachedResponse, nil); 65 | } else { 66 | block(nil, error); 67 | } 68 | } else { 69 | block(object, nil); 70 | } 71 | }]; 72 | break; 73 | } 74 | //The call first loads from the cache, then loads from the network. In this case, the callback will actually be called twice - first with the cached results, then with the network results. 75 | case kPFCachePolicyCacheThenNetwork: 76 | { 77 | id cachedResponse = [self fetchFromCache:function params:parameters]; 78 | if (cachedResponse) { 79 | block(cachedResponse, nil); 80 | } 81 | [self callFunctionInBackgroundAndCache:function withParameters:parameters block:block]; 82 | break; 83 | } 84 | } 85 | } 86 | 87 | + (void)callFunctionInBackground:(NSString*)function withParameters:(NSDictionary*)parameters cachePolicy:(PFCachePolicy)cachePolicy target:(id)target selector:(SEL)selector 88 | { 89 | [self callFunctionInBackground:function withParameters:parameters cachePolicy:cachePolicy block:^(id object, NSError* error) { 90 | NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]]; 91 | [invocation setTarget:target]; 92 | [invocation setSelector:selector]; 93 | [invocation setArgument:&object atIndex:2]; 94 | [invocation setArgument:&error atIndex:3]; 95 | [invocation invoke]; 96 | }]; 97 | } 98 | 99 | + (void)clearCachedResult:(NSString*)function withParameters:(NSDictionary*)parameters 100 | { 101 | [[TMDiskCache sharedCache] removeObjectForKey:[self cacheKey:function params:parameters]]; 102 | } 103 | 104 | + (void)clearAllCachedResults 105 | { 106 | [[TMDiskCache sharedCache] removeAllObjects]; 107 | } 108 | 109 | + (BOOL)hasCachedResult:(NSString*)function params:(NSDictionary*)parameters 110 | { 111 | id cachedResult = [self fetchFromCache:function params:parameters]; 112 | return cachedResult != nil; 113 | } 114 | 115 | + (void)setMaxCacheAge:(NSTimeInterval)maxCacheAge 116 | { 117 | [TMDiskCache sharedCache].ageLimit = maxCacheAge; 118 | } 119 | 120 | #pragma mark - Private 121 | 122 | + (void)callFunctionInBackgroundAndCache:(NSString*)function withParameters:(NSDictionary*)parameters block:(PFIdResultBlock)block 123 | { 124 | [self callFunctionInBackground:function withParameters:parameters block:^(id object, NSError* error) { 125 | if (error) { 126 | block(nil, error); 127 | } else { 128 | [self saveToCache:object function:function params:parameters]; 129 | block(object, nil); 130 | } 131 | }]; 132 | } 133 | 134 | + (NSString*)cacheKey:(NSString*)function params:(NSDictionary*)params 135 | { 136 | NSDictionary* call = @{ function:params }; 137 | NSData* data = [NSKeyedArchiver archivedDataWithRootObject:call]; 138 | return [data MD5HexDigest]; 139 | } 140 | 141 | + (void)saveToCache:(id)object function:(NSString*)function params:(NSDictionary*)params 142 | { 143 | [[TMDiskCache sharedCache] setObject:object forKey:[self cacheKey:function params:params]]; 144 | } 145 | 146 | + (id)fetchFromCache:(NSString*)function params:(NSDictionary*)params 147 | { 148 | return [[TMDiskCache sharedCache] objectForKey:[self cacheKey:function params:params]]; 149 | } 150 | 151 | + (NSError*)noCacheError 152 | { 153 | return [NSError errorWithDomain:@"PFCloud+Cache" code:120 userInfo:@{ NSLocalizedDescriptionKey : @"cache miss" }]; 154 | } 155 | 156 | @end 157 | --------------------------------------------------------------------------------