I am currently working on an iOS app where I need to retrieve some informations from several feeds. With these data, I create a table view to display them. Unfortunately, sometimes I get the following errors:
NetworkStorageDB:_openDBReadConnections: failed to open read connection to DB
CacheRead: unable to open cache files in...
Here is what I have in the debug console:
2016-10-27 10:59:54.490 tdr-ios-prototype[5216:129128] NetworkStorageDB:_openDBReadConnections: failed to open read connection to DB @ /Users/e-novinfo/Library/Developer/CoreSimulator/Devices/1EE7579E-CEC3-4CBF-BB64-438FCDE1C61A/data/Containers/Data/Application/06427070-79E6-4534-BAFC-7EAFF79460C7/Library/Caches/com.e-novinfo.tdr-ios-prototype/Cache.db. Error=14. Cause=unable to open database file
2016-10-27 10:59:54.491 tdr-ios-prototype[5216:129128] CacheRead: unable to open cache files in /Users/e-novinfo/Library/Developer/CoreSimulator/Devices/1EE7579E-CEC3-4CBF-BB64-438FCDE1C61A/data/Containers/Data/Application/06427070-79E6-4534-BAFC-7EAFF79460C7/Library/Caches/com.e-novinfo.tdr-ios-prototype
Basically, I just parse a JSON from an URL and use the URLCache
class, which I extended, to check if there's a cache for a specific request. I often get this problem when there is a lot of data, like when I retrieve videos from YouTube or pictures from Picasa.
I proceed this way to get the data: on the viewDidAppear
of the concerned table view controller, I call a method named loadMedia
which will use a class, for example, PicasaWebServices (singleton). This last class will reach the JSON through URLSession.shared.dataTask
. Before making a request, I check if there is cache for the concerned request and, if it is not too old, I use it. Else, I clean the cache. If there is no connection (checked with Reachability), the cache is used. Finally, if there is a response, the data are placed in the table view controller. While the display is created, other informations are reached through URLSession.shared.dataTask
, like the images I need (I use an UIImageView
extension to do that).
I firstly suspected a cache overload so I added some check in my URLCache
extension, but it didn't solve the problem. Maybe there is too many request URLSession.shared.dataTask
and I have to delay them but I can't figure how to do that.
This error occurs only when I run the app with the simulator. On an iPhone 6, sometimes, in the same case, I get the following error:
libc++abi.dylib: terminating with uncaught exception of type NSException
It often happens when the albums list from Picasa is displayed and I try to go to the next view, which display the album content, by tapping on a row.
Any help would be much appreciated! Many thanks!
For example, here is my Picasa WebServices:
class PicasaWebServices {
//MARK: Properties
/**********************/
/***** PROPERTIES *****/
/**********************/
static let sharedInstance = PicasaWebServices()
fileprivate var googleApiKey: String = "..."
fileprivate var picasaUsername: String = "..."
fileprivate var reachability: Reachability?
/****************************************/
/****************************************/
/****************/
/***** INIT *****/
/****************/
fileprivate init() {
self.reachability = Reachability()!
//END init
}
/****************************************/
/****************************************/
/*****************************/
/***** GET PICASA ALBUMS *****/
/*****************************/
func getPicasaAlbums( _ completionHandler:@escaping ( Array< Dictionary< String, AnyObject > >?, NSError? ) -> Void ) {
let requestURL = "http://picasaweb.google.com/data/feed/api/user/\( picasaUsername )?kind=album&access=public&prettyprint=true&thumbsize=320c&imgmax=640&fields=entry(id,title,link(@href),gphoto:id,media:group(media:thumbnail,media:description))&alt=json"
let url = URL( string: requestURL )!
let request = NSMutableURLRequest( url: url )
URLCache().checkIfCacheForRequest( request )
URLSession.shared.dataTask( with: request as URLRequest ) { data, response, error in
if error != nil {
completionHandler( nil, error as NSError? )
return;
}
var jsonError: NSError?
var jsonResult: AnyObject?
do {
jsonResult = try JSONSerialization.jsonObject( with: data!, options: [] ) as AnyObject
} catch let error as NSError {
jsonError = error
jsonResult = nil
} catch {
fatalError()
}
var albums: Array< Dictionary< String, AnyObject > > = []
let jsonResultFeed = jsonResult![ "feed" ] as! [ String: AnyObject ]
if let items = jsonResultFeed[ "entry" ] as? [ [ String: AnyObject ] ] {
for item in items {
albums.append( item )
//END for item in items
}
//END if let items = jsonResultFeed[ "entry" ] as? [ [ String: AnyObject ] ]
}
completionHandler( albums, jsonError );
//END let dataTask = urlSession.dataTaskWithURL( url, completionHandler: { ( data, response, error ) -> Void in
}.resume()
//END getPicasaAlbums
}
/****************************************/
/****************************************/
/************************************/
/***** GET PICASA ALBUM CONTENT *****/
/************************************/
func getPicasaAlbumContent( _ requestURL: String, completionHandler:@escaping ( Array<Dictionary<String, AnyObject>>?, NSError? ) -> Void ) {
let url = URL( string: requestURL )!
let request = NSMutableURLRequest( url: url )
URLCache().checkIfCacheForRequest( request )
URLSession.shared.dataTask( with: request as URLRequest ) { data, response, error in
if error != nil {
completionHandler( nil, error as NSError? )
return;
}
var jsonError: NSError?
var jsonResult: AnyObject?
do {
jsonResult = try JSONSerialization.jsonObject( with: data!, options: [] ) as AnyObject
} catch let error as NSError {
jsonError = error
jsonResult = nil
} catch {
fatalError()
}
var photos: Array< Dictionary< String, AnyObject > > = []
let jsonResultFeed = jsonResult![ "feed" ] as! [ String: AnyObject ]
if let items = jsonResultFeed[ "entry" ] as? [ [ String: AnyObject ] ] {
for item in items {
photos.append( item )
//END for item in items
}
//END if let items = jsonResultFeed[ "entry" ] as? [ [ String: AnyObject ] ]
}
completionHandler( photos, jsonError );
//END let dataTask = urlSession.dataTaskWithURL( url, completionHandler: { ( data, response, error ) -> Void in
}.resume()
//END getPicasaAlbumContent
}
//END PicasaWebServices
}
My URLCache extension:
/*******************/
/***** IMPORTS *****/
/*******************/
import UIKit
/****************************************/
/****************************************/
/**********************/
/***** ERROR TYPE *****/
/**********************/
enum UrlCacheExtensionError: Error {
case empty
case dateError
}
/****************************************/
/****************************************/
/*****************/
/***** CLASS *****/
/*****************/
extension URLCache {
/**********************************/
/***** GET THE REQUEST'S DATE *****/
/**********************************/
/*
* @param Dictionary headers request's headers
* @return NSDate
*/
public func getRequestDate( _ headers: Dictionary< String, AnyObject > ) throws -> Date {
var output = Date()
guard !headers.isEmpty else {
throw UrlCacheExtensionError.empty
}
if let theDate = headers[ "Date" ] as? String {
let formatedDate = Date().formateStringToDate( theDate, format: "E, dd MMM yyyy HH:mm:ss zzz", localIdentifier: "en_US" )
print( formatedDate )
output = formatedDate
}
return output
//END getRequestDate
}
/****************************************/
/****************************************/
/*************************************/
/***** GET THE REQUEST'S HEADERS *****/
/*************************************/
/*
* @param NSMutableURLRequest request the request
* @return Dictionary
*/
public func getRequestHeaders( _ request: NSMutableURLRequest ) throws -> Dictionary<NSObject, AnyObject> {
var headers: Dictionary<NSObject, AnyObject>
let cacheResponse = URLCache.shared.cachedResponse( for: request as URLRequest )
if let response = cacheResponse?.response as? HTTPURLResponse {
headers = response.allHeaderFields as Dictionary<NSObject, AnyObject>
} else {
throw UrlCacheExtensionError.empty
}
return headers
//END getRequestHeaders
}
/****************************************/
/****************************************/
/**************************************************/
/***** CHECK IF CACHE IS OLDER THAN ONE VALUE *****/
/**************************************************/
/*
* @param NSDate date the cache date
* @param Int time time to compare
* @return Bool
*/
public func checkIfCacheIsOlder( _ date: Date, time: Int ) throws -> Bool {
guard date == date else {
throw UrlCacheExtensionError.dateError
}
if Date().compareDatesInSeconds( date ) >= time {
return true
} else {
return false
}
//END checkIfCacheIsOlder
}
/****************************************/
/****************************************/
/********************************************************************/
/***** CHECK IF THE CACHE HAS BE CLEANED FOR A SPECIFIC REQUEST *****/
/********************************************************************/
/*
* @param NSMutableURLRequest request the request
* @return Bool
*/
@discardableResult
public func checkIfCacheHasToBeCleanedForRequest( _ request: NSMutableURLRequest ) -> Bool {
let reachability = Reachability()!
if !reachability.isReachable {
return false
} else {
do {
let requestHeaders = try getRequestHeaders( request )
do {
let requestDate = try getRequestDate( requestHeaders as! Dictionary<String, AnyObject> )
do {
let isCacheOlder = try checkIfCacheIsOlder( requestDate, time: 30 )
if isCacheOlder {
//Broken in 9.3 & 9.3.1
//NSURLCache.sharedURLCache().removeCachedResponseForRequest( request )
URLCache.shared.removeAllCachedResponses()
return true
} else {
return false
}
} catch {
return false
}
} catch {
return false
}
} catch {
return false
}
//END else
}
//END checkIfCacheHasToBeCleanedForRequest
}
/****************************************/
/****************************************/
/**************************/
/***** CHECK CAPACITY *****/
/**************************/
public func checkCapacity() -> Void {
if URLCache().currentMemoryUsage >= URLCache().memoryCapacity || URLCache().currentDiskUsage >= URLCache().diskCapacity {
URLCache.shared.removeAllCachedResponses()
}
return
//END checkCapacity
}
/****************************************/
/****************************************/
/*************************************/
/***** CHECK CACHE FOR A REQUEST *****/
/*************************************/
/*
* @param NSMutableURLRequest request the request
* @return Bool
*/
@discardableResult
public func checkIfCacheForRequest( _ request: NSMutableURLRequest ) -> Bool {
let reachability = Reachability()!
if reachability.isReachable {
self.checkCapacity()
let theRequest = self.checkIfCacheHasToBeCleanedForRequest( request )
if theRequest {
request.cachePolicy = .reloadIgnoringCacheData
return false
} else {
request.cachePolicy = .returnCacheDataElseLoad
return true
}
//END if reachability.isReachable
} else {
request.cachePolicy = .returnCacheDataDontLoad
return false
}
//END checkIfCacheForRequest
}
//END NSURLCache
}
My UIImage extension:
extension UIImageView {
/*******************************/
/***** DOWNLOADED FROM URL *****/
/*******************************/
func downloadedFrom( url: URL, contentMode mode: UIViewContentMode = .scaleAspectFit, completionHandler: @escaping( _ image: UIImage?, NSError? ) -> Void ) {
contentMode = mode
let request = NSMutableURLRequest( url: url )
URLCache().checkIfCacheForRequest( request )
URLSession.shared.dataTask( with: request as URLRequest ) { ( data, response, error ) in
//URLSession.shared.dataTask( with: url ) { ( data, response, error ) in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix( "image" ),
let data = data, error == nil,
let image = UIImage( data: data )
else { return }
completionHandler( image as UIImage, nil )
//END URLSession.shared.dataTask
}.resume()
//END downloadedFrom
}
/****************************************/
/****************************************/
/********************************/
/***** DOWNLOADED FROM LINK *****/
/********************************/
func downloadedFrom( link: String, contentMode mode: UIViewContentMode = .scaleAspectFit ) {
guard let url = URL( string: link ) else { return }
self.downloadedFrom( url: url, contentMode: mode, completionHandler: { ( image, error ) -> Void in
if image != nil {
DispatchQueue.main.async( execute: {
self.image = image
} )
//END if posts != nil
}
//END self.downloadedFrom( url: url, contentMode: mode, completionHandler: { ( image, error ) -> Void in
} )
//END downloadedFrom
}
//END UIImageViewExtension
}
Where I call the data (TableViewController):
func loadMedia() {
PicasaWebServices.sharedInstance.getPicasaAlbums( { ( albums, error ) -> Void in
if albums != nil {
self.media?.removeAll()
self.media = albums
DispatchQueue.main.async( execute: {
self.tableView.reloadData()
} )
//END if albums != nil
}
//END PicasaWebServices.sharedInstance.getPicasaAlbums
} )
}
UPDATE 1:
So, after many checks, the problem seems to come from my UIImageView extension when I use my URLCache extension. When there is many images to load, check the cache of the state seems to be a problem and the CacheRead: unable to open cache files in...
error is raised. In a case of a TableViewController, the images are loaded through the cellForRowAt
function. Any idea how I can handle this?