So I got entities Level and Tile. Level has a to-many relationship with Tile. Tile has a property 'index'.
Right now I'm using this code to get the tiles array of Level sorted:
- (NSArray *)sortedTiles
{
NSMutableArray *sortedTiles = [NSMutableArray arrayWithArray:[self.tiles allObjects]];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"index" ascending:YES];
[sortedTiles sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];
return sortedTiles;
}
This works, but I want to be able to retrieve a single Tile with a certain index, so I wrote this method in Level.h:
- (Tile *)tileWithIndex:(NSInteger)index;
The implementation is fairly simple:
- (Tile *)tileWithIndex:(NSInteger)index
{
NSArray *sortedTiles = [self sortedTiles];
Tile *tile = [sortedTiles objectAtIndex:index];
return tile;
}
Now, ofcourse this isn't the most efficient way in doing so because the tiles array has to be allocated and sorted each time, so I was thinking: if I just add an instance variable to Level, 'sortedTiles', then I won't have to rebuild it each time. But Level is a subclass of NSManagedObject, so is this possible and/or wise to do?
I wouldn't. The NSManagedObjects are a reflection of a record in the database and adding additional member variables outside the model strikes me as adding something that doesn't belong.
There are other, better ways to accomplish the same thing. The simplest would be to get the controller or delegate that's fetching all these objects to fetch, sort and retain the array locally.
For large or complex situations, you can use an NSFetchRequestController to collect, sort and dole out NSManagedObjects as needed. This integrates nicely into a UITableViewController. I haven't done any performance testing, but for a situation where there are potentially a large number of records, I would try this first to see if the Fetch Results classes own cache management is sufficient.
Related
So this is a rather basic question regarding the best way to sort an NSMutableArray of custom objects.
I have a an NSMutableArray of custom objects, each object with an NSString and NSDate that go together. I need to sort the array by the newest object (so latest NSDate), and I'm pretty sure I could simply use NSDate compare: NSDate if this was an array of just NSDate, but since I need all objects to be sorted and not just the date, I'm not sure if I can use that method.
In terms of pseudo-code, I need to: Look at individual object, determine if the current object's NSDate is the next biggest in the array, and if it is, move the object, not just the date.
Again, this is something I was even hesitant to ask since it's so basic but I don't want to go writing some grossly inefficient method if there is a pre-existing class method that will essentially do what I want, search an array of object's sub properties and sort the objects according to the subproperties.
Thanks for any help.
NSSortDescriptorss make this really simple. With NSMutableArray you can sort the existing array using sortUsingDescriptors: and with immutable arrays you create a new array using sortedArrayUsingDescriptors:
//This will sort by stringProperty ascending, then dateProperty ascending
[mutable_array sortUsingDescriptors:
#[
[NSSortDescriptor sortDescriptorWithKey:#"stringProperty" ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:#"dateProperty" ascending:YES]
]];
This little snippet worked great for me:
[students sortUsingDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES]]];
I have the following relationship defined in Core Data
Person --> Worked <-- Job
I have a view that shows Person information in a tableview. The user can then click on Worked Items to see all worked items for that person (Worked entity shows hours worked and related job).
I then push a view showing worked jobs for that person.
I also show, in a picker view, a list of jobs that can be added to the Worked list.
I've tried to do this every which way, but I'm not sure if I'm going about the right way, so I'd like the experts' input on this.
What should I pass into the Worked view? I currently pass in the Person object containing the Worked NSSet to load the table view. Then I use a NSFetchedResultsController to load the picker.
So I got the add functionality working, by using the Person and Job addWorkedObject: methods.
But I need to let the user delete a worked item from the table view.
Should I be using two NSFetchedResults? If so, how?
I'm really at my witt's end with this one, so if anyone can help, I'd really appreciate it.
Thanks,
Rod
I'd need a little more information. First, are you relationships uni-directional as your diagram suggests? From what I've read, Core Data is much happier with bidirectional relationships.
For example, this model (below)
Person <-->> Worked <<--> Job
states that a Person can 'have' many Worked objects, while a Worked object will be associated with at most one Person. Similarly, a Job could be associated with many Worked objects but a Worked object would only ever be associated with at most one Job. Is this your model?
If so, I would use an NSFetchedResultsController for the root view (the one that is showing all Persons). But I would just use NSSets/NSArrays to populate UITableView and UIPickerView.
This is a method that I use for debugging - but it might provide a starting point for getting all Jobs, for example. For your purposes, you could populate an ivar NSArray with the NSManagedObjecs in 'items'.
- (void) dumpAllObjects:(NSString *) entityDescription
{
DLog(#"Dump all object of type '%#']", entityDescription);
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:entityDescription
inManagedObjectContext:managedObjectContext]];
NSError *error = nil;
NSArray *items = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
DLog(#" There are %d instances of entity '%#'", [items count], entityDescription);
for (NSManagedObject *managedObject in items)
{
if ([managedObject respondsToSelector:#selector(dump)])
[managedObject dump];
}
}
Meanwhile, I would subclass UIViewController for your view that shows Worked objects and Job objects - and have that class implement UITableViewDelegate, UITableViewDataSource, UIPickerViewDelegate and UIPickerViewDataSource.
As for deleting, I am not sure which aspect is causing problems. If you have -addWorkedObject: methods for Person and Job, then you should also have -reomveWorkedObject: methods. No?
Hope this helps.
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?
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."
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.
Why are class methods such as
+ (NSMutableArray *)array;
needed when there are perfectly fine parent class methods such as
+ arrayWithObjects:(id)firstObj, ...;
which could be set to
[array arrayWithObjects:nil]
and have the same effect.
Are they actually not equivalent things?
They're convenience methods, to make it easier to just get an autoreleased object fast instead of having to write a lot more to get the same result.
BTW, NSArray does not have an instance method called arrayWithObjects, they are only class methods.
Jacob's answer is right (so accept that one) but I'll add that the Cocoa framework has a spiffy thing under the hood called class clusters. The idea is that, although you may call [NSArray array], the object you get back is actually a private subclass of NSArray that's optimized for your specific situation. These convenience methods can give the NSArray class a "hint" as to which class to use.
For example, if you call [NSArray array], you get an empty, immutable array. Well, how many different values can an empty immutable array have? Just one. So behind the scenes, Cocoa can return the same empty NSArray to every call to [NSArray array] so that it only ever has to allocate one of these. This is a nifty optimization that saves some memory.
Now, this is really an implementation detail you don't need to concern yourself with, but the takeaway is that you should use the constructor that most closely matches the result you want, especially with collection classes like arrays, sets, and dictionaries, because Apple's implemented a boatload of optimizations that make your application work better.
To optimize a bottleneck, I converted the creation of a large NSArray to a c-style array. (The resulting creation was 1/8 the time of the original NSArray version. Yeah!) But once it's created, speed is no longer an issue, so I'd rather benefit from it being an NSArray again.
However, it seems ridiculously involved to convert a c-style array to an NSArray (unless I'm missing some magic initWithArrayWrapElementsInObjects method.)
As I understand the process now, I first have to create an NSMutableArray, iterate through the c-style array converting each element (floats in my case) to objects, adding each object to the NSMutableArray, then creating the NSArray with the NSMutableArray.
Is that right? There's got to be a better way.
And help would be appreciated.
Thanks!
There's no direct way to take a blob of memory that you own and "convert" it cheaply into an NSArray-- after all, the framework would need to then own that memory, and it doesn't know where you got it from (malloc, stack, etc). If there were a convenience method for initWithArrayWrapElementsInObjects, it would itself need to do internally what you surmise: iterate over your provided memory and add items to itself (it's possibly the framework could do this as quickly as a memcpy, but who knows).
One way you could tackle this (and probably a fun learning exercise) is by actually creating your own subclass of NSArray that manages memory exactly as you want (ie, lets you create and init with whatever semantics you want), but that behaves to the outside world as an NSArray would. You can do this by inheriting from NSArray and implementing the methods count: and objectAtIndex: to operate on whatever memory you're holding on to. Obviously, you'd need to implement the management of your own memory in the init/dealloc, etc methods as well. See this page http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html
under "Subclassing Notes".
The design discussion here hinges on what your data looks like. NSArray, of course, expects its items to be Obj-C references (of type id), and not just arbitrary chunks of data. If your C-style array is holding structures or some other primitive values that aren't object references, then this technique won't really work for you-- NSArray's interface will never be happy with non-reference items.
One final note: you mention taking an NSMutableArray and "creating" an NSArray with it. You should be aware that an NSMutableArray is already an NSArray, since it's a subclass. You can use an instance of NSMutableArray anywhere you'd want an NSArray, without creating some new copy of it.
UPDATE: Missed the note about your array containing floats. Yeah, you're a little bit screwed here. NSArrays want objects. If the capacity doubling was the expensive part (as another poster notes), then try initWithCapacity:. If it's the boxing/unboxing of the floats into object types, there's nothing you can do.
I have created (but don't have handy) a pair of very simple classes (called like MYArray and MYMutableArray) that are intended to wrap just this kind of data with NSArray-like methods on them. But they're not interchangeable with NSArrays. You must use them intentionally.
UPDATE #2. I know it's been ages since this question was live, but I just revisited it and realized there actually is a sort of clever way around this in this specific case. (You want a non-mutable NSArray from a C-style float array). You can create a custom subclass of NSArray that wraps the float values and only converts them to objects when they're accessed via the primitives. This may have performance pitfalls in some corners (?), but it does neatly meet your requirements:
#interface FloatProxyArray : NSArray
{
float * values;
NSUInteger count;
}
- (id)initWithCArray:(float *)arrayOfFloats count:(int)numberOfValues;
#end
.
#implementation FloatProxyArray
- (id)initWithCArray:(float *)arrayOfFloats count:(int)numberOfValues
{
if ((self = [super init])) {
values = (float *)malloc(numberOfValues * sizeof(float));
if (!values) {
[self release]; return nil;
}
memcpy(values, arrayOfFloats, numberOfValues * sizeof(float));
count = numberOfValues;
}
return self;
}
- (void)dealloc
{
free(values);
[super dealloc]
}
- (NSUInteger)count
{
return count;
}
- (id)objectAtIndex:(NSUInteger)index
{
if (index >= count) {
[NSException raise:NSRangeException format:#""];
return nil;
}
float val = values[index];
return [NSNumber numberWithFloat:val];
}
#end
(N.B. Written in the editor without compiling/testing.)
One optimization that you can do with the NSMutableArray is initWithCapacity which will prevent the doubling of your array which is the expensive operation in the addition.
Outside of that, since NSArrays and NSMutableArrays expect objects, so it's difficult to get around this.
What benefits of it being an NSArray are you looking to get?
It seems like you may be better off with a custom wrapper object around the C array that responds to whatever NSArray messages you are looking to call. Otherwise you are right back at the point of array creation... You could try manually creating a call to initWithObjects, but at the very least every float has to be wrapped in an NSNumber which would bring down your speed again.
If you really need an NSArray because something else you want to use takes NSArray objects, then you are probably better off subclassing NSArray (following the guidelines posted by Ben).
The “best” optimisation (for speed) would almost certainly be to avoid using NSArray altogether, if possible.