4

PLEASE NOTE: This is not the same question as this question, as I already know that this stems from an Apple bug. Mind you, overriding the specific methods (say, -addFriendsObject: if I had a friends relationship) is not an option, since I need to do this in a category so it works for any managed object and regardless of modifying the model and rebuildiong the autogenerated classes from it.

The need for this stems from the fact that apparently the minute one makes relationships ordered and to-many (NSMutableOrderedSets) Core Data dynamic methods go to hell:

  1. methods -add<Relationship>Object, -add<Relationship>, -remove<Relationship>Object and -remove<Relationship> will all crash with an exception alike 'NSInvalidArgumentException', reason: '*** -[NSSet intersectsSet:]: set argument is not an NSSet'
  2. methods along the lines of insertObject:in<Relationship>AtIndex: will instead crash because no implementation for them was actually provided by CoreData.

As far as 2, I wrote a category that overrides -methodSignatureForSelector: and -forwardInvocation to instead do something like mutableOrderedSetValueForKey for the relationship name followed by the actual adding or removing.

Now for 1, the problem is that CoreData is actually providing implementations for those methods (though they are not the right implementations for ordered sets). So I need a way of intercepting those selectors too, so I can implement the behavior over mutableOrderedSetValueForKey.

Any ideas how to pull it off?

Community
  • 1
  • 1
SaldaVonSchwartz
  • 3,769
  • 2
  • 41
  • 78
  • Check out this answer, http://stackoverflow.com/a/7922993/250190 – Chris Wagner May 29 '13 at 04:50
  • Right, I know it's an Apple bug. Thing is it won't get fixed anytime soon. And the workaround in that question assumes one does override in any specific ManagedObjects. What I need is a generic (that is, category over NSManagedObject or NSObject) way of intercepting method calls and rerouting them or acting on them. Like I explained, in the case of the -insert flavor of methods this can be done since they don't exist to start with and thus trigger specific NSObject methods. My remaining problem is when there are already implementations, since these won't trigger methodSignatureForSelector – SaldaVonSchwartz May 29 '13 at 04:57
  • why am I getting voted for close people? please take a min or two to understand my question... not like I didn't first search for similar questions – SaldaVonSchwartz May 29 '13 at 04:59
  • I'll make a clarification at the begining of my question, thanks for the heads up. – SaldaVonSchwartz May 29 '13 at 05:45

1 Answers1

0

I don't know that this is the best approach (it's certainly not the prettiest thing ever), but it gets the job done:

#import "NSManagedObject+OrderedSets.h"
#import <objc/runtime.h>

@implementation NSManagedObject (OrderedSets)

+ (void)swizzleMethod:(SEL)originalSelector with:(SEL)replacementSelector
{
    const char *methodTypeEncoding = method_getTypeEncoding(class_getInstanceMethod([self class], originalSelector));
    class_replaceMethod(self,
                        originalSelector,
                        class_getMethodImplementation(self, replacementSelector),
                        methodTypeEncoding);
}

+ (void)initialize
{
    NSEntityDescription *selfEntity = // get a hold of your NSManagedObjectContext and from its entities grab model.entitiesByName[NSStringFromClass(self)] ... 
    for (NSString *rKey in [selfEntity relationshipsByName]) {
        NSRelationshipDescription *r = selfEntity.relationshipsByName[rKey];
        if (r.isOrdered) {
            NSString *rKeyFirstCaps = [[rKey substringToIndex:1] capitalizedString];
            NSString *capitalizedKey = [rKey stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:rKeyFirstCaps];
            NSString *addSelectorString = [NSString stringWithFormat:@"add%@Object:", capitalizedKey];
            NSString *removeSelectorString = [NSString stringWithFormat:@"remove%@Object:", capitalizedKey];
            [self swizzleMethod:NSSelectorFromString(addSelectorString) with:@selector(addOrRemoveObjectInOrderedSet:)];
            [self swizzleMethod:NSSelectorFromString(removeSelectorString) with:@selector(addOrRemoveObjectInOrderedSet:)];
        }
    }
}

- (void)addOrRemoveObjectInOrderedSet:(NSManagedObject*)object
{
    NSString *selectorString = NSStringFromSelector(_cmd);
    NSString *prefix = [selectorString hasPrefix:@"add"] ? @"add" : @"remove";
    selectorString = [selectorString stringByReplacingCharactersInRange:[selectorString rangeOfString:prefix] withString:@""];
    selectorString = [selectorString stringByReplacingCharactersInRange:[selectorString rangeOfString:@"Object" options:NSBackwardsSearch] withString:@""];
    selectorString = [selectorString substringToIndex:selectorString.length - 1];
    NSString *selectorFirstLowerCase = [[selectorString substringToIndex:1] lowercaseString];
    NSString *camelCasedKey = [selectorString stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:selectorFirstLowerCase];

    if ([prefix isEqualToString:@"add"]) {
        [[self mutableOrderedSetValueForKey:camelCasedKey] addObject:object];
    }
    else {
        [[self mutableOrderedSetValueForKey:camelCasedKey] removeObject:object];
    }
}

@end
SaldaVonSchwartz
  • 3,769
  • 2
  • 41
  • 78