Using NSManagedObject manually - something wrong with the NSManagedContext I get? - objective-c

I'm new to Cocoa programming, and decided for my first project to create a small application to monitor and remember certain battery stats for my laptop. (I have it plugged in most of the time, and apple recommend you discharge it now and again, so why not try to make a small program to help you remember to do this? :))
Anyway, I have a standard Objective-C project, with a DataModel file.
It contains an Entity, BatteryEvent, with properties, charge and event.
I then have PowerListener.m (and .h).
PowerListener.m is implemented as follows:
#implementation PowerListener
void myPowerChanged(void * context) {
printf("Is charging: %d\n", [PowerFunctions isCharging]);
printf("Is on ac: %d\n", [PowerFunctions isOnAC]);
printf("Charge left: %d\n", [PowerFunctions currentCapacity]);
printf("Powerchanged\n");
NSManagedObject *newBatteryEvent = [NSEntityDescription
insertNewObjectForEntityForName:#"BatteryEvent"
inManagedObjectContext:context];
}
- (PowerListener*) init {
self = [super init];
if(self) {
CFRunLoopSourceRef loop = IOPSNotificationCreateRunLoopSource(myPowerChanged, [[NSApp delegate] managedObjectContext]);
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, kCFRunLoopDefaultMode);
CFRelease(loop);
} else {
printf("Error\n");
}
return self;
}
#end
My problem is that once I run this (inited through main.m's main-method) and the power actually DOES change, I get thrown an error where I try to create the new BatteryEvent object:
2009-08-19 17:59:46.078 BatteryApp[5851:813] +entityForName: could not locate an NSManagedObjectModel for entity name 'BatteryEvent'
So it looks to me like I have the wrong ManagedContext? How do I get the right one?
Am I even on the right track here?
I've tried passing another kind of NSManagedObjectContext to the callback function as well.
I followed this guide: Core Data Guide, but, again same error...
I'm at my wits end!
Any help appreciated!

It looks like your app isn't loading the managed object model as a part of the launch and/or Core Data stack initialization.
Where is your model loaded?
Also, make sure you spelled the entity name correctly in the model.

Related

Mac app NSArrayController bindings not autosaving Core Data

I was under the impression that when using bindings (been following this tutorial despite being outdated. http://cocoadevcentral.com/articles/000085.php - You can use it to see what I'm doing) the Persistent Store would automagically save the changes you make. In fact, though it was hours ago and I wouldn't be surprised if I'm now going mad, I got it working, and when I made a change it would persist on rebuilding the app.
However, the test app I've built following the tutorial no longer saves and despite showing the changes I make within the app, they disappear once I re-run the app. I've been checking the Core Data debug menu and nothing happens when I press the "+" button which is set up to the "Add" method of my NSArrayController. I know it's accessing my data model too as my textField for the Title (again, see the tutorial so you know what I'm referring to) adopts the default text I put in the DataModel section. The only thing missing therefore is the actual saving.
So my real question is, based on the tutorial, what part of the bindings actually makes the managedObjectContext save? Is there a flag or something that isn't checked?
I don't know if it's important or not, but there were differences between the tutorial and my project, mainly that the NSArrayControllers are bound to "App Delegate"with a Model Key Path of "self.managedObjectContext". Also, I removed all the relationships in an attempt to whittle down the issue.
Any help would be greatly appreciated.
Regards,
Mike
UPDATE: Here are some pictures that show the bindings.
How I set up the NSArrayController:
Here is how is how my Data Model Looks:
Lastly, this is how I set up the TextFields to update the NSArrayControllers:
I hope this helps to get a an ideas as to the set up.
Thanks,
Mike
Could you check to make sure you've copied all the Core Data boiler-plate code from the source code of the tutorial you mentioned.
Specifically this part in the App Delegate:
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
NSError *error;
NSManagedObjectContext *context;
int reply = NSTerminateNow;
context = [self managedObjectContext];
if (context != nil) {
if ([context commitEditing]) {
if (![context save:&error]) {
// This default error handling implementation should be changed to make sure the error presented includes application specific error recovery. For now, simply display 2 panels.
BOOL errorResult = [[NSApplication sharedApplication] presentError:error];
if (errorResult == YES) { // Then the error was handled
reply = NSTerminateCancel;
} else {
// Error handling wasn't implemented. Fall back to displaying a "quit anyway" panel.
int alertReturn = NSRunAlertPanel(nil, #"Could not save changes while quitting. Quit anyway?" , #"Quit anyway", #"Cancel", nil);
if (alertReturn == NSAlertAlternateReturn) {
reply = NSTerminateCancel;
}
}
}
} else {
reply = NSTerminateCancel;
}
}
return reply;
}
If it's there, changes will be saved when the app is terminated normally. Pressing the 'stop' button in Xcode will terminate the app immediately, without going through the method mentioned above.
My guess is that you are not going mad, but first exited the app properly and have been pressing the 'stop' button later ;).

Cocoa app behaves diffirently with breakpoint on & off

Important update: I found out that most part of my question was based on a false premise (see my answer below). Notifications actually got to the receiver, they just got there too fast. (Although, it still doesn't explain why the behavior with breakpoint and without it was different.)
I'm developing the app that calculates the hashes of files given to it. The calculation takes place in SHHashComputer. It's an abstract class (well, intended to be abstract, as there are no abstract classes in Objective C) that takes the file path and creates an NSInvocationOperation. It, in turn, calls the method (void)computeAndSendHash, which uses the file path saved in the object to compute hash and sends it as notification. The actual computing takes place in (NSString*)computeHash method that child classes need to override.
Here's SHHashComputer.m:
- (NSString*)computeHash {
return [NSString stringWithFormat:#"unimplemented hash for file %#", self.path];
}
- (void)computeAndSendHash {
NSString *result = [self computeHash];
NSString *notificationName = [NSString stringWithFormat:#"%#%#",
gotResultNotification,
self.hashType];
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName
object:result];
self.operation = nil;
}
And here's SHMD5Computer.m (the child class of SHHashComputer):
- (NSString*)computeHash {
return #"MD5 test"; // it actually doesn't matter what it returns
}
I won't bother you with the receivers of notification. Let's just say that as long as I comment out the computeHash method in SHMD5Computer.m everything works just fine: the notification with text "unimplemented ..." is received & displayed in GUI. But if I don't — then it gets really interesting.
If I don't set up any breakpoints, the notification just never comes. However, if I set up a breakpoint at the declaration of computeHash in SHMD5Computer.h and then step over until the line 'self.operation = nil', and continue execution at that point, the notification gets to destination. If I don't stop there, the debugger suddenly switches to the state as if it isn't debugging anything, and the app freezes.
I don't think that 'WTF' is a good form for a question here, so let me put it this way: am I missing something? Are there errors in my code? What can cause this type of behavior in xcode? How can I fix this?
(If you'll want to get all my code to reproduce it, I'll gladly give it to you.)
More experiments:
If I continute execution exactly after stopping at breakpoint, the application encounters EXC_BAD_ACCESS error in the code that receives the notification, at the last line:
id newResult = [newResultNotification object];
if (newResult == nil)
[NSException raise:#"No object"
format:#"Expected object with notification!"];
else if (![newResult isKindOfClass:[NSString class]])
[NSException raise:#"Not NSString"
format:#"Expected NSString object!"];
else
self.result = (NSString*) newResult;
[self.textField setStringValue:self.result];
When I tried to reproduce the previous experiment, something even stranger happenned. In my debug setup, I have two hash computer objects: one SHMD5HashComputer (which we're talking about), and one SHHashComputer (which, of course, produces the "unimpemented" hash). In all previous experiments, as long as app didn't crash, the notification form SHHashComputer always successfully arrived. But in this case, both notifications didn't arrive, and the app didn't crash. (All the steps are exactly the same as in previous one).
As Josh Caswell pointer out in the comments, I wasn't using the notifications correctly. I should've sent the object itself as notification object, as described in documentation. I fixed that, and I'm getting exactly the same results. (Which means that I fixed it correctly, because sometimes the notifications work correctly, and also that it wasn't the problem).
More updates:
The notification that I'm sending should arrive at SHHashResultViewController. That's how I create it and register for notification:
- (id)initWithHashType:(NSString *)hashType {
self = [self initWithNibName:#"SHHashResultView" bundle:[NSBundle mainBundle]];
if (self) {
[self setHashType:hashType];
}
return self;
}
- (void)setHashType:(NSString *)hashType {
[self.label setStringValue:[NSString stringWithFormat:#"%#:", hashType]];
_hashType = hashType;
NSString *notificationName = [NSString stringWithFormat:#"%#%#",
gotResultNotification,
_hashType];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(gotResult:)
name:notificationName
object:nil];
}
Actually, the question was based on a false premise. I thought that notification never came through because I never saw the information displayed in the GUI; however, my error was in the code of controllers (not published there) which made possible the situation in which the GUI first got results of hash calculation and only after that got information about a new input — which resulted in deleting all the text and activating progress animation.

iOS: Core Data class method

Is it possible and practical to create a Core Data class method that will return the current instance of managedObjectContext? I am wondering so that I can segue to other controllers and load modal views without having to pass the managedObjectContext.
Also if I am using Core Data with dispatch_async I know I need to create my own instance of managedObjectContext but I can use the same coordinator. Will this make the information accessible both inside the dispatch_async and in the main thread?
I am basically using the dispatch_async to get data from the API and store it while the user is using the application.
In the past, I've created a Core Data manager singleton class that has simplified things. Here is an example, but this is pre-iOS5/ARC, so some changes need to be made.
I had a similar issue when trying to asynchronously getting data from my server to the app. My method is a bit different, but basically here it is (this is a 4.3 project, so no ARC):
The following methods are in my DataUpdater singleton. This first method is called at app startup:
- (void) update { //download the updates on a new thread
[NSThread detachNewThreadSelector:#selector(updateThread)
toTarget:self withObject:nil];
}
It initializes a thread with this selector, which is responsible only for downloading the content from the API, then passing it back to the main thread to be saved.
- (void) updateThread { //the actual update thread
//New thread, new auto-release pool
//(dunno if you need to do anything fancy for ARC)
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
//...
//YOUR CODE TO DOWNLOAD (BUT *NOT* SAVE) DATA FROM THE SERVER
//DON'T CREATE ANY MANAGED OBJECTS HERE
//...
//Pass the data to the main thread to perform
//the commit to the Core Data Model
[self performSelectorOnMainThread:#selector(saveUpdate:)
withObject:data waitUntilDone:NO];
//kill the thread & the auto-release pool
[NSThread exit];
[pool release];
}
Now that we're back on the main thread, the data is added to the Core Data Model and then the context is saved.
- (void) saveUpdate:(NSArray *) data {
//add the objects to your Core Data Model
//and save context
NSError * error = nil;
[[[CoreManager defaultCoreManager] CoreContext] save:&error];
if (error) {
[NSException raise:#"Unable to save data update"
format:#"Reason: %#", [error localizedDescription]];
} else {
[[NSNotificationCenter defaultCenter] postNotification:
[NSNotification notificationWithName:#"DONE" object:nil]];
}
}
Dealing with the first part of the question only (you shouldnt really ask multiple questions!) you don't have to pass the managed object context around - presumably you are passing a managed object? In that case the context is available as a property of the managed object itself - .managedObjectContext.

NSFetchedResultsController not displaying changes from background thread

My app is using an NSFetchedResultsController tied to a Core Data store and it has worked well so far, but I am now trying to make the update code asynchronous and I am having issues. I have created an NSOperation sub-class to do my updates in and am successfully adding this new object to an NSOperationQueue. The updates code is executing as I expect it to and I have verified this through debug logs and by examining the SQLite store after it runs.
The problem is that after my background operation completes, the new (or updated) items do not appear in my UITableView. Based on my limited understanding, I believe that I need to notify the main managedObjectContext that changes have occurred so that they may be merged in. My notification is firing, nut no new items appear in the tableview. If I stop the app and restart it, the objects appear in the tableview, leading me to believe that they are being inserted to the core data store successfully but are not being merged into the managedObjectContext being used on the main thread.
I have included a sample of my operation's init, main and notification methods. Am I missing something important or maybe going about this in the wrong way? Any help would be greatly appreciated.
- (id)initWithDelegate:(AppDelegate *)theDelegate
{
if (!(self = [super init])) return nil;
delegate = theDelegate;
return self;
}
- (void)main
{
[self setUpdateContext:[self managedObjectContext]];
NSManagedObjectContext *mainMOC = [self newContextToMainStore];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:updateContext];
[self setMainContext:mainMOC];
// Create/update objects with mainContext.
NSError *error = nil;
if (![[self mainContext] save:&error]) {
DLog(#"Error saving event to CoreData store");
}
DLog(#"Core Data context saved");
}
- (void)contextDidSave:(NSNotification*)notification
{
DLog(#"Notification fired.");
SEL selector = #selector(mergeChangesFromContextDidSaveNotification:);
[[delegate managedObjectContext] performSelectorOnMainThread:selector
withObject:notification
waitUntilDone:YES];
}
While debugging, I examined the notification object that is being sent in contextDidSave: and it seems to contain all of the items that were added (excerpt below). This continues to make me think that the inserts/updates are happening correctly but somehow the merge is not being fired.
NSConcreteNotification 0x6b7b0b0 {name = NSManagingContextDidSaveChangesNotification; object = <NSManagedObjectContext: 0x5e8ab30>; userInfo = {
inserted = "{(\n <GCTeam: 0x6b77290> (entity: GCTeam; id: 0xdc5ea10 <x-coredata://F4091BAE-4B47-4F3A-A008-B6A35D7AB196/GCTeam/p1> ; data: {\n changed =
The method that receives your notification must indeed notify your context, you can try something like this, which is what I am doing in my application:
- (void)updateTable:(NSNotification *)saveNotification
{
if (fetchedResultsController == nil)
{
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
//Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
else
{
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
// Reload your table view data
[self.tableView reloadData];
}
}
Hope that helps.
Depending on the specifics of what you are doing, you may be going about this the wrong way.
For most cases, you can simply assign a delegate using NSFetchedResultsControllerDelegate. You provide an implementation for one of the methods specified in "respondingToChanges" depending on your needs, and then send the tableView a reloadData message.
The answer turned out to be unrelated to the posted code which ended up working as I expected. For reasons that I am still not entirely sure of, it had something to do with the first launch of the app. When I attempted to run my update operation on launches after the Core Data store was created, it worked as expected. I solved the problem by pre-loading a version of the sqlite database in the app so that it did not need to create an empty store on first launch. I wish I understood why this solved the problem, but I was planning on doing this either way. I am leaving this here in the hope that someone else may find it useful and not lose as much time as I did on this.
I've been running into a similar problem in the simulator. I was kicking off an update process when transitioning from the root table to the selected folder. The update process would update CoreData from a web server, save, then merge, but the data didn't show up. If I browsed back and forth a couple times it would show up eventually, and once it worked like clockwork (but I was never able to get that perfect run repeated). This gave me the idea that maybe it's a thread/event timing issue in the simulator, where the table is refreshing too fast or notifications just aren't being queued right or something along those lines. I decided to try running in Instruments to see if I could pinpoint the problem (all CoreData, CPU Monitor, Leaks, Allocations, Thread States, Dispatch, and a couple others). Every time I've done a "first run" with a blank slate since then it has worked perfectly. Maybe Instruments is slowing it down just enough?
Ultimately I need to test on the device to get an accurate test, and if the problem persists I will try your solution in the accepted answer (to create a base sql-lite db to load from).

Exception/Error handling in Objective-C (iPhone app)

I actually have two questions regarding exception/error handling in the iPhone app that I am making:
The app uses Internet, but when there's no connection, the app just dies (during launch). How can I handle this to print some infomsg to the user, instead of just getting thrown back to the springboard?
Can someone show me an example of how to handle for instance a "page not found" or "no contact with server" error, so I can give some sort of info to the user in the same way as above?
For crashes, the first step is to use error messages and the debugger to figure out what call is causing the problem. If the problem is caused by an uncaught exception, read this Apple article on exception handling. The specific answer really depends on your code and exactly what is causing the crash, so I won't speculate about a particular solution.
As far as detecting server error response codes (such as 404), that's more specific to WebKit. I assume you're using UIWebView on iPhone, and you've probably noticed that none of the primary methods return errors. This is because it uses a delegate model to report progress or errors asynchronously. (It makes sense because you don't want your UI code to be at the mercy of a slow-loading (or non-existent) webpage. To be notified of such errors, there are a few steps.
Adopt the UIWebViewDelegate protocol, usually in the same class that will start the webpage load for convenience.
Set that object as the delegate of the UIWebView instance. (It has a delegate property, so you can use something like either uiView.delegate = self or [uiView setDelegate:self] based on what you prefer.)
Implement the webView:didFailLoadWithError: method in that class. (You can be notified when the load finishing by implementing webViewDidFinishLoad: as well.) This is where you include the logic of what should happen when an error occurs.
I didn't see any detailed documentation on the content of any particular errors handed back via this delegate method, but it's a standard NSError object, and I recommend checking out the contents by calling its methods, such as -localizedDescription and -userInfo.
Here is some sample code with #import statements excluded for brevity.
MyClass.h
#interface MyClass : NSObject <UIWebViewDelegate> {
IBOutlet UIWebView* myWebView;
}
-(void)webView:(UIWebView*)webView didFailLoadWithError:(NSError *)error;
#end
MyClass.m
#implementation MyClass
- (id) init {
if ((self = [super init]) == nil)
return nil;
// initialize myWebView
myWebView.delegate = self;
return self;
}
- (void) webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error {
...
}
#end
Testing for a connection is pretty easy...
NSString * test = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://www.stackoverflow.com"]];
if (test == nil) {
//display an alertview saying you don't have an internet connection
}
Using a URL to test for a connection is not a good idea, it is not robust enough to determine if the internet connection is down, the website is down or some other network issue etc and above all it adds an overhead to the call as far as network traffic.
Look at the Reachability demo on the Apple site, it uses the correct way to determine connectivity, including whether you are on wifi etc.