IndexedDB with ngforage - domexception

I am storing data on the browser indexedDB and I am using Angular 14 with ngforage to store the data inside the indexed DB. So I want to be able to catch an error if the indexedDB memory is exhausted. So when I catch the error I am getting an empty object back as an error. How can I get an actual error when the memory is full so that I know what to if the storage is full?
Below is my code for storing the data inside the indexedDB:
this.obj.id = uuidv4()
this.obj.name = this.name.nativeElement.value
this.obj.surname = this.surname.nativeElement.value
this.obj.age = this.age.nativeElement.value
await this.setItem(this.obj.id, this.obj).catch((reason)=>
{console.log(JSON.stringify(reason))});

Related

An error occurring during Core Data persistent store migration in iOS 13

After updating XCode to version 11 I added a new model version to Core Data and in new version I added a new attribute to an Entity. Made the new version active and added the new property to managed object file.
After releasing this version to the users it started to crash with the following message: "The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store." and "duplicate column name ZNEWCOLUMN". Until now I made a lot of changes to the Core Data model and migration always worked.
This crash appears only on iOS 13!
This is how I load Core Data:
lazy var managedObjectContext: NSManagedObjectContext = {
return self.persistentContainer.viewContext
}()
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "MyModel")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions.append(description)
return container
}()
Any help would be appreciated.
The same thing is happening to me, lightweight migration at iOS 12 was right at real device and Simulator but at iOS 13 fail with the next log result:
SQLite error code:1, 'duplicate column name: ZNAME_OF_THE_COLUMN .... Error Domain
= NSCocoaErrorDomain Code = 134110 "An error occurred during persistent storage migration."
I load data like #iOS Dev post.
I check the xxxx.sqlite database file in the emulator path before and after the migration and there were no columns with those new same names.
To know the route of the *.sqlite in emulator you have to put a breakpoint and when it is stopped put in the console po NSHomeDirectory().
Then go to Finder window, tap the keys Control + Command + G and paste the route. Yo can handle it (for example) with DB Browser for SQLite program, it´s free.
After a long search I have seen what has happened to some people but I have not seen any solution.
Mine was:
Select the actual *.xcdatamodel.
Select Editor > Add Model Version.
Provide a version name based on the previous model (like XxxxxxV2.xcdatamodel).
Click on this new version model NewV2.xcdatamodel.
Select this new version as Current on Properties at right hand of IDE.
Make your changes at DDBB.
Run app and will work fine.
I did tests overriding app (with new values) and it was fine.
I hope this may help.
If you want to edit the descriptions, you need to do so before you load the stores (and I have no idea what appending a new description would do):
container.persistentStoreDescriptions.forEach { storeDesc in
storeDesc.shouldMigrateStoreAutomatically = true
storeDesc.shouldInferMappingModelAutomatically = true
}
container.loadPersistentStores { [unowned self] (storeDesc, error) in
if let error = error {
// handle your error, do not fatalError! even a message that something is wrong can be helpful
return
}
// do any additional work on your view context, etc.
}
If your problem is reproduceable, you should look at the error that's being returned and look for something called ZNEWCOLUMN (though this sounds like a temporary default name?) This nomenclature is the raw column name in the SQL database though, so it's likely the migrator is attempting to add this new column and failing.
Try turning on SQL debugging in your scheme's Arguments:
-com.apple.CoreData.SQLDebug 1
Try logging into the raw SQL database (the above will give you the raw path if you're on the simulator). Try rolling back to the previous data model on a previous OS and then just upgrading to 13.
Sounds like you have some duplicate column somewhere so these are just some ideas to find out where it is.

iOS: flush all output files

Is there an iOS method to flush all open output files-- so that when the call returns, all pending buffered data is written to persistent storage? I mean this in general terms-- where I don't have access to the specific file handles. I've tried sync() from Swift (a POSIX call) but that appears not guaranteed to actually make sure the data hits persistent storage before it returns (see http://pubs.opengroup.org/onlinepubs/009696899/functions/sync.html).
Not sure if this solves the problem in general, but it's addressing my need. Here's what I'm doing:
// Write to file (in my case, this is a log file where I don't have access to the file handle).
// I'm using logging to a file with SwiftyBeaver. https://github.com/SwiftyBeaver/SwiftyBeaver
DispatchQueue.main.async {
// Read from the file-- it has the flushed contents. I'm doing:
do {
let fileContents = try String(contentsOfFile: path)
} catch (let error) {
}
}
It looks like files are flushed at the end of the event loop.

CloudKit: CKFetchRecordChangesOperation, CKServerChangeToken and Delta Download

My question is related to the "Delta Download" thing as it was named in WWDC 2014 Advanced CloudKit.
I'm trying to make syncronization for my Core Data app, which is iPhone only for now (think: there is only one device active). So, basically the app will store user records in the cloud from one same device, for the most cases for now.
I have trouble with understanding custom zone feature which is based on CKFetchRecordChangesOperation aka Delta Download.
As I got it right, we have CKServerChangeToken's to maintain sync operations (I mean download only those records which was added/modified/deleted by another device), as was presented on WWDC.
But, what I can't understand is that we recieve that token only after CKFetchRecordChangesOperation, when we save records to the cloud we don't get new token.
And if we make fetch with the current available token (since it changes only after fetch), we recieve records that was saved from our previous save operation. Basicaly we get save recods that already have on our device. Why? I'm missing something here?
What if we seeding some data to the cloud (from device A), it is justified for situation when device B is fetching the zone records, but what if device A be? Download all the records again?
I found recordChangeTag in the CKRecord, is this a property I can use for resolving conflicts with local objects - fetched objects (same or different version), if so can somebody give me example of how I need to do this: save recordChangeTag to Core Data when save record to CloudKit for the first time or how?
The lack of documentation is such a headache.
I found a time to write an answer for this question. I won't dig into implementation, but I will discuss the concept.
CloudKit provides a way to data synchronisation between your device and the CloudKit server.
What I use to establish synchronisation process in my case between iPhone and server only (again, if you have iPhone + iPad app, the process require more steps.):
I have custom zone in the private cloud database.
I use OperationQueue to establish different asynchronous processes which depend on each other. Some operations have own operation queues.
Steps:
1) Check if my custom zone is exist
1.1) If there is no custom zone
1.2) Create new custom zone. (Optional: add records)
1.3) Refresh zone change token
You can refresh zone change token by: performing
CKFetchRecordChangesOperation,
fetchRecordChangesCompletionBlock returns CKServerChangeToken
save it to UserDefaults (for example) using NSKeyedArchiver). This operation's task is to refresh token and it's performed at the end synchronisation process.
2) If there is custom zone already
2.1) Get changes from zone using previously saved zone change token. (CKFetchRecordChangesOperation)
2.2) Update and delete local records.
2.3) Refresh zone change token.
2.4) Check for local changes (I'm using last cloud sync timestamp to check what records was modified after).
2.5) Upload records to cloud kit database
2.6) Refresh zone change token again.
I highly recommend Nick Harris article series: https://nickharris.wordpress.com/2016/02/09/cloudkit-core-data-nsoperations-introduction/
You'll find there implementation and design concepts. It worth reading. I hope somebody'll find all of this helpful.
As of iOS 13 there is a super helpful method in Core Data called NSPersistentCloudKitContainer. This method will automatically take care of all local caching and syncing with iCloud on private databases. You can set it up by simply changing
static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "ShoeTrack")
container.loadPersistentStores(completionHandler: {
(storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
to
static var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "ShoeTrack")
container.loadPersistentStores(completionHandler: {
(storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
You will have to modify the Core Data Model file in your project and check "Use with CloudKit on each configuration.

How to prevent using cache when new data is available?

I'm caching data in CoreData within my app to reduce updating request when there's nothing new.
Here's my caching logic in pseudo code:
if cacheExistsInCoreData {
if cacheIsOutdated {
loadDataFromRemoteAndCacheItWithCurrentDate()
}else {
useCache()
}else {
loadDataFromRemoteAndCacheItWithCurrentDate()
}
How I check if cache is outdated:
func checkIfCacheIsOutdated {
if lastCachedDate isOrderThan selfDefinedCheckingDate {
return true // need to load new data
}else {
return false // just use cache
}
}
This mechanism works fine almost all the time.
While in rare situation I find my program caches the wrong data with a right date, which means user might see the older data and could not get update when new one is available.
If there's nothing wrong with my caching logic, I wonder if the reason could be that when the remote data is fetched by my app before it gets updated and then gets stored in core data with the latest date time.
A cache in core data includes:
data(provided by remote server) //nothing I can do with it...
date(provided by me using NSDate())
How can I make sure if the two objects are correctly connected (latest data with current time) before storing them?
Assuming you are calling a web api, most of the web apis would return cache headers. It is safe to cache the data based on cache header value, that way there wont be any stale cache.
The solution that I came across that works for me is to set the stalenessInterval on the Main Queue NSManagedObjectContext as follows inside of your extension:
objectContext.stalenessInterval = 0.0;
This tells the context in the extension to fetch new data every time and ignore the cache.

Parse - How to save a html file in iOS device

I'm using Parse for my iOS app and I'm trying to locally save my HTML file from Parse core data to my device's local storage. I'm doing this so the user can still access the data even when they're offline. I've read about local datastore and cache policy and have tried both. With local datastore, it doesn't let me load the HTML to my UIWebView but it loads everything else (probably due to the HTML needing internet to access it). Cache policy actually works but only accesses the more recent items (just as a cache should work).
I am asking on here to see what I can do to locally save these HTML files from Parse so a user can access them even when the internet is gone.
Thanks!
You can use PFFile's method getData to get NSData and save it to NSUserDefaults:
let data = file.getData()
NSUserDefaults.standardUserDefaults().setObject(data, forKey: file.name)
...and when you need to read it:
let data = NSUserDefaults.standardUserDefaults().dataForKey(file.name)
This can help you display the page in WebView: How do I convert HTML NSData to an NSString?
Update: UIWebView has method to show NSData: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIWebView_Class/#//apple_ref/occ/instm/UIWebView/loadData:MIMEType:textEncodingName:baseURL:

Resources