views:

265

answers:

2

Totally new to Objective-C and Core Data, coming from a .net background I really want to put all of my fetch requests into some sort of class that I can call, preferably statically to get my objects, something like:

ObjectType *myObject = [CoreDataDAL GetObject:ID];

Anyone have a pattern to implement this?

I am hacking my way through one right now but it's probably not quite right, will post code when I have it.

EIDT: Here is my code as it stands right now - seems to work great - please rip it part if I am going down the wrong road - here is the basic DAL:

#import "CoreDataDAL.h"
#import "CoreDataAppDelegate.h"

@implementation CoreDataDAL

@synthesize managedObjectContext;

-(id)init {
    if (self=[super init]) {
        CoreDataAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
        self.managedObjectContext = appDelegate.managedObjectContext;
    }
    return self;
}

-(Client *) GetClient:(NSString *) ClientID{
    /* Client Fetch Request */
    NSFetchRequest *request = [[NSFetchRequest alloc]init];
    NSEntityDescription *entityType = [NSEntityDescription entityForName:@"Client" inManagedObjectContext:managedObjectContext];
    [request setEntity:entityType];
    NSPredicate *predicate =[NSPredicate predicateWithFormat:@"ClientID==%@",ClientID];
    [request setPredicate:predicate];

    NSError *error;
    NSArray *entities = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];

    return [entities objectAtIndex:0];
}

@end

And here is how it is used in my view controllers:

CoreDataDAL *dal = [[CoreDataDAL alloc]init];
Client *client = [dal GetClient:clientID];
[dal release];

Seems straight forward enough, thoughts?

+1  A: 

Fetch request properly belong to the individual controllers in the Model-View-Controller pattern. A fetch returns the specific information, in the specific order, required by each individual view. Each fetch is customized for the needs of each particular view. As such, putting all of an app's fetches in a single object would break encapsulation instead of enhancing it. Only fetched properties and fetched relationships belong in the data model itself.

The managed object context performs the function of the data object in simple apps. It implements all the functions necessary to get information in and out of the Core Data stack e.g. - objectWithID:. Much of the time, all you need to do is pass the context to controllers and let them configure fetches to it.

If your app is large or has multiple context, you can always wrap up the context/s in a custom manager class with various accessors and convenience methods to make the Core Data operations run more smoothly. You can legitimately and safely implement the manager class as a singleton to make accessing it everywhere in the app easy.

Edit:

The code looks okay except for the mutable copy. It's pointless and will leak memory. The entities array is only needed for one line and it's autorelease. The Client objects retention is managed by the context. You should test the error and at least log it for debugging.

You do want to avoid "get" and "set" for method names that are not accessors. The runtime looks methods that begin with "get" and "set" to find accessors. By convention, all method names start with lower case. You might want to make the method name more descriptive so that it auto-comments when you read it months down the road.

So:

[theCoreDataDal GetClient:vaugelyNamedString];

to

[theCoreDataDal clientWithClientID:vaugelyNamedString];

The problem your going to run into with trying to cram everything into one object is that the fetches are usually unique and configured for the needs of a specific interface. Moreover, you usually start with a fetch to find specific objects but then you spend the rest of the time walking relationships based on input unknown until runtime.

Core Data is the data access layer. Most of the code you write for Core Data is actually controller code. There is nothing conceptually problematic about this GetClient method but how often are you going to execute this particular fetch?

When I create a Data Model Manager object, I use it largely to store boiler plate code. For example, while each fetch request is unique, they all start out the same with an entity description so I autogenerate methods to return the basic fetch for each entity and put that in the manager. Then I have another boiler plate method to actually perform the fetch. In use, a controller ask the manager for a fetch object for a specific entity. The controller customizes the fetch and then sends it back to the manager to perform the fetch and return the results.

Everything boiler plate is in the manager and everything customized is in the controller.

TechZen
I think what i meant was what your 3rd paragraph was getting at,posting code in a moment - seems to work really nicely
Slee
+10  A: 

Don't do this; what you're doing is porting a pattern from one context to another where it doesn't really make sense.

For one thing, you shouldn't be modeling IDs at all in Core Data; the framework does that for you with NSManagedObjectID already. Thus a -clientWithID: method on a CoreDataDAL class is redundant. (Note that I've also changed the name of your hypothetical method to follow proper Cocoa naming conventions.) Instead, you can just use -[NSManagedObjectContext objectWithID:] or -[NSManagedObjectContext existingObjectWithID:error:] to get an object based on its NSManagedObjectID.

Similarly, relationship management is handled for you. You don't need to have a method in your DAL that can (say) fetch all of the Address instances that apply for a given Client by evaluating some query. You can just traverse your Client's to-many addresses relationship to get at them, and manipulate the same relationship directly (rather than setting foreign keys etc.).

Finally, if you really do want to have methods to perform specialized queries, you can either specify the query via a fetched property on the appropriate entity for its results, or you can add that method directly to the appropriate class. Class methods in Objective-C aren't like static methods in C++, Java or C# - they can be overridden just as instance methods can, and are much more appropriate for this kind of use.

For example, say your Client entity has a syncID property representing the ID of the object that it represents in some web service. (Note that this is specifically for relating a local object to a remote object, not the "primary key" of the local object.) You'd probably have class methods on the MyClient class associated with your Client entity like this:

@implementation MyClient

+ (NSString *)entityClassName
{
    return @"Client";
}

+ (NSEntityDescription *)entityInManagedObjectContext:(NSManagedObjectContext *)context
{
    return [NSEntityDescription entityForName:[self entityClassName] inManagedObjectContext:context];
}

+ (MyClient *)clientWithSyncID:(NSString *)syncID
        inManagedObjectContext:(NSManagedObjectContext *)context
                         error:(NSError **)error
{
    MyClient *result = nil;

    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity:[self entityInManagedObjectContext:context]];
    [request setPredicate:[NSPredicate predicateWithFormat:@"syncID == %@", syncID]];
    [request setFetchLimit:1];

    NSArray *results = [context executeFetchRequest:request error:error];
    if ([results count] > 0) {
        result = [results objectAtIndex:0];
    } else {
        if (error != NULL) {
            *error = [NSError errorWithDomain:MyAppErrorDomain
                                         code:MyAppNoClientFoundError
                                     userInfo:nil];
        }
    }

    return result;
}

@end

This is similar to what you wrote in your DAL class, but instead of consolidating all of the fetches in one place, it puts the logic for fetches appropriate to a particular managed object class on that class, which is really where it belongs. Thanks to the fact Objective-C has true class methods, you can actually put methods like +entityInManagedObjectContext: and +entityClassName on a common base class and then override only the latter as appropriate in subclasses (or even have it generate an appropriate entity name from the class name).

To sum up:

  • Don't recreate what Core Data already implements for you in terms of things like object IDs, relationship management, and so on.
  • Leverage polymorphism at both the instance and the class level to keep your code clean, rather than use "utility" classes like "data access layers."
Chris Hanson
Coming from .NET I totally fell into the same trap as the OP with my first Core Data project. Good to know the correct way of doing it.
half_brick
Looks like if there are no matches this will return nil without setting the error, which could lead to crashes.
Michael Tsai
In Objective-c, you can safely send a message to a nil object so a nil return in a method is not really a problem.
TechZen
+1 The point about not using design patterns from other environments is a very good one.
TechZen
@TechZen When there's an NSError** parameter, the standard contract is that if you return nil you must set the error. It might have been pointing to garbage, and now the caller expects it to point to a valid object.
Michael Tsai
@Michael Tsai -- +1 yes good point.
TechZen
I added the error out-parameter because I figured people would just copy and paste, and it'd be better to not mislead them into thinking they don't need to check errors. I'll update the code snippet to set the error when not found.
Chris Hanson