3

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?

Lancelot
  • 573
  • 2
  • 5
  • 27

0 Answers0