I'm using an xcdatamodel to define a number of classes based upon CoreData data entities. This is working great and I can retrieve them in accordance to Apple's examples:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdFetching.html
What I'm after however, is a way to package the fetch method up into another class, but I have a couple of questions...
e.g.
MyDataAccessClass *mdac = [[MyDataAccessClass alloc] init];
myFetchedData = [mdac fetchData];
Q1. If I do this, is it ok that the NSManagedObjectContext is defined in the class? or does it still need to be referenced in my view controller and passed to my 'MyDataAccessClass'?
Q2. It makes sense to me to have the data retrieval methods set up in the classes created by XCode for the entities in the data model. Although every time I try to do this, then update those classes automatically because they are automatically generated by XCode, they overwrite any methods I've defined.
Thanks in advance!
You might be able to create a new NSManagedObjectContext in the seperate class - not sure if there will be any issues with that since one is already created in the appDelegate. What I did was what you suggest in the second part of Q1, where I pass the NSManageObjectContext into the seperate method class so then I can do something like:
myFetchedData = [mdac fetchData:currentNSManagedObjectContext];
Related
Suppose I create a new project with core data. A project with a root view controller.
By default, Apple adds a self.managedObjectContext reference to this main viewController.
Now I add other classes to my project.
At some point I will have to read the objects from the core data entities and I will have to access that managedObjectContext. How do you guys obtain that context from other classes?
I know I can import the rootViewController.h on the class I want to access the context, but I am trying to do that without importing the header on multiple classes, because I want to make the classes as much independent of each other as possible and because I think this is not a good solution as it will create a mess of cross references between classes. I may be wrong.
I now I can use:
managedObjectContext = [(MyAppDelegateName *)[[UIApplication sharedApplication] delegate] managedObjectContext];
but then I will have to import MyAppDelegateName.h and the problem is the same.
Is there a better way to do that?
How do you solve that?
thanks.
Reduce dependency on CoreData by wrapping either with Repository design pattern, or with just a high level Model that handles all Core Data interactions for you. Then either have the model as a statically available class, singleton it (not recommended), or pass it through your ViewControllers.
Edit:
Repository Pattern - define specific repositories for each of your data objects. For example, if you have Suppliers and Products, you would have 2 repository classes, and each Repository conforms to a set interface. As a illustrative example:
#protocol Repository{
-(id)GetByKey:(NSInteger *)key;
-(id)GetAll;
-(id)RemoveByKey:(NSInteger *)key;
-(id)RemoveAll;
}
#interface SupplierRepository:NSObject <Repository>
#end
#interface ProductRepository:NSObject <Repository>
#end
Your core data entities will also need to conform to some conventions, such as a surrogate key. I've never done this, so someone else may be able to better explain. Depending on your tolerance rating, you may decide to just instantiate repositories in your ViewControllers, or you may decide to just put it in a single Model instance anyway.
I have three classes, A, B and C. A is the main class.
When the user wants to see the list of all objects that were purchased, Class B is called from A and shows the list of objects in a core data entity.
Inside class B, the user can buy new objects (in-app purchase). When the user wants to buy another object, class C is called.
When class C is called, a new object is created on the core data entity using
anObject = [NSEntityDescription insertNewObjectForEntityForName:#"Objects" inManagedObjectContext:context];
this object is then assigned to a local reference on Class C, using something like
self.object = anObject;
this object variable was declared like this:
.h
MyObjects *object;
#property (nonatomic, retain) MyObjects *object;
and #synthesized on .m
MyObjects is a core data class representing the entity.
In theory, object will retain anything assigned to it, so the line self.object = anObject I typed previously will retain anObject reference on self.object, right?
The problem is that when I try to access self.object in the same class after buying the new object, I receive an error "CoreData could not fulfill a fault for XXX", where XXX is exactly self.object.
At no point in the code there's any object removal from the database. The only operation to the database I could identify was a saving operation done by another class moments before the crash. The save is done by something like
if (![self.managedObjectContext save:&error]) ...
Is there any relation? what may be causing that?
CoreData manages the lifetime of managed objects and you should not retain and release them. If you want to keep a reference to the object so that it can be retrieved later then you have to store the object's id (obtained using -[NSManagedObject objectID]). Then use that to retrieve the object later using -[NSManagedObjectContext objectWithID:].
Make sure you understand about CoreData faulting. Read the documentation.
I had a similar issue a few days ago (using NSFetchedResultsController) where I was placing my fetchedObjects into an array and gathering attributes to populate tables from the array objects. It seems that if the objects in the array are faulted, you cannot unfault it unless you are acting on the direct object. In my case, I solved the issue by taking the lines of code in question and calling [[_fetchedResultsController objectAtIndexPath:indexPath] someAttribute]. I would assume that doing something similar would fix your problem as well. It seems a bit tedious to need to fetch from the managedObjectContext to obtain a faulted value, but this was the only way I could personally get past the issue.
Core Data is responsible for managing the lifetime of managed objects in memory. It's really important to understand Managed Object Contexts - Read the documentation.
Apple also provides an entire troubleshooting section here, and it contains among other things the causes for your error. But it's really only useful if you understand how core data works.
Most likely error is that the object you are saving does not belong to the managed object context.
Say you use the same object on different threads and those different threads use different managed object context, then this will happen.
I am using a tableview with data from coredata using nsfetchedresultscontroller. When the view loads i make a new entity using
SomeManagedObject *someManagedObject = [NSEntityDescription insertNewObjectForEntityForName:#"SomeManagedObject" inManagedObjectContext:self.managedObjectContext];
This way the new entity appears in my tableview. Now i want this entity to be only temporary, but when i edit some object inside the tableview and save the managedObjectContext the temporary entity will also get saved and i don't want that.
Is their a way to save one object only and not everything inside de managedObjectContext?
Is their some other way to make a temporary object for my tableview.
Any help would be very welcome.
Thanks
Ton
Create the new NSManagedObject with it's alloc init and pass nil instead of the NSManagedObjectContext. Then if you later decide you want that object to be permanent then set it's context. However this will not allow you to see it in a NSFetchedResultsController because it will not be associated with the context.
A better answer can be provided if you could explain what your ultimate goal is.
No, in a managedObjectContext saving is a all or nothing. What I do not know is what happens if you set the persistent store of the managed object to nil
- (void)assignObject:(id)object toPersistentStore:(NSPersistentStore *)store
If you then save the managedObjectContext this object should not be saved. It is just a guess, but tell me if it works ;-)
For temporary managed objects, create them with a 2nd managed object context (MOC). When you are finished, simply release the MOC without performing a save.
Look at the Adding a Book code in CoreDataBooks which uses the same approach to throw away the newly added object when the user cancels.
I apologise if this has been asked before but I can't find the info I need.
Basically I want a UITableView to be populated using info from a server, similar to the SeismicXML example. I have the parser as a separate object, is it correct to alloc, init an instance of that parser & then tell RootViewController to make it's table data source a copy of the parser's array.
I can't include code because I haven't written anything yet, I'm just trying to get the design right before I start. Perhaps something like:
xmlParser = [[XMLParser alloc] init];
[xmlParser getXMLData];
// Assuming xmlParser stores results in an array called returnedArray
self.tableDataSource = xmlParser.returnedArray
Is this the best way of doing it?
No, you don't want to do this. You don't want your view controller directly accessing the array of the data-model. This would work in the technical sense but it would be fragile and likely to fail as the project scaled.
As the projects grow in complexity, you will want to increasingly wrap your data model object (in this case the xmlParser) in protective layers of methods to control and verify how the data model changes. Eventually, you will have projects with multiple views, multiple view controllers as well as information entering from both the user and URLs. You need to get into the habit of using the data-model object not just a dumb store you dump stuff into but as an active manager and verifier of your data.
In a situation like this I would have my data-model's array completely wrapped by making it a #protected or #private property. Then I would have dedicated methods for fetching or inserting data into the actual array inside the data-model class itself. No objects outside of the data-model should actually have direct access to the array or have knowledge of its indexes.
So, in this case your data-model would have something like:
- (NSString *) textForLineAtIndexPath:(NSIndexPath *) anIndexPath{
//... do bounds checking for the index
NSString *returnString=[self.privateArray objectAtIndex:anIndexPath.row];
if (returnString=='sometest'){
return returnString;
}
return #""; //return an empty string so the reciever won't nil out and crash
}
as well as a setTextForLineAtPath: method for setting the line if you need that.
The general instructional materials do not spend enough (usually none) time talking about the data-model but the data-model is actually the core of the program. It is where the actual logic of the application resides and therefore it should be one of the most complex and thoroughly tested class in your project.
A good data-model should be interface agnostic i.e. it should work with a view based interface, a web based interface or even the command line. It should neither know nor care that its data will be displayed in a tableview or any other interface element or type.
When I start a new project, the first thing I do is comment out the '[window makeKeyAndVisible];' in the app delegate. Then I create my data-model class and test it old-school by loading data and logging the outputs. Only when it works exactly how I wish it to do I then proceed to the user interface.
So, think real hard about what you want the app to do on an abstract level. Encode that logic in a custom class. Isolate the data from all direct manipulation from any other object. Verify all inputs to the data before committing.
It sounds like a lot of work and it is. It feels like overkill for a small project and in many cases it is. However, getting the habit early will pay big dividends very quickly as your apps grow in complexity.
Not quite. You want the data source to be an object that implements the UITableViewDataSource protocol; what I would do in this situation is create an object that implements that protocol and parses XML, so that you can alloc-init it, then set the data source to that object and have it update the table view as appropriate. So based off your code (and assuming you're running within the table view's controller):
XMLParserAndDataSource xpads = [[XMLParserAndDataSource alloc] init];
[xpads getXMLData];
self.tableView.dataSource = xpads;
It's probably a good idea to give this class itself a reference to an NSXMLParser object, so you can use that to parse the XML, then provide convenience methods (like getXMLData) as well as the UITableViewDataSource methods for your own use. (If you go this route, you should also make your XMLParserAndDataSource class implement the more useful of the NSXMLParser delegate methods, and use them as appropriate to update your table view.)
I'm a Mac programmer and not an iPhone programmer; but on the mac,
self.tableDataSource = xmlParser.returnedArray is not correct. You are supposed to either bind the table's content to an Array Controller (if iPhone has one?) or set the datasource outlet to your RootViewController.
In your rootview controller, you would implement the methods:
– tableView:cellForRowAtIndexPath:
– tableView:numberOfRowsInSection:
For – tableView:cellForRowAtIndexPath: you would return a UITableViewCell with the data you received from the XML parsing according to the index path like so:
UITableCell *myCell = [UITableCell new];
myCell.textLabel.text = [parsedXMLArray objectAtIndex:[indexPath indexAtPosition:indexPath.length-1]];
return myCell;
(Something people don't know is that you can use the + new class method on all NSObject subclasses which automatically call alloc/init.)
For – tableView:numberOfRowsInSection just return the count of the data array:
return parsedXMLArray.count;
Can't edit my question nor post replies, can only post my response as answer.
#TechZen: I'm somebody who tries to form analogies, helps me understand. What you're saying is something like: My original idea was like going into the file room & dumping all the originals on my desk to work on where as you suggest the object be more like an organised file clerk who will search through the data for me and only return the specific datum that I need while being the only one with direct access to that data.
Have I understood correctly?
#Tim: What if I later need the parser to get data for something which is not a table? That's why I thought to dump it into an array & let the caller decide what to do with the data. Would you suggest a second object that would supply the data in the newly required form? (Am I sort of one the right track here or way off?)
What is the best way to add custom methods to my core data generated classes?
For example, say I have a "Person" entity with properties "firstname" and "lastname". I wish to add a "fullname" method, which returns a concatenation of the firstname and lastname properties.
I could add the method to the generated .h and .m files, but this would be difficult to maintain during development when my entities may still change. Recreating the .h and .m file would overwrite these changes. Another idea is to subclass the generated class and add the methods there.
Is there a better way?
I find that the best way to add custom methods that aren't directly tied to data properties is to use a category. This generally works best if you create your Core Data entities as their own subclasses of NSManagedObject in the data modeler, but it can work without that as well. This way all the machine generated code can stay in the main .h and .m files, and all your custom code goes in the .h and .m for your category on that class.
I'd recommend adding these methods to your custom NSManagedObject subclass. If you're worried about maintaining accessors as your data model changes, while preserving your custom methods, I'd suggest looking to "Wolf" Rentzsch's mogenerator. Many people swear by this tool for just this purpose.
You create your own subclass of NSManagedObject for the entities.
Better explained in the NSManagedObject documentation