The short answer:
Given by @Leo Natan: You could build a multi-dimensional array of URIRepresentation
for each of the Step
s linked to the Chain
.
This solution is very hard to maintain (no automatic cascade and such).
The problem:
represent a multi-dimensional array using CoreData framework ...
There is no trivial implementation as CoreData is an object graph persistence layer while you need a data structure that have strict order and might contain node duplication.
Also, you must define what is your use for such a data-structure as you might want to implement and optimise your representation to support specific functionality.
My suggestion:
Since you require "Array"s and "object duplication" you should partially give-up your direct relationship modelling and introduce an intermediary entity (lets call it StepPlaceholder
).
This entity will take care of order and duplication if needed.
Since you have not specified your use for such a structure, I thought in terms of optimising read operations and incremental growth.
So, we will store your Steps
in a sparse, N-dimensional matrix using a form of NSIndexPath
My model:
The binaryIndexPath
property is a serialised NSUInteger
C-style array where each index in the array is a dimension index the array matrix (the final position of the Step
in the Chain
).
The indexPath
property is transient and only used as utility to convert index paths to NSData
(not necessary, just an example).
Edit:
apparently, There is a byte order issue in my implementation. So, in order to resolve it we must flip the order of bytes in each index prior to saving it to the store.
(This could be averted entirely if we use in-memory sorting, which won't have to much of a performance degradation if any).
The implementation of the setter is:
//byte order correction
NSUInteger flipNSUInteger(NSUInteger input) {
uint8_t* buff = (uint8_t*)(&input);
uint8_t tmp;
size_t size = sizeof(input);
for (NSUInteger i = 0; i < size/2; ++i) {
tmp = buff[i];
buff[i] = buff[size-i-1];
buff[size-i-1] = tmp;
}
return input;
}
- (void) setIndexPath:(NSIndexPath*)indexPath
{
NSIndexPath* currentIndexPath = [self indexPath];
if ( currentIndexPath==nil ||
[currentIndexPath compare:indexPath] != NSOrderedSame)
{
[self willChangeValueForKey:@"indexPath"];
if (indexPath) {
NSUInteger* bytes = (NSUInteger*)malloc([indexPath length]*sizeof(NSUInteger));
[indexPath getIndexes:bytes];
for (NSUInteger i = 0; i < indexPath.length; ++i) {
bytes[i] = flipNSUInteger(bytes[i]);
}
self.binaryIndexPath = [NSData dataWithBytesNoCopy:bytes
length:[indexPath length]*sizeof(NSUInteger)];
} else {
self.binaryIndexPath = nil;
}
[self setPrimitiveValue:indexPath forKey:@"indexPath"];
[self didChangeValueForKey:@"indexPath"];
}
}
Now, why do we need all that? because we like to sort our StepPlaceholder
s on the binaryIndexPath
blob property.
For example, in your above use-case (suppose only one Step
called S is populating the matrix) your index paths would be:
[S : (0)], [S : (1)], [S - (2)], [S - (3,0)], [S - (3,1)], [S - (4)]
The sort order would work like string sorting (MSB compare first).
All that is left to do now is fetch all StepPlaceholder
s attached to a given chain sorted on the binaryIndexPath
property like so:
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:@"StepPlaceholder"];
r.predicate = [NSPredicate predicateWithFormat:@"chain = %@",self.objectID];
r.relationshipKeyPathsForPrefetching = @[@"step"];
r.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"binaryIndexPath"
ascending:YES]];
The request is optimised to fetch the related Steps
along with the placeholders.
I added it as a method on the Chain
entity hence the self.objectID
.
If this seems unstable for you to sort on a binary property you could also fetch the placeholders and sort them in-memory using the indexPath
transient property (not to hard to accomplish).
And now we will produce the multi-dimension array on demand using a class method on Chain
:
(along with some additions to NSArray
)
static char const* const virtual_last_index_key = "virtual_last_index";
@interface NSArray (lastvirtualindex)
@property (nonatomic,assign) NSUInteger virtualLastIndex;
@end
@implementation NSArray (lastvirtualindex)
- (NSUInteger) virtualLastIndex
{
return [objc_getAssociatedObject(self, virtual_last_index_key) unsignedIntegerValue];
}
- (void) setVirtualLastIndex:(NSUInteger)virtualLastIndex
{
objc_setAssociatedObject(self, virtual_last_index_key, @(virtualLastIndex),OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
+ (NSArray*) buildMultiDimArrayWithSortedPlaceholders:(NSArray*)placeholders
{
NSMutableArray* map = [NSMutableArray new];
for (StepPlaceholder* placeholder in placeholders) {
NSMutableArray* currentMap = map;
NSUInteger i = 0;
NSUInteger length = [placeholder.binaryIndexPath length]/(sizeof(NSUInteger));
const NSUInteger* indexes = [placeholder.binaryIndexPath bytes];
for (;i < length-1; ++i) {
if ([currentMap count] &&
currentMap.virtualLastIndex == indexes[i] &&
[[currentMap lastObject] isKindOfClass:[Step class]])
{
return nil;
} else if ([currentMap count] == 0 || currentMap.virtualLastIndex != indexes[i]) {
[currentMap addObject:[NSMutableArray new]];
}
currentMap.virtualLastIndex = indexes[i];
currentMap = currentMap.lastObject;
}
[currentMap addObject:[placeholder step]];
currentMap.virtualLastIndex = indexes[i];
}
return map;
}
You can now compose a transient property that IS a multi-dimensional array.
The algorithm is built so you don't have to have consecutive indexes for your objects only a monotonically increasing index path representation.
Disadvantages:
It is not easy to insert a step to an existing mapping (you will need to update the placeholders of all elements in the same array), due to the array data structure.
It is difficult to ensure Step
uniqueness in this representation (but since this model must allow for duplications of Step
s this should not be much of an issue).
you must bookkeep indexes. deletion of a Step
or a StepPlaceholder
without properly adjusting the indexes will create "holes" in the index continuity (1,2,7 instead of 1,2,3). the algorithm and representation will not be harmed by holes but its not pretty :)
Advantages:
A real multi-dimensional representation of the data.
Much easier to construct then the relational traversal.
Much more efficient (performance wise) then traversing a set object by object (or dimension by dimension) as other implementations does not have a direct link between Step
and the Chain
it belongs to, and faulting will occur more often.
Allow a step to be repeated in the same dimension/array.
Sample project could be found HERE
Step of one or more Steps
? In which case you would have an EntityStep
with an attributecontent
and aone-to-many
relationship to itself callednextStep
(or something). Then you would need to traverse the relationships (checkingcontent
ornextStep
) to discover if you are the bottom of a branch. This is just my thoughts (as a Core Data amateur) based on the info above. – HomozygoteStep
may belong to multipleChains
, and thus thenextStep
value may not always be the same. In regards to adding further details about what I have tried, well, I haven't really tried anything because I don't know how to serialize a multidimensional array of object references – ReconvertNSValueTransformer
, but since the arrays include Core Data object, I would urge you to reconsider a different approach. Are you interested in thinking about a different solution? – LemckeURIRepresentation
or anNSManagedObject
's objectID as they are not portable. They may change between app launches, and will almost certainly change during any Core Data migration. – Soggy