'Cause of the difficulties I found combining UITableView and SpriteKit Framework I decided to develop a custom SKTableView (provided with full scrolling system). Everything works really fine but I have serious problem understanding an error I get during the update process.
My SKTableView shows the user a list of items in alphabetical order. Due to the tricky algorithm I should have create in order to update this list (add a cell in a defined position, refresh the headers and scroll down all of the items), I decided to simply update the database and reload the list (by removing existing content and recreate it) while showing asynchrony a notification to the user.
What is happen is the application to success reload the list once, once again, then after some random updates ... here comes the chaos
Issues
The application crashes giving me EXC_BAD_ACCESS during the removeAllChildren methods.
The application crashes with the same error during a for statement (???) or during other statements
The application crashes giving me jet_context::set_fragment_texture... error.
Debug:
The console window doesn't show any kind of information.
I can't monitoring the application using 'Allocation' or 'Zombies' instruments because it critically slow down the application. It takes more than 2 minutes to even load the SKTableView during the init method.
Code
Interface
/** Container for all the table items. */
#property (nonatomic, readonly) SKNode *nodeContainer;
Initialization
- (instancetype)init
{
if (self = [super init]) {
...
Create the style of the SKNode
...
// Container initialization
_nodeContainer = [[SKNode alloc] init];
_nodeContainer.position = CGPointZero;
_nodeContainer.zPosition = 0;
[self addChild:_nodeContainer];
// Generate the content
[self generateContent];
}
return self;
}
Populate the table
- (void)generateContent
{
// Checks if the node exists, if it does, remove all of its children
// Note: used for the recreation of the list
if (_nodeContainer != nil)
[_nodeContainer removeAllChildren];
...
Initializes several SKSpriteNode and SKLabelNode
...
[_nodeContainer addChild:imgItem];
[_nodeContainer addChild:labelItem];
}
Reload the content
...
Update the database
...
[self showProgressNotification];
// Reload the content showing a notification
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Reload the content
[self generateContent];
dispatch_async(dispatch_get_main_queue(), ^{
[self hideProgressNotification];
});
});
I really cannot figure it out what's happening. The code crashes overtime in a different line without a logic. I mean, is this the right way to remove and recreate items? Any advise?
Related
In my app, a graph in one view can be dragged to a second view so that the new graph replaces the second view (like a copy/paste effect with a drag and drop feature). The app works if the delegate protocol is taken out so that the second view handles the change in function itself. When the protocol is added, the app crashes in the main file at
return UIApplicationMain(argc, argv, nil, NSStringFromClass([Load_CreatorAppDelegate class]));.
There isn't any error output other than the standard (lldb). Even when I take out the call to the delegate (keeping in the code), the app crashes. I know that it has to be related to the protocol code, though, because it worked fine before that.
Here is part of the code for the second view (BeamView):
[self drawSupportsAtLeftPoint:self.beamBottomLeft rightPoint:self.beamBottomRight inContext:context :leftPin :rightPin];
BOOL pt = NO;
if (self.tempLoad) {
//self.loadGraph = [self.dataSource changeToTempLoad:self]; NOTE #1
//if (self.tempPtLoad.x != 0 || self.tempPtLoad.y != 0) pt = YES;
pt = [self changeLoad];
[self drawLoadWithFunction:self.loadGraph inContext:context fromPoint:self.beamTopLeft toPoint:self.beamTopRight withAlpha:0.3 isPointLoad:pt inBlack:YES];
}
else {
self.loadGraph = ^(int x) {return x/15;};
[self drawLoadWithFunction:self.loadGraph inContext:context fromPoint:self.beamTopLeft toPoint:self.beamTopRight withAlpha:1 isPointLoad:pt inBlack:NO];
}
self.tempLoad = NO;
NOTE #1: These lines that are commented out are the ones that call on the delegate. Those two methods and their implementation are the only changes I made.
I'm completely confused, any help would be greatly appreciated. What are possible reasons the app will crash in the main file?
Ok! I feel kind of stupid, but it turned out that the crash didn't have anything to do with delegation (well, kind of). I had deleted outlets in the ViewController.m file without disconnecting them in IB, which was causing the crash.
I'd forgotten that I'd done that, and so it took me a while to think of it--it wasn't until I went back to an older saved version that I saw the differences.
We're having this issue where different threads see different data on the same records but with different managed object contexts (moc). Our app syncs in the background to a server API. All of the syncing is done on it's own thread and using it's own moc. However, we've discovered that when data gets updated on the main moc that change in data is not shown in the background moc. Any ideas what could be happening? Here's some more details: we're using grand central dispatch like so to put the sync operations on it's own thread: We've checked which queue things are running on and it all is happening on the queue expected.
- (void) executeSync; {
dispatch_async(backgroundQueue, ^(void) {
if([self isDebug])
NSLog(#"ICSyncController: executeSync queue:%# \n\n\n\n\n", [self queue]);
for(id <ICSyncControllerDelegate> delegate in delegates){
[delegate syncController:self];
}
if([ICAccountController sharedInstance].isLoggedIn == YES && shouldBeSyncing == YES) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 300ull * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
[self executeSync];
});
}
});
}
here's how we create the background moc and we've confirmed that it's created on the background queue.
- (NSManagedObjectContext*)backgroundObjectContext {
if (_backgroundObjectContext)
return _backgroundObjectContext;
_backgroundObjectContext = [[NSManagedObjectContext alloc] init];
[_backgroundObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
[_backgroundObjectContext setStalenessInterval:0.0];
return _backgroundObjectContext;
}
I should add that our background moc is requerying for data and those records returned from that action still have the old values for some fields. How does the background moc get the current data that was already saved by the main moc? I thought just by requerying I would get the current state of these records..
by requerying I mean the following:
The background MOC is executing another "query" to get "fresh" data after the records have been changed by the main moc, yet the data has old values - not the updated values seen in the main moc.
+ (NSArray *)dirtyObjectsInContext:(NSManagedObjectContext *)moc {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(memberships, $m, $m.category.name == %# AND $m.syncStatus > %d).#count > 0", MANAGED_CATEGORY_FAVORITES, ManagedObjectSynced];
return [self managedObjectsWithPredicate:predicate inContext:moc];
}
Your help is hugely appreciated as we've been trying to figure this out, or find a work around that doesn't include ditching our threads for days now.
That's how it's supposed to work -- indeed, an important role of the managed object context is to protect you from changes to the data made in other threads. Imagine the havoc that would result if you had a background thread modifying the same objects that the main thread was using without some sort of synchronization scheme.
Read Communicating Changes Between Contexts to learn how to merge changes from one context into another.
I use the following code to listen for changes on context 2, so that context 1 keeps up to date:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:context1
selector:#selector(contextTwoUpdated:)
name:NSManagedObjectContextDidSaveNotification
object:context2];
it causes this method to be called on context 1, and i invoke the merge method:
- (void)contextTwoUpdated:(NSNotification *)notification {
[context1 mergeChangesFromContextDidSaveNotification:notification];
}
a side effect of this is any NSFetchedResultsController that is attached to context1 will send a variety of messages to its delegate informing it of the changes,
i've never tried listening both ways while the user changes the object and you update them on the user from behind - i suspect you may have to manage merges if that's the case, since it's one-way (and all user driven) for me i assume all updates to be valid
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).
I'm trying to get data from a website- xml. Everything works fine.
But the UIButton remains pressed until the xml data is returned and thus if theres a problem with the internet service, it can't be corrected and the app is virtually unusable.
here are the calls:
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if(!appDelegate.XMLdataArray.count > 0){
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[appDelegate GetApps]; //function that retrieves data from Website and puts into the array - XMLdataArray.
}
XMLViewController *controller = [[XMLViewController alloc] initWithNibName:#"MedGearsApps" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
}
It works fine, but how can I make the view buttons functional with getting stuck. In other words, I just want the UIButton and other UIButtons to be functional whiles the thing works in the background.
I heard about performSelectorInMainThread but I can't put it to practice correctly.
You don’t understand the threading model much and you’re probably going to shoot yourself in the foot if you start adding asynchronous code without really understanding what’s going on.
The code you wrote runs in the main application thread. But when you think about it, you don’t have to write no main function — you just implement the application delegate and the event callbacks (such as touch handlers) and somehow they run automatically when the time comes. This is not a magic, this is simply a Cocoa object called a Run Loop.
Run Loop is an object that receives all events, processes timers (as in NSTimer) and runs your code. Which means that when you, for example, do something when the user taps a button, the call tree looks a bit like this:
main thread running
main run loop
// fire timers
// receive events — aha, here we have an event, let’s call the handler
view::touchesBegan…
// use tapped some button, let’s fire the callback
someButton::touchUpInside
yourCode
Now yourCode does what you want to do and the Run Loop continues running. But when your code takes too long to finish, such as in your case, the Run Loop has to wait and therefore the events will not get processed until your code finishes. This is what you see in your application.
To solve the situation you have to run the long operation in another thread. This is not very hard, but you’ll have to think of a few potential problems nevertheless. Running in another thread can be as easy as calling performSelectorInBackground:
[appDelegate performSelectorInBackground:#selector(GetApps) withObject:nil];
And now you have to think of a way to tell the application the data has been loaded, such as using a notification or calling a selector on the main thread. By the way: storing the data in the application delegate (or even using the application delegate for loading the data) is not very elegant solution, but that’s another story.
If you do choose the performSelectorInBackground solution, take a look at a related question about memory management in secondary threads. You’ll need your own autorelease pool so that you won’t leak autoreleased objects.
Updating the answer after some time – nowadays it’s usually best to run the code in background using Grand Central Dispatch:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// No explicit autorelease pool needed here.
// The code runs in background, not strangling
// the main run loop.
[self doSomeLongOperation];
dispatch_sync(dispatch_get_main_queue(), ^{
// This will be called on the main thread, so that
// you can update the UI, for example.
[self longOperationDone];
});
});
Use NSURLConnection's connectionWithRequest:delegate: method. This will cause the specified request to be sent asynchronously. The delegate should respond to connection:didReceiveResponse: and will be sent that message once the response is completely received.
You can make use of a background operation that gets pushed into the operation queue:
BGOperation *op = [[BGOperation alloc] init];
[[self operationQueue] addOperation:op];
[op release];
I've created specific "commands" that get executed in the background:
#implementation BGOperation
# pragma mark Memory Management
- (BGOperation *)init
{
if ((self = [super init]) != nil)
/* nothing */;
return self;
}
- (void)dealloc
{
self.jobId = nil;
[super dealloc];
}
# pragma mark -
# pragma mark Background Operation
- (void)main
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[appDelegate GetApps];
[pool release];
return;
}
#end
After completion it might be a good idea to send a notification to the main thread because the internal database has been changed.
It looks as if you might be using NSURLConnection inside your getApps method. If so, you should convert it to an asynchronous call.
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.