(Optional, offline mode only) Caching is an important part of getting an application to work in offline mode. The Cache method is used to cache and persist data, when the mobile device is out of the network range, and process the data, when it is within the range of the network.
The caching functionality comprises of two types of stored entries, a copy of the entries you receive from the server called as "server entry", and a copy of locally modified entries called as "local entry".
Use the Caching interface, which is implemented by the Cache class, to create an object to work with.
To initialize all the elements required for cache to work, use the initializeCacheWithError method. This method should be executed once during application start.
id<Caching> cache = [[Cache alloc] init]; NSError* error = nil; if (![cache initializeCacheWithError:&error]) { NSLog(@"Initialize Error : %@", error); return; }
Cache can be used in persistable mode (database) and non-persistable mode (memory). By default, cache is persistable.
[cacheLocal setIsPersistable:NO];
To incrementally update the cache for server entries with the latest state of server objects, use the mergeEntries method. Every online HTTP GET operation on a URL should be followed by a mergeEntries method to enable the server cache to be in-sync with the backend service. It is important to instate a server cache using the mergeEntries method for all operations to work after a successful online GET request for a URL at the initial stage. It is recommended to optimize GET operation with Delta support to avoid redundancies.
ODataDataParser* dataParser = [[ODataDataParser alloc] initWithEntitySchema:actualcollection.entitySchema andServiceDocument: serviceDocument]; [dataParser parse:[collection responseData]]; ODataFeed *feed = [dataParser getFeed]; NSError *errormerge=nil; if(![cache mergeEntries:feed forUrlKey:URL withError:&errormerge withCompletionBlock:^(NSNotification *notif){ NSLog(@"Cache updated"); }] ) { NSLog(@"Merge error:%@",errormerge); return; }
You can read server entries or local entries from cache.
To read only server entries, use readEntriesForURLKey. To display local entries, use readEntriesLocalForEntryId. You can also combine the server and local entries to display the latest version of application data.
The ODataEntry class of the parser has two member properties. The isLocalEntry property checks if the particular entry is local, and the cacheState checks if the local entry is created, updated, or deleted on the client. The cache states are represented by the enums - CreatedState, UpdatedState, and DeletedState.
/*Read entry from server.*/ /* Note: To maintain brevity in iOS, the method name does not have the key word server in it and it is always server entries by default that the read returns */ id<Caching> cacheLocal = [[Cache alloc] init]; NSError* error = nil; NSArray* cachedEntries = [cacheLocal readEntriesForUrlKey:urlKey withError:&error]; if (error) { NSLog(@"Read Error : %@", error); return; } /*Read entry from local.*/ id<Caching> cacheLocal = [[Cache alloc] init]; NSError* error = nil; NSArray* localEntries = [cacheLocal readEntriesLocalForEntryId:id forEntityType:type withError:&error]; if (error) { NSLog(@"Read Local Entry Error: %@", error); return; }
id<Caching> cacheLocal = [[Cache alloc] init]; NSError* error = nil; NSString* tempEntryId = [cacheLocal addEntry:entry withError:&error]; if (error) { NSLog(@"Add Entry Error : %@", error); return; }
id<Caching> cacheLocal = [[Cache alloc] init]; NSError* error = nil; ///updateEntry is an ODataEntry with updated values. The method updateEntryWithChangedValues() is a user created method and not provided by the Native OData SDK. ODataEntry* updateEntry = updateEntryWithChangedValues(); if (![cacheLocal updateEntry:updateEntry withError:&error]) { NSLog(@"Update Entry Error : %@", error); return; }
id<Caching> cacheLocal = [[Cache alloc] init]; NSError* error = nil; NSString* deletedEntryId = [deletedEntry getEntryID]; if ([cacheLocal deleteEntryForEntryId:deletedEntryId withError:&error]) { NSLog(@"Delete Entry Error : %@", error); return; }
In case of request to a single endpoint, each request is associated with single cache entry ID.
-(void)createCacheEntryAndFireRequest { id<Caching> cache = [[Cache alloc] init]; NSError* error = nil; if (![cache initializeCacheWithError:&error]) { NSLog(@"Initialization Error:%@", error); } id<Requesting> request = [RequestBuilder requestWithURL:[NSURL URLWithString:@"<URL"]]; [request setUsername:@"<username>"]; [request setPassword:@"<password>"]; error = nil; ODataEntry* entry = buildNewEntry(); // --> Sample dummy call. Not actual procedure to build an entry NSString* cacheEntryId = [cache addEntry:entry withError:&error]; [request setCacheEntryIdList:[NSArray arrayWithObject:cacheEntryId]]; [request startAsynchronous]; } -(void)onSuccessOrError:(id<Requesting>)request { id<Caching> cache = [[Cache alloc] init]; NSArray* localEntryIdList = [request cacheEntryIdList]; NSError* error = nil; // In case of a single (not batched) request if ([localEntryIdList count] == 1 ) { NSString* entryId = [localEntryIdList objectAtIndex:0]; if (![cache clearLocalEntryForEntryId:entryId withError:&error]) { NSLog(@"Clear Local Entry %@ Error : %@", entryId, error); error = nil; } return; } }
In case of batch requests, each request can be associated with multiple cache entry IDs that have been batched.
-(void)createCacheEntryAndFireRequest { id<Caching> cache = [[Cache alloc] init]; NSError* error = nil; if (![cache initializeCacheWithError:&error]) { NSLog(@"Initialization Error:%@", error); } NSMutableArray* cacheEntryIdList = [NSMutableArray array]; // The entire batch request BatchRequest* request = [BatchRequest requestWithURL:[NSURL URLWithString:@"<URL"]]; [request setUsername:@"<username>"]; [request setPassword:@"<password>"]; error = nil; // Creating an entry in Cache. FOR POST: ODataEntry* createEntry = buildNewEntry(); // --> Sample dummy call. Not actual procedure to build an entry NSString* cacheEntryId = [cache addEntry:createEntry withError:&error]; id<Requesting> createRequest = buildNewCreateRequest(); // -> Dummy Call. For actuall snippet please refer to Batch processing section [request addRequestToChangeset:createRequest]; [request closeExistingChangeSet]; [cacheEntryIdList addObject:cacheEntryId]; // Updating an entry in Cache. FOR POST: ODataEntry* updateEntry = updateExistingEntry(); // --> Sample dummy call. Not actual procedure to build an entry [cache addEntry:updateEntry withError:&error]; id<Requesting> updateRequest = buildNewUpdateRequest(); // -> Dummy Call. For actuall snippet please refer to Batch processing section [request addRequestToChangeset:createRequest]; [request closeExistingChangeSet]; [cacheEntryIdList addObject:updateEntry.entryID]; // Note here that update does not take the temp entry id but the actual entry id [request setCacheEntryIdList:cacheEntryIdList]; // Set all the locally modified entry IDs (order maintained) [request startAsynchronous]; } -(void)requestSuccess:(id<Requesting>)request { id<Caching> cache = [[Cache alloc] init]; NSArray* localEntryIdList = [request cacheEntryIdList]; NSError* error = nil; for (NSString* entryId in localEntryIdList) { if (![cache clearLocalEntryForEntryId:entryId withError:&error]) { NSLog(@"Clear Local Entry %@ Error : %@", entryId, error); error = nil; } } }
id<Caching> cacheLocal = [[Cache alloc] init]; NSError* error = nil; NSString* deleteEntryId = [deleteEntry getEntryID]; if ([cacheLocal clearLocalEntryForEntryId:deleteEntryId withError:&error]) { NSLog(@"Clear Local Entry Error : %@", error); return; }
typedef enum { ServiceDocumentType = 0, MetaDocumentType = 1 } DocType; //Service document id<Caching> cacheLocal = [[Cache alloc] init]; NSError* error = nil; if ([cacheLocal storeDocument:servDoc forDocType:ServiceDocumentType forUrlKey:urlKey withError:&error]) { NSLog(@"Store Document Error : %@", error); return; } //Metadata document id<Caching> cacheLocal = [[Cache alloc] init]; NSError* error = nil; if ([cacheLocal storeDocument:metaDoc forDocType:MetaDocumentType forUrlKey:urlKey withError:&error]) { NSLog(@"Store Document Error : %@", error); return; }
To read the service and metadata documents from cache, use the readDocumentForUrlKey method. This method returns the stored document in the cache. The application should pass the document type and the URL for which the document is relevant.
id<Caching> cacheLocal = [[Cache alloc] init]; NSError* error = nil; id document = [cacheLocal readDocumentForUrlKey:urlKey forDocType:ServiceDocumentType withError:&error]; if (error) { NSLog(@"Read Document Error : %@", error); return; }
To clear the cache and persistence of all entries based on the URL key, use the clearCacheForUrlKey method. This method also deletes the delta token of any document that is stored against this URL. SAP recommends that you use this method only when the application does not support delta queries.
id<Caching> cacheLocal = [[Cache alloc] init]; NSError* error = nil; if ([cacheLocal clearCacheForUrlKey:urlKey withError:&error]) { NSLog(@"Clear Cache Error : %@", error); return; }
The OData SDK does not provide any APIs for configuring cache behavior related to cache size or age. The underlying database used is SQLCipher, which implements the following limits: http://www.sqlite.org/limits.html.
The cache database utilizes private timestamps internally, but these may not reflect the time of the last valid GET request, and are not exposed as public APIs for the purpose of validating the freshness of data in the cache.