Manual migration with MagicalRecord - core-data

I'm implementing a migration of my CoreData store, where I replace a string attribute with a BOOL attribute: when the string was "0", the bool should be "YES", and in all other cases the bool should be "NO". Sounds simple enough, but I think I still need to add a mapping model. I added that in Xcode, and implemented createDestinationInstancesForSourceInstance:
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance entityMapping: (NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError **)error
{
NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName] inManagedObjectContext:[manager destinationContext]];
NSString *oldValue = [sInstance valueForKey: #"oldString"];
NSNumber *newValue = #(NO);
if ([oldValue integerValue] == 0)
newValue = #(YES);
[newObject setValue: newValue forKey: #"newBool"];
[manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];
return YES;
}
However this never gets called.
Since I'm using MagicalRecord, I also am using:
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed: #"storename.sqlite"];
I read I also need to use: NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption: #(YES), NSInferMappingModelAutomaticallyOption: #(NO)}; when I init my store, but how do I use that with MagicalRecord?
UPDATE: So MR uses MR_autoMigrationOptions to set the migration options. Is there any way to modify these to support a manual migration ?

In order to perform a manual migration you will need to use:
[MagicalRecord setupManuallyMigratingStackWithSQLiteStoreNamed: #"storename.sqlite"];

Related

Should NSFetchRequest be explicitly performed on context queue?

Just a little question...
When performing CoreData requests (in objective-C), do I have to make them on the context queue explicitly, or are they assumed to do that by themselves?
In other words can I just write:
NSArray *results = [context executeFetchRequest:request error:nil];
Or do I have to write (if I want to avoid issues):
__block NSArray *results = nil;
[context performBlockAndWait:^{
results = [context executeFetchRequest:request error:nil];
}];
Thanks!
You can never assume that Core Data will use the correct queue. You always need to tell it via one of the “perform” methods. The only exception is if you have a main-queue context and you know your code is running on the main queue. You can skip “perform” in that one and only case.
You mention using a convenience method to do a fetch, using “perform” inside the method. That’s fine, but don’t forget that you also need to use “perform” any time you use any of the fetched objects. For example, you can’t use property values from fetched objects unless you also use “perform” (again with the one exception I mentioned).
So just to be sure Tom:
All my CoreData objects (subclasses of NSManagedObject) have their CoreData properties as private (with a prefix "cd_"). I never expose them and use wrapper properties instead:
- (nullable NSString *) email {
return self.cd_email;
}
I've created a method on NSManagedObject:
- (id) safeValueForProperty: (SEL)property {
__block id value = nil;
[self.managedObjectContext performBlockAndWait:^{
value = [self valueForKey:NSStringFromSelector(property)];
}];
return value;
}
So now I can use (from any thread in theory):
- (nullable NSString *) email {
return [self safeValueForProperty:#selector(cd_email)];
}
(and of course the same for setters).
Am I doing right?

updating NSManagedObject doesn't call NSFetchedResultsControllerDelegate using MagicalRecord

I have a model with this one to many relationShip:
Order -->> LineItem
I display LineItems in UITableViewCells:
I use UIPickerView for changing quantity of LineItems.
GOAL=> by changing picker value, subTotal be recalculated again.
the problem is here by updating lineItem, NSFetchedResultsController Delegate doesn't call (where I can reconfigure the cell again and display updated data). but when I update Order e.g set it as completed NSFetchedResultsController Delegate methods will be called.
why by updating lineItem doesn't affect delegates methods to be called?
I use magicalRecord and here is how I get NSFetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
else
{
_fetchedResultsController = [Order fetchAllSortedBy:#"orderDate" ascending:YES withPredicate:nil groupBy:nil delegate:self];
}
return _fetchedResultsController;
}
the way I setup table view:
ConfigureCellBlock configureCell = ^(OrderDetailsCell *cell, LineItem *lineItem)
{
[cell configureForLineItem:lineItem];
};
//set fetchedresults controller delegate
Order *order = [[self.fetchedResultsController fetchedObjects] lastObject];
NSArray *lineItems = [order.lineItems allObjects];
self.ordersDataSource = [[ArrayDataSource alloc] initWithItems:lineItems cellIdentifier:#"lineItemCell" configureCellBlock:configureCell];
self.tableView.dataSource = self.ordersDataSource;
configuring cell:
- (void)configureForLineItem:(LineItem *)lineItem
{
self.menuItemName.text = lineItem.menuItemName;
self.price.text = [lineItem.unitPrice stringValue];
self.quantity.text = [lineItem.quantity stringValue];
self.totalPrice.text = [lineItem.totalPrice stringValue];
self.pickerController.model = lineItem;
self.picker.delegate = self.pickerController;
self.picker.dataSource = self.pickerController;
[self.picker setSelectedNumber:lineItem.quantity];
}
does fetching obj1 then updating obj3 cause the NSFRC delegate methods to be called?
The FRC will only observe changes to the objects that it is directly interested in, not any of the objects that they are related to.
You should configure your own observation, either directly with KVO or to the context being saved, and use that to trigger a UI refresh.

Load a previous model version

I am loading a NSManagedObjectModel model with the initWithContentsOfURL: constructor like this:
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"MyDocument" withExtension:#"momd"];
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
However this only gives me access to the latest/current version of a model. Is it posible to load previous versions with the same momd file? how?
Actually you can load an older version with:
- (NSManagedObjectModel *)managedObjectModelForVersion:(NSString *)version
{
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:[NSString stringWithFormat:#"AppModel.momd/AppModel %#",version] withExtension:#"mom"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return model;
}
Just replace AppModel with your model name.
I'm using this to get myself out of a sticky manual migration situation involving iCloud. Searched high and low and couldn't find this anywhere.
If you just want to load the version of the model that's compatible with a particular existing store try:
NSError *error = nil;
NSDictionary *storeMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:storeURL
error:&error];
NSManagedObjectModel *oldManagedObjectModel = [NSManagedObjectModel mergedModelFromBundles:[NSArray arrayWithObject:[NSBundle mainBundle]]
forStoreMetadata:storeMetadata];
Note that if you use XCode version identifiers for your data model versions, the persistent store's current version identifiers are accessible through the NSStoreModelVersionIdentifiersKey entry in the store metadata dictionary.
As far as loading a particular arbitrary version is concerned, the mom files are typically located under the momd directory in your app's bundle, so you could enumerate them using NSFileManager. I believe to find one with a particular version identifier you would have to use NSManagedObjectModel's initWithContentsOfURL: initializer and then inspect the versionIdentifiers property, or use the isConfiguration:compatibleWithStoreMetadata: instance method to determine compatibility.
Made the solution offered by #Schoob into a category, because they rock.
#interface NSManagedObjectModel (version)
+ (NSManagedObjectModel *)modelFromBundle:(NSBundle *)bundle name:(NSString *)modelName version:(NSString *)version;
#end
#implementation NSManagedObjectModel (version)
+ (NSManagedObjectModel *)modelFromBundle:(NSBundle *)bundle name:(NSString *)modelName version:(NSString *)version
{
if(!bundle)
bundle = [NSBundle mainBundle];
NSString *resource = [[modelName stringByAppendingPathExtension:#"momd"] stringByAppendingPathComponent:version];
NSURL *modelURL = [bundle URLForResource:resource withExtension:#"mom"];
NSAssert(modelURL,#"Unable to find MOM - %#",resource);
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSAssert(model,#"Unable to load MOM at URL- %#",modelURL);
return model;
}
#end
Swift version. Replace file name.
import CoreData
extension NSManagedObjectModel
{
class func model(forVersion version: Int) -> NSManagedObjectModel?
{
if let fileUrl = Bundle.main.url(forResource: "Model.momd/Model \(version)", withExtension: "mom")
{
return NSManagedObjectModel(contentsOf: fileUrl)
}
return .none
}
}
No, it is not foreseen that this is possible. I deduce that from the NSManagedObjectModel documentation, where it says discussing the property versionIdentifiers:
This value is meant to be used as a debugging hint to help you determine the models that were combined to create a merged model.
So it does not seem you are supposed to use previous model versions for your program logic.

Xcode 4 static code analysis question

This is the follow up to my question earlier about the Xcode 4 static analyzer. It is not specifically a problem since I have the code now working as it needs to, but I am just wondering how things are working behind the scenes. Consider the following code:
- (IBAction)cameraButtonPressed:(id)sender
{
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] == NO)
{
return;
}
UIImagePickerController *cameraUI = [[UIImagePickerController alloc] init];
cameraUI.sourceType = UIImagePickerControllerSourceTypeCamera;
cameraUI.allowsEditing = NO;
cameraUI.delegate = self;
[self presentModalViewController:cameraUI animated:YES];
NSString *theString = [[NSString alloc] initWithString:#"cameraButtonPressed done"];
NSLog(#"%#", theString);
}
To me, the way this code looks, there are two objects (cameraUI and theString) that need to be released. However, the analyze function correctly identifies that only theString needs to be released at the end of the method, even though both objects are returned from alloc-init, which in my experience has always meant that you release when you are done.
The question I have here is, how does the static code analyzer know not to flag cameraUI as an issue?
I would call this a bug with the static analyzer. The UIImagePickerController instance assigned to cameraUI should be released or autoreleased in a non-garbage-collected environment (like iOS).

Update/Edit coreData managed object

I'm trying to edit a CoreData object when a user clicks on a cell in a UITableView based on the cell.accessoryType to show if the item has been clicked. Here is the current code.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSManagedObject *itemToUpdate = [groceryArray objectAtIndex:indexPath.row];
NSLog(#"updating: %#", itemToUpdate);
if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
cell.accessoryType = UITableViewCellAccessoryNone;
itemToUpdate.purchased = NO;
}else {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
itemToUpdate.purchased = YES;
}
// Commit the change.
NSError *error;
if (![managedObjectContext save:&error]) {
// Handle the error.
NSLog(#"Saving changes failed: %#", error);
}
}
It seems to be selecting the right object because the NSLog() will show the correct item but when I try to update using the dot notation e.g. "itemToUpdate.purchased = YES;" the compiler throws an error "request for member 'purchased' in something not a structure or union".
I know I'm probably doing this wrong (my first project in xcode) - any advice would be greatly appreciated!
Thanks
Have you tried:
[itemToUpdate setValue:[NSNumber numberWithBool:NO] forKey:#"purchased"]
form?
I always subclass NSManagedObject and the dot notation works for declared properties. But you might try this "older" notation to see if that works.
I suppose you created a custom subclass of 'NSManagedObject' with 'purchased' as one of the properties. Declare 'itemToUpdate' as an object of this subclass, rather than NSManagedObject:
YourCustomSubclassOfNSManagedObject *itemToUpdate = [groceryArray objectAtIndex:indexPath.row];

Resources