Handling errors in addPersistentStoreWithType - objective-c

I am trying to find information on handling errors when creating a persistent store coordinator on the iPhone. I have implemented lightweight migration
NSError *error = nil;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() 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 persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES}
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
This is based on the code from Apple with the added support for lightweight migration.
I can't find any information on handling errors if the application would still encounter an error here. It seems to me that if the database cannot be created the application can't be used at all.
Do I just ask the user to try reinstalling the application and display relevant infromation ?
Can I keep the abort() statement while adding a prompt about the error or will this cause the application to be rejected by Apple ?

Calling abort() in this situation is out of question. Any app that crashes will be rejected by Apple. And it does not solve the problem: Starting the app again will find the same store file and therefore fail again.
For the same reason, reinstalling the app does not help, and it would be a bad user experience.
Of course, the situation should not occur if the migration has been tested. But if this fatal error occurs and your app cannot open the database, you have to create a fresh database.
The exact steps to take depend on what is stored in the database and if/how you can recover the data. So you could
remove the old database file with [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil], or copy a default database file from your programs resources to storeURL,
call _persistentStoreCoordinator addPersistentStoreWithType:... again to open the new database.
perhaps fill the database again with data from a server, or whatever has to be done to recreate the data.

Related

Core data crash occurring since iOS 10

I've been struggling to resolve a locally-unreproducible crash since iOS 10 within the sqlite/coredata library. It's occurring very infrequently - somewhere in the realm of 0.2% in production.
What I know (or at least suspect):
It only happens on iOS 10 and above.
Most often occurs while saving the context, but may also be occurring during a core data fetch request.
Occurring fairly rarely (ballpark rate of 0.15% of sessions)
I have run stress tests with concurrency debug flags enabled, as well as some of the xcode memory management tools. No issues detected.
Tested for memory leaks.
I've never been able to reproduce this stacktrace in a development environment.
No exceptions are being thrown prior to the crash. The entire code is wrapped.
This action is performed within a block, and the app is in the foreground.
Occurs seemingly at random during normal app operation. (Not at initialization time or anything special)
It's a SIGABRT crash
libsystem_kernel.dylib0x00000001841c3014 __pthread_kill+4
libsystem_c.dylib0x0000000184137400 abort+136
libsystem_malloc.dylib0x0000000184207a5c nanozone_error+328
libsystem_malloc.dylib0x0000000184209028 nano_realloc+644
libsystem_malloc.dylib0x00000001841fb240 malloc_zone_realloc+176
libsqlite3.dylib0x0000000185730c34 sqlite3_value_text+1220
libsqlite3.dylib0x0000000185777f38 sqlite3_rekey+1564
libsqlite3.dylib0x000000018578df78 sqlite3_rekey+91740
libsqlite3.dylib0x0000000185791c88 sqlite3_rekey+107372
libsqlite3.dylib0x000000018571df98 sqlite3_log+86448
libsqlite3.dylib0x0000000185757780 sqlite3_bind_int+11992
libsqlite3.dylib0x00000001856f1c80 sqlite3_exec+35188
libsqlite3.dylib0x00000001856eb608 sqlite3_exec+8956
libsqlite3.dylib0x00000001856ea838 sqlite3_exec+5420
libsqlite3.dylib0x00000001856e9f24 sqlite3_exec+3096
libsqlite3.dylib0x00000001856e9ae0 sqlite3_exec+2004
CoreData0x00000001874f1284 -[NSSQLiteConnectionprepareSQLStatement:]+468
CoreData0x00000001876166f0 -[NSSQLiteConnectionupdateRow:forRequestContext:]+496
CoreData0x00000001876c3430 _writeChangesForSaveRequest+1596
CoreData0x00000001876c4958 _executeSaveChangesRequest+312
CoreData0x00000001876ba7f4 -[NSSQLSaveChangesRequestContextexecuteRequestUsingConnection:]+40
CoreData0x00000001875cdaf8 __52-[NSSQLDefaultConnectionManagerhandleStoreRequest:]_block_invoke+256
libdispatch.dylib0x000000018407e1bc _dispatch_client_callout+12
libdispatch.dylib0x000000018408b7f0 _dispatch_barrier_sync_f_invoke+80
CoreData0x00000001875cd994 -[NSSQLDefaultConnectionManagerhandleStoreRequest:]+204
CoreData0x0000000187693f80 -[NSSQLCoreDispatchManagerrouteStoreRequest:]+284
CoreData0x00000001875fb7e4 -[NSSQLCoredispatchRequest:withRetries:]+196
CoreData0x00000001875f7560 -[NSSQLCoreprocessSaveChanges:forContext:]+200
CoreData0x00000001874f8360 -[NSSQLCoreexecuteRequest:withContext:error:]+744
CoreData0x00000001875da2f4 __65-[NSPersistentStoreCoordinatorexecuteRequest:withContext:error:]_block_invoke+3248
CoreData0x00000001875d2bf0 -[NSPersistentStoreCoordinator_routeHeavyweightBlock:]+272
CoreData0x00000001874f7f20 -[NSPersistentStoreCoordinatorexecuteRequest:withContext:error:]+404
CoreData0x00000001875195ac -[NSManagedObjectContextsave:]+2768
Here's what the code generally looks like:
NSManagedObject *object = [[MyManagedObject alloc] init];
// This is actually within the init method
NSEntityDescription *desc = [NSEntityDescription entityForName:NSStringFromClass(object.class)
inManagedObjectContext:context];
[object initWithEntity:desc insertIntoManagedObjectContext:nil];
// later on...
[context performBlock:^{
// Fetch another (different) object from core data
NSArray *fetchResults = [context executeFetchRequest:request error:&error];
// Changing some properties of object with values from fetched results
object.property = fetchResults[0].property;
// insert the object
[context insertObject:object];
// save the context
[context save:&error]
}
Any ideas would be greatly appreciated.
Update:
I found this release note that coincided with iOS 10.2, which may have caused some existing issue(s) to be exposed. It's not clear what the change was, or how it might cause problems, but it seems pretty likely that this is related somehow.
https://support.apple.com/en-us/HT207422
Impact: Processing malicious strings may lead to an unexpected application termination or arbitrary code execution
Description: A memory corruption issue existed in the processing of strings. This issue was addressed through improved bounds checking.
CVE-2016-7663
If the majority of your code base is, as your suggest, asynchronous and you are trying to perform a synchronous save operation within this asynchronous block, there is every reason to suspect that is why you're receiving the NSPersistentStoreCoordinator error in the error message.
The key is the issue with the NSPersistentStoreCoordinator (PSC) failing to properly coordinate the data save. Unless I'm mistaken the error message identifies that the PSC is locked when you ask the PSC to respond to the call to save for that MOC.
In my humble opinion your problem still most likely stems from your call to performBlock... in this code you're performing a fetch request, then updating a property, then inserting the object back into the MOC, then saving, all in the same block. These are very different functions taking different amounts of processing power and time, all dumped into one single concurrency block.
Also, how you instantiate a property when using concurrency and blocks is important. You may need to check where in your code is most appropriate to instantiate your properties.
So some questions...
Do you need every line of this code in a performBlock? Consider that, unless you're blocking your UI, the fetch request and the update to your property may be ok in code outside the call to performBlock.
If you do need every line of this code in a core data concurrency block such as performBlock, have you considered instead embedding your call to save in a "block-within-a-block" and using performBlockAndWait?
The Apple developer website has an example of embedding a save call into a performBlockAndWait block, included (in part) following:
NSManagedObjectContext *moc = '…; //Our primary context on the main queue
NSManagedObjectContext *private = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[private setParentContext:moc];
[private performBlock:^{
'Complete your fetch request and update the managed object's property.
NSError *error = nil;
if (![private save:&error]) {
NSLog(#"Error saving context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
}
[moc performBlockAndWait:^{
NSError *error = nil;
if (![moc save:&error]) {
NSLog(#"Error saving context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
}
}];
}];
If you're able to update your question with a little more code and a more detailed description I might be able to provide a more accurate fix for your specific problem.
Also I'd recommend you do some research...
Despite the age of the book, the concepts of concurrency are still very well explained in "Core Data, 2nd Edition, Data Storage and Management for iOS, OS X, and iCloud" (Jan 2013 from The Pragmatic Bookshelf) by Marcus S. Zarra, and in particular Chapter 4 titled "Performance Tuning” and Chapter 5 titled "Threading”.
Another valuable book on core data from Apress publishers – "Pro iOS Persistence Using Core Data", by Michael Privat and Robert Warner.
No longer occurs as of iOS 10.3. Root cause is still unknown. Assuming some iOS memory management break in 10.2, with a fix in 10.3.

Why is my restore of the Core Data files not found by app?

UPDATED: I am trying to backup and restore my Core Data files (using journaling and MagicalRecord based on issue #444). When running under the iOS simulator, I know that the files are moved each time from the last execution of the app. Here is the complete code (I put it here because it's lengthly and now it's formatted and syntax is highlighted, making it easier to read).
I can find the new location when the app starts, retrieve my stored files and do my restore from the files in the Documents directory, placing them in the correct new location as determined by console output from NSLog statements (it doesn't show the -shm file being restored but it was to the same location).
AppDelegate-sqliteFilePath: /Users/rolfmarsh/Library/Developer/CoreSimulator/Devices/1EE69744-255A-45CD-88F1-63FEAD117B32/data/Containers/Data/Application/B424CA1A-D41C-488D-A7E9-0F13CB2244B3/Library/Application Support/SalonBook
SVC-sqliteFilePath: /Users/rolfmarsh/Library/Developer/CoreSimulator/Devices/1EE69744-255A-45CD-88F1-63FEAD117B32/data/Containers/Data/Application/B424CA1A-D41C-488D-A7E9-0F13CB2244B3/Library/Application Support/SalonBook
documentsBasePath: /Users/rolfmarsh/Library/Developer/CoreSimulator/Devices/1EE69744-255A-45CD-88F1-63FEAD117B32/data/Containers/Data/Application/B424CA1A-D41C-488D-A7E9-0F13CB2244B3/Documents
starting - storePath: /Users/rolfmarsh/Library/Developer/CoreSimulator/Devices/1EE69744-255A-45CD-88F1-63FEAD117B32/data/Containers/Data/Application/B424CA1A-D41C-488D-A7E9-0F13CB2244B3/Library/Application Support/SalonBook/saori.sqlite
Finished! storePath: /Users/rolfmarsh/Library/Developer/CoreSimulator/Devices/1EE69744-255A-45CD-88F1-63FEAD117B32/data/Containers/Data/Application/B424CA1A-D41C-488D-A7E9-0F13CB2244B3/Library/Application Support/SalonBook/saori.sqlite-wal
When I start the restore, I issue:
[MagicalRecord cleanUp]; // disable Core Data
When I finish the restore, I issue the following:
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:#"saori.sqlite"]; // enable Core Data migration
defaultContext = [NSManagedObjectContext MR_defaultContext]; // set default NSManagedObjectContext for MagicalRecord
When I look at the records that should be in the Core Data store (using SQLite Browser), they are in the sqlite file (saori.sqlite) but they do not appear in the app! It's like the MR stack wasn't set up correctly or the default context is farkeled somehow. Any idea how to fix this?
OK, so after having a bit of a prod and a poke around, I believe +[MagicalRecord cleanUp] needs some love. Basically, the persistent store coordinator is being released without first removing the SQLite persistent store.
As you noted in another conversation we had about this, you need to reset the managed object context:
[[NSManagedObjectContext MR_defaultContext] reset];
Then iterate over all of your persistent stores to remove them:
NSError *error;
NSPersistentStoreCoordinator *persistentStoreCoordinator = [NSPersistentStoreCoordinator MR_defaultStoreCoordinator];
for (NSPersistentStore *store in [persistentStoreCoordinator persistentStores]) {
BOOL removed = [persistentStoreCoordinator removePersistentStore:store error:&error];
if (!removed) {
NSLog(#"Couldn't remove persistent store: %#", error);
}
}
After that, it should be safe to call [MagicalRecord cleanUp], and then restore the entire stack as you have been (although you could opt to just attach the updated persistent store to the coordinator rather than completely blowing everything away).

Uploading From App to Server in IOS

I know that conventionally for an app to interact with the internet, it must use a web service to exchange information. However, how would one upload data(photos, text, audio recordings etc.etc.) from app to server(which holds data for all user accounts)? I know some people use an email-to-server tactic from research but even then it sounds ineffective and slow. How do apps such as Instagram upload so fast? I am trying to replicate that sort of uploading. Please guide me in the right direction.
Thanks for the help!
You should definitely look into AFNetworking. Here is an example of my uploading an image to a php web service:
NSData *imageData = UIImagePNGRepresentation(pageImage);
AFHTTPClient *client= [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:#"http://www.SERVER.com"]];
//You can add POST parameteres here
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
author, #"author",
title, #"title",
nil];
NSMutableURLRequest *request = [client multipartFormRequestWithMethod:#"POST" path:#"/PATH/TO/WEBSERVICE.php" parameters:params constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
//This is the image
[formData appendPartWithFileData: imageData name:#"cover_image" fileName:#"temp.png" mimeType:#"image/png"];
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
//Setup Upload block to return progress of file upload
[operation setUploadProgressBlock:^(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
float progress = totalBytesWritten / (float)totalBytesExpectedToWrite;
NSLog(#"Upload Percentage: %f %%", progress*100);
}];
//Setup Completeion block to return successful or failure
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString *response = [operation responseString];
NSLog(#"response: [%#]",response);
//Code to run after webservice returns success response code
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", [operation error]);
//Code to Run if Failed
}];
[operation start];
Edit- Also I use MBProgressHUD to display to the user the uploading on longer uploads.
As you might know, upload speed is always bound to the speed of the connection type you're using. Even the best upload technique will be slow when the connection is slow (GPRS for example, or EDGE, even 3G can be slow if network coverage is not good).
To upload large sets of data faster/better one thing you could do is compressing the data you're sending using ZIP or any other file compression format you wish or even develop you own compression algorithm (you might not want to do that ;-)).
If you want to reduce the overhead of HTTP/HTTPS connections for example, you can write your very own protocol for data exchange, implement it on the client/server side and upload faster. This will be a lot of work as you have to do all the implementation work not only for the protocol itself as you need to add security etc. But even if you choose to create a protocol, as said in the beginning, it will be slow if the connection is slow.
Update: A presenatation by Mike Krieger (Co-Founder of Instagram) where he covers your question just crossed my way https://speakerdeck.com/u/mikeyk/p/secrets-to-lightning-fast-mobile-design?slide=1.
The reason why you think it's so fast is, that they're updating the UI before the request (the Upload in this case) even completes. This is what Mike describes as "being optimistic". If the request fails you can still notify the user, but in the meantime make him feel productive and act like the request completed successfully.
This is a pretty open ended question but here are a few things to look at:
"Uploading fast" depends on the user's connection and server bandwidth so I won't get into that.
You can upload photos (and other files) by creating NSData objects and attaching them to a POST request. There is already a ton of sample code for uploading NSData but to convert a UIImage you will do the following:
NSData *imageData = UIImagePNGRepresentation(image);
You can do this using the built in Cocoa classes (NSMutableURLRequest) and with 3rd party networking classes (such as AFNetworking - just scroll down to file uploads).
When I send simple data to my webserver, I use the following approach: Use the ASIHttpRequest framework for connecting to your sever. Send the data in HTTP Post body, which is easy to do in the ASIHttpRequest framework. You will want to convert your data to either XML or JSON(use the SBJson framework for this) before sending it. I then write php scripts that parse the json or xml and then input this data into my database with custom SQL scripts. I can give you code snippets if you need them for any of these procedures...
It seems to me that, with your first sentence, you've basically answered your own question.
You need something on your server to receive the files and then you write client code to match. It could be as simple as ftp or as complex as a custom protocol depending on the security and control that you need.

AFNetworking HTTP PUT Request

I have the following API call:
URL: /api/some-call
Method: PUT
PARAMS: No params
Its just a simple PUT method. I am trying to use AFNetworking to do that and unfortunately, I am failing. Here's what I have right now:
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSMutableURLRequest *req = [httpClient requestWithMethod:#"PUT" path:#"" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:req];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Success");
} failure: ^(AFHTTPRequestOperation *operatn, NSError *error) {
NSLog(#"Failure");
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
This is however, not working. Why is that? Furthermore, what is path supposed to be in a PUT request? I've tried several things and this is what I have now at the end, which I believe should be close to what is correct.
One last question: AFNetworking does not use ARC. Does that mean I still need the autorelease at the end of the NSOperationQueue statement?
EDIT:
Here is error NSLog: Failure Error Domain=com.alamofire.networking.error Code=-1011 "Expected status code in (200-299), got 409" UserInfo=0x7a91fb0 {NSErrorFailingURLKey=*the url*/api/some-call, NSLocalizedDescription=Expected status code in (200-299), got 409}
Well. You are getting a 409 error code.
Quoted from http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html :
10.4.10 409 Conflict
The request could not be completed due to a conflict with the current
state of the resource. This code is only allowed in situations where
it is expected that the user might be able to resolve the conflict and
resubmit the request. The response body SHOULD include enough
information for the user to recognize the source of the conflict.
Ideally, the response entity would include enough information for the
user or user agent to fix the problem; however, that might not be
possible and is not required.
Conflicts are most likely to occur in response to a PUT request. For
example, if versioning were being used and the entity being PUT
included changes to a resource which conflict with those made by an
earlier (third-party) request, the server might use the 409 response
to indicate that it can't complete the request. In this case, the
response entity would likely contain a list of the differences between
the two versions in a format defined by the response Content-Type.
Which means the error is caused by your server not with your code. unless you have provided some wrong parameters.
Well. As for the question regarding the "what is the path supposed to be in PUT".
Normally I'll put baseURL as the domain name of the server.
Which is something like
http://localhost
then i'll put the path to be something like
#"the/rest/of/the/api/url"
then it's easier to switch between development and production servers with just a switch of a baseURL. :)
And for your last question, "AFNetworking does not use ARC. Does that mean I still need the autorelease at the end of the NSOperationQueue statement?"
Does that mean your project is using ARC with AFnetworking, or AFNetworking WITHOUT ARC.
if it's ARC with AFNetworking, you don't have to. Take a look at this
https://github.com/AFNetworking/AFNetworking#arc-support
if it's non-ARC with AFNetworking, you basically have to do all the memory management yourself. :)
Hit me up again if you need more info and i'll edit accordingly. :)
Hope i've helped in someway.

Updating live app in store to use Core Data that currently does not use Core Data

I currently have an app in the store which is SQLite-backed and does not use Core Data. In the past when I have wanted to release an update that had SQLite changes, the update would include some code that would detect the version of the app, and programmatically update the tables if necessary. Now I am working on an update that uses Core Data. I don't care about any of the old data that is currently live, and I know how I can delete all the old SQLite tables programmatically. Are all the Core Data model files included in the update binary, or do I have to programmatically generate some or all of the Core Data model? Will the .xcdatamodeld be included with the binary? Any other pitfalls I should be wary of?
Thanks for your help
With core data it can be quite tricky when updating to a new version. I have experienced many times that even the slightest changes in the core data model cause weird app behaviour (understandable to some extend). The easiest way to avoid any unwanted sideeffects is to just change the name. Here the code
- (NSPersistentStoreCoordinator *) persistentStoreCoordinator {
// D_IN;
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
// Allow inferred migration from the original version of the application.
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"myData073.sqlite"]];
NSError *error = nil;
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl
options:options error:&error]){
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
// D_OUT;
return persistentStoreCoordinator;
}
So all you really have to do is to change the name myData073.sqlite to for example myData074.sqlite
The data model description file is not really part of the binary, but the model behind it with all the classes and access methods surely is. You do not need to worry about that. ps even during development I change the name frequently, otherwise one might waste lots of time looking for coding errors that are not really there...
An App Store update replaces the entire app bundle, so everything in your app bundle, including any Core Data model, will be included. You can test this by installing an Ad Hoc or developer build over an App Store build on your own device (without deleting first). In the compiled App, the model files are compiled or processed, and have different extensions (.momd, .mom). So you'll see those instead of .xcdatamodeld or .xcdatamodel.
Other pitfalls: Now, the Core Data data file is not part of your bundle. If you want the updated app to start with an empty database, you don't have to do anything special. But if you want to install/update with a prepopulated database, you have to figure out a way to get that database into a read-write location on the device. Basically, you generate a Core Data file, included it in the app bundle as part of your Xcode project, then at first launch, programmatically copy it somewhere in your Documents or Library folders (using the correct Cocoa API for finding these).
Pitfall #2: modifying an empty core data file with SQLLite on the Mac is suggested on some web sites, but specifically dis-recommended by Apple. Instead, you might write an iOS app that puts data into core data and run it in the simulator. How do I initialize a store with default data?
As user387184 indicated, your next update that changes the model on an existing Core Data database can get tricky. Try to get the model as right as you can the first time.