Skip to content

Instantly share code, notes, and snippets.

@benlodotcom
Last active April 13, 2017 09:41
Show Gist options
  • Save benlodotcom/5296180 to your computer and use it in GitHub Desktop.
Save benlodotcom/5296180 to your computer and use it in GitHub Desktop.
Methods to flush the HTML5 Application Cache on iOS. In a UIWebView Webkit uses an sqlite database to store the resources associated to a cache manifest, as there is no API yet to flush the Application Cache you can use this method which delete entries directly from the database. However, this approach could break anytime if the implementation o…
static NSString *cacheDatabaseName = @"ApplicationCache.db";
static NSString *cacheGroupTable = @"CacheGroups";
static NSString *cacheGroupTableManifestURLColums = @"manifestURL";
static NSString *cacheTable = @"Caches";
static NSString *cacheTableCacheGroupId = @"cacheGroup";
/**
Clears the cached resources associated to a cache group.
@param manifestURLs An array of `NSString` containing the URLs of the cache manifests for which you want to clear the resources.
*/
- (void)clearCacheForCacheManifestURLs:(NSArray *)manifestURLs {
sqlite3 *newDBconnection;
/*Check that the db is created, if not we return as sqlite3_open would create
an empty database and webkit will crash on us when accessing this empty database*/
if (![[NSFileManager defaultManager] fileExistsAtPath:[self cacheDatabasePath]]) {
NSLog(@"The cache manifest db has not been created by Webkit yet");
return;
}
if (sqlite3_open([[self cacheDatabasePath] UTF8String], &newDBconnection) == SQLITE_OK) {
if (sqlite3_exec(newDBconnection, "BEGIN EXCLUSIVE TRANSACTION", 0, 0, 0) != SQLITE_OK) {
NSLog(@"SQL Error: %s",sqlite3_errmsg(newDBconnection));
}
else {
/*Get the cache group IDs associated to the cache manifests' URLs*/
NSArray *cacheGroupIds = [self getCacheGroupIdForURLsIn:manifestURLs usingDBConnection:newDBconnection];
/*Remove the corresponding entries in the Caches and CacheGroups tables*/
[self deleteCacheResourcesInCacheGroups:cacheGroupIds usingDBConnection:newDBconnection];
[self deleteCacheGroups:cacheGroupIds usingDBConnection:newDBconnection];
if (sqlite3_exec(newDBconnection, "COMMIT TRANSACTION", 0, 0, 0) != SQLITE_OK) NSLog(@"SQL Error: %s",sqlite3_errmsg(newDBconnection));
}
sqlite3_close(newDBconnection);
} else {
NSLog(@"Error opening the database located at: %@", [self cacheDatabasePath]);
newDBconnection = NULL;
}
}
/**
Get the Cache group IDs associated to cache manifests URLs
@param urls The URLs of the cache manifests.
@param db The connection to the database.
*/
- (NSArray *)getCacheGroupIdForURLsIn:(NSArray *)urls usingDBConnection:(sqlite3 *)db {
NSMutableArray *result = [NSMutableArray arrayWithCapacity:0];
sqlite3_stmt *statement;
NSString *queryString = [NSString stringWithFormat:@"SELECT id FROM %@ WHERE %@ IN (%@)", cacheGroupTable,cacheGroupTableManifestURLColums, [self commaSeparatedValuesFromArray:urls]];
const char *query = [queryString UTF8String];
if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW) {
int id = sqlite3_column_int(statement, 0);
[result addObject:[NSNumber numberWithInt:id]];
}
}
else {
NSLog(@"SQL Error: %s",sqlite3_errmsg(db));
}
sqlite3_finalize(statement);
return result;
}
/**
Delete the rows in the CacheGroups table associated to the cache groups we want to delete.
@param cacheGroupIds An array of `NSNumbers` corresponding to the cache groups you want cleared.
@param db The connection to the database.
*/
- (void)deleteCacheGroups:(NSArray *)cacheGroupsIds usingDBConnection:(sqlite3 *)db {
sqlite3_stmt *statement;
NSString *queryString = [NSString stringWithFormat:@"DELETE FROM %@ WHERE id IN (%@)", cacheGroupTable,[self commaSeparatedValuesFromArray:cacheGroupsIds]];
const char *query = [queryString UTF8String];
if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
{
sqlite3_step(statement);
}
else {
NSLog(@"SQL Error: %s",sqlite3_errmsg(db));
}
sqlite3_finalize(statement);
}
/**
Delete the rows in the Caches table associated to the cache groups we want to delete.
Deleting a row in the Caches table triggers a cascade delete in all the linked tables, most importantly
it deletes the cached data associated to the cache group.
@param cacheGroupIds An array of `NSNumbers` corresponding to the cache groups you want cleared.
@param db The connection to the database
*/
- (void)deleteCacheResourcesInCacheGroups:(NSArray *)cacheGroupsIds usingDBConnection:(sqlite3 *)db {
sqlite3_stmt *statement;
NSString *queryString = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ IN (%@)", cacheTable,cacheTableCacheGroupId, [self commaSeparatedValuesFromArray:cacheGroupsIds]];
const char *query = [queryString UTF8String];
if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
{
sqlite3_step(statement);
}
else {
NSLog(@"SQL Error: %s",sqlite3_errmsg(db));
}
sqlite3_finalize(statement);
}
/**
Retrieves the path of the ApplicationCache sqite db. The db is located in `Library/Caches/your.bundle.id/ApplicationCache.db`
@return The path of the db
*/
- (NSString *)cacheDatabasePath {
NSArray *pathsList = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *pathSuffix = [NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] bundleIdentifier], cacheDatabaseName];
NSString *path = [(NSString *)pathsList[0] stringByAppendingPathComponent:pathSuffix];
return path;
}
/**
Helper to transform an `NSArray` in a comma separated string we can use in our queries.
@return The comma separated string
*/
- (NSString *)commaSeparatedValuesFromArray:(NSArray *)valuesArray {
NSMutableString *result = [NSMutableString stringWithCapacity:0];
[valuesArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[NSNumber class]]) {
[result appendFormat:@"%d", [(NSNumber *)obj intValue]];
}
else {
[result appendFormat:@"'%@'", obj];
}
if (idx != valuesArray.count-1) {
[result appendString:@", "];
}
}];
return result;
}
@trthtai
Copy link

trthtai commented Jul 27, 2016

Hi ben, I'm facing the UIWebView Cache problem which describes meticulously in http://stackoverflow.com/questions/38606333/the-uiwebview-wont-clear-the-loaded-resources-when-its-parent-view-dismiss.
So I'm just wondering if your code can solve my problem ?
Thank you

-- Update:
I have just tried your code, and it doesn't work for my problem. Anyway, thanks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment