Nearing release, implement shiny things whilst waiting.

As the project nears release my thoughts turn to what will be needed in the near future and how best to solve an intermittent bug that’s present now. The bug was resolving by doing something I should have done ages ago, be consistent with how I use data in table views. When developing the app the most complicated view did not use NSFetchedResultsController as it required 2 of them, one to drive each section.
Implementing this bought another problem to the fore, how to identify recipes uniquely in the DB? The obvious method is to use the objectID that core data uses but this changes until the recipe is finalised by saving it. The problem there is driving the sections from NSFetchResults whilst the recipe is being created. Each addition can change the objectID invalidating a simple predicate such as:

[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"recipe== %@",[recipe objectID]]];

The simplest solution is to create the recipe and immediately save it, blank, to the database then present it to the view. I have issues with that, crashes could lead to blank recipes polluting the integrity of the database and all sorts of problems. My minimum requirement is for the recipe to have a title (non unique) at least. So the solution I’ve known was needed has been implemented, UUID’s. For ease of use you want something that:

  • Adds UUIDs as part of the core db migration when the new managed object model is loaded.
  • Automatically creates and adds a UUID to a newly inserted recipe.

To help with this I created a helper class with the function:

+ (NSString*)UUIDString {
    CFUUIDRef theUUID = CFUUIDCreate(NULL);
    CFStringRef string = CFUUIDCreateString(NULL, theUUID);
    CFRelease(theUUID);
    return [NSMakeCollectable(string) autorelease];
}

To ensure the UUID is added to the core data object every time a recipe is created you can add this to the NSManagedObject subclass:

- (void)awakeFromInsert {
	[super awakeFromInsert];
	[self setValue:[KookaDIS UUIDString] forKey:@"uuid"];
}

Now for the method to update the database upon launch. Assuming you have automatic migrations turned on with the NSMigratePersistentStoresAutomaticallyOption option you need to create a NSEntityMigration subclass. I didn’t choose the best name for mine but it looks like this:

#import 

@interface AddUUIDToRecipeEMP : NSEntityMigrationPolicy {

}

@end
#import "AddUUIDToRecipeEMP.h"
#import "KookaDIS.h"

@implementation AddUUIDToRecipeEMP

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject*)src 
									  entityMapping:(NSEntityMapping*)map 
											manager:(NSMigrationManager*)mgr 
											  error:(NSError**)error
{
	NSManagedObjectContext *destMOC = [mgr destinationContext];
	NSString *destEntityName = [map destinationEntityName];
	
	NSManagedObject *dest = [NSEntityDescription insertNewObjectForEntityForName:destEntityName
														  inManagedObjectContext:destMOC];
	
	NSArray *keys = [[[src entity] attributesByName] allKeys];
	NSDictionary *values = [src dictionaryWithValuesForKeys:keys];
	
	[dest setValuesForKeysWithDictionary:values];
	[dest setValue:[KookaDIS UUIDString] forKey:@"uuid"];
	// 511 is binary 111 111 111 or chmod 777.
	[dest setValue:[NSNumber numberWithInt:511] forKey:@"permissions"];
	
	[mgr associateSourceInstance:src withDestinationInstance:dest forEntityMapping:map];
	
	return YES;
}

@end

Ignore the permissions setValue for now, that’s for when we’ve covered the new UUID updates. So we now have a custom entity migration class. To use it create a new file in your project choosing “iOS->Resource->Mapping Model”. Within the mapping model click on the pre-made entity mapping that is relevant for you. In the far right hand pain you should see a Custom Policy field, simply type the name of the class you made in there. That’s it. Afaict this turns off any other automatic migration for that record so if you have other alterations you will need to code those changes manually too.

The above changes mean that the predicate for getting the data for the sections is consistent whether the recipe is new or not. It becomes:

[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@”recipe.uuid == %@”,recipe.uuid]];

The other thing I have considered is the potential inclusion of other recipes with permission and permissions for user uploads to the server. So I implemented a quick bitwise permissions system based on linux simple user/group/world permissions for read/write/execute. I’ve modified it to creater/owner/world. Owner would be someone with explicit permission or (heaven forbid) in app purchase and world would be any other download/import.

I felt that storing the data as an int is the simplest solution but to present that to the rest of the program in such a way would be anachronistic in such a modern language so I needed two routines to convert the info from an int to an NSDictionary and vice versa. Thus these two routines were born and added to the afore mentioned class:

NSString * const kKDISShareEnabled = @"KookaDocumentShareable";
NSString * const kKDISEditEnabled = @"KookaDocumentEditable";
NSString * const kKDISPrintEnabled = @"KookaDocumentPrintable";

NSString * const kKDISCreator = @"KookaDocumentCreator";
NSString * const kKDISOwner = @"KookaDocumentOwner";
NSString * const kKDISWorld = @"KookaDocumentWorld";


+ (NSDictionary *)permissionsDictionaryFromNumber:(NSNumber *)permissions {
	NSMutableDictionary *dictionary = [[[NSMutableDictionary alloc] init] autorelease];
	int value = [permissions intValue];
	int creator = value & 0x7;
	int owner = value >> 3 & 0x7;
	int world = value >> 6 & 0x7;
	
	NSNumber *cs = [NSNumber numberWithInt:creator & 0x1];
	NSNumber *ce = [NSNumber numberWithInt:creator >> 1 & 0x1];
	NSNumber *cp = [NSNumber numberWithInt:creator >> 2 & 0x1];
	NSNumber *os = [NSNumber numberWithInt:owner & 0x1 ];
	NSNumber *oe = [NSNumber numberWithInt:owner >> 1 & 0x1];
	NSNumber *op = [NSNumber numberWithInt:owner >> 2 & 0x1];
	NSNumber *ws = [NSNumber numberWithInt:world & 0x1];
	NSNumber *we = [NSNumber numberWithInt:world >> 1 & 0x1];
	NSNumber *wp = [NSNumber numberWithInt:world >> 2 & 0x1];
	
	[dictionary setObject:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:cs,ce,cp,nil]
													  forKeys:[NSArray arrayWithObjects:kKDISShareEnabled,kKDISEditEnabled,kKDISPrintEnabled,nil]]
				   forKey:kKDISCreator];
	[dictionary setObject:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:os,oe,op,nil]
													  forKeys:[NSArray arrayWithObjects:kKDISShareEnabled,kKDISEditEnabled,kKDISPrintEnabled,nil]]
				   forKey:kKDISOwner];
	[dictionary setObject:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:ws,we,wp,nil]
													  forKeys:[NSArray arrayWithObjects:kKDISShareEnabled,kKDISEditEnabled,kKDISPrintEnabled,nil]] 
				   forKey:kKDISWorld];
	
	return dictionary;
}

+ (NSNumber *)permissionsNumberFromDictionary:(NSDictionary *)dictionary {
	int value = 0;
	int world = [[[dictionary valueForKey:kKDISWorld] valueForKey:kKDISPrintEnabled] intValue] << 2;
	world |= [[[dictionary valueForKey:kKDISWorld] valueForKey:kKDISEditEnabled] intValue] << 1;
	world |= [[[dictionary valueForKey:kKDISWorld] valueForKey:kKDISShareEnabled] intValue];
	int owner = [[[dictionary valueForKey:kKDISOwner] valueForKey:kKDISPrintEnabled] intValue] << 2;
	owner |= [[[dictionary valueForKey:kKDISOwner] valueForKey:kKDISEditEnabled] intValue] << 1;
	owner |= [[[dictionary valueForKey:kKDISOwner] valueForKey:kKDISShareEnabled] intValue];
	int creator = [[[dictionary valueForKey:kKDISCreator] valueForKey:kKDISPrintEnabled] intValue] << 2;
	creator |= [[[dictionary valueForKey:kKDISCreator] valueForKey:kKDISEditEnabled] intValue] << 1;
	creator |= [[[dictionary valueForKey:kKDISCreator] valueForKey:kKDISShareEnabled] intValue];
	
	value |= world << 6 & 0x1c0;
	value |= owner << 3 & 0x38;
	value |= creator & 0x7;
	
	return [NSNumber numberWithInt:value];
}

I am not used to coding bitwise operations so my use of odd masks may be a very poor way of doing it but it works.

This entry was posted in Programming and tagged , , , , , , . Bookmark the permalink.