NSTimer in NSOperation subclass - multithreading

Dear community.
I try to setup NSTimer:
#interface GetExternalInfo : NSOperation {
NSTimer *keepAliveTimerMain;
#property (nonatomic,retain) NSTimer *keepAliveTimerMain;
.m:
#synthesize keepAliveTimerMain
-(void) main;
{
self.keepAliveTimerMain = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(keepAlive:) userInfo:nil repeats:YES];
[keepAliveTimerMain fire];
[[NSRunLoop currentRunLoop] addTimer:self.keepAliveTimerMain forMode: NSDefaultRunLoopMode];
BOOL timerState = [keepAliveTimerMain isValid];
NSLog(#"STAT:Timer Validity is: %#", timerState?#"YES":#"NO");
- (void)keepAlive:(NSTimer *)theTimer
{
BOOL currentState = [self isCancelled];
NSLog(#"STAT:cancelled state is %#.\n",currentState?#"YES":#"NO");
}
In Logs
2011-02-02 18:58:31.041
snow[54705:5d07] STAT:cancelled state
is NO. 2011-02-02 18:58:31.042
snow[54705:5d07] STAT:Timer Validity
is: YES
i see this only once. No next repeat attempts every 5 seconds
any opinions in this case? GC is enabled.

Do you have a runloop in the current thread? The timer needs a runloop to be able to fire. I notice you call -fire manually which explains why -keepAlive is called, but this doesn't actually start the timer.

You need to add your timer to a run loop, something like [[NSRunLoop currentRunLoop] addTimer:keepAliveTimerMain forMode: NSDefaultRunLoopMode];.
EDIT: The code you posted shows that you manually fire the timer before adding it to the runloop. This will fire and invalidate the timer, so you are actually trying to schedule an invalid timer on the runloop. That is why you only see the NSLog message once.

Related

Threading issue with UICollectionview loading images from CloudKit

I'm having a threading issue loading images in a collectionview where the data is coming from cloudkit. I know this is a threading/blocking issue because before I implemented CK, I dumped some images in a folder on my desktop and read/parsed them from there and had no issue. With CK, I just created a handful of records via the dashboard and I'm successfully getting the expected records returned and use the images from those results to populate the CV cells. I store the CK query results in an array and use the size of that array to set the numberOfItemsInSection delegate.
Here's the issue...in the numberOfItemsInSection delegate method, I'm calling the model class, which executes the CK query. Since that is obviously a network call, I put that in a background thread. From logging, I can see the query execute and the results come back very quickly - within 2-3 seconds. However, the CV cells never display and I don't see the custom cell get initialized (via logging). But if I tap the camera button and take a photo, which I've implemented, I take the resulting image and add it to the array, then call reloadData on the CV and all the cells (and images) appear, including the new image just taken with the camera.
By accident, I found out a hack that somewhat works, which is calling reloadData on the CV inside the background thread of the numberOfItemsInSection delegate method. As a result, I thought I might have stumbled on to the solution by switching back to the main thread when calling reloadData, but that put it in a sort of endless loop of continuously calling the numberOfItemsInSection method and cellForItemAtIndexPath and made it to where it lagged to a point that you could barely scroll and tapping on any of the cells wouldn't do anything.
At this point, after trying many, many various things, I'm at a complete loss on how to fix this. I know this is probably a pretty easy solution as it's very common to load images asynchronously to populate a collectionview or tableview. Can someone please provide some guidance? Thanks in advance!!!
#property (nonatomic) NSInteger numberOfItemsInSection;
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
NSLog(#"***numberOfItemsInSection***");
dispatch_queue_t fetchQ = dispatch_queue_create("load image data", NULL);
dispatch_async(fetchQ, ^{
self.numberOfItemsInSection = [self.imageLoadManager.imageDataArray count];
[self.myCollectionView reloadData]; // should be done on main thread!
});
NSLog(#"numberOfItemsInSection: %ld", (long)self.numberOfItemsInSection);
return self.numberOfItemsInSection;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell"; // string value identifier for cell reuse
ImageViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
NSLog(#"cellForItemAtIndexPath: section:%ld row:%ld", (long)indexPath.section, (long)indexPath.row);
cell.layer.borderWidth = 1.0;
cell.layer.borderColor = [UIColor grayColor].CGColor;
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
ImageData *imageData = [self.imageLoadManager imageDataForCell:indexPath.row]; // maps the model to the UI
dispatch_async(dispatch_get_main_queue(), ^{
if (imageData.imageURL.path) {
cell.imageView.image = [UIImage imageWithContentsOfFile:imageData.imageURL.path];
[cell setNeedsLayout];
} else {
// if imageURL is nil, then image is coming in from the camera as opposed to the cloud
cell.imageView.image = imageData.image;
[cell setNeedsLayout];
}
});
return cell;
}
before returning self.numberOfItemsInSection you should wait until the async call is finished. You can do that using semaphores. But then why are you doing this async? You are just getting the count of an array. And then you shouldn't reloadData there. Where do you start your CloudKit query? are you doing that onViewDidLoad? That is also an async operation. When that completes just doe a reloadData of your collectionView. Besides that doing this would be enough:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.imageLoadManager.imageDataArray count];
}
If you really want to use async there, then you do have to wait for the result. You could change your code to something like:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
NSLog(#"***numberOfItemsInSection***");
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_queue_t fetchQ = dispatch_queue_create("load image data", NULL);
dispatch_async(fetchQ, ^{
self.numberOfItemsInSection = [self.imageLoadManager.imageDataArray count];
[self.myCollectionView reloadData]; // should be done on main thread!
dispatch_semaphore_signal(sema);
});
NSLog(#"numberOfItemsInSection: %ld", (long)self.numberOfItemsInSection);
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return self.numberOfItemsInSection;
}
And then why do you go to the main queue in cellForItemAtIndexPath? It's already executed on the main queue.

NSFetchedResultsController do not get updated when the managedobjectcontext change

I make a program where I sometimes moves some anchor to another
When I move those anchors I would recompute distance of bizs nearby the 2 anchors (before and after anchors). The computation is done in background
I used this standard code to update stuff
+(void)commit {
// get the moc for this thread
[Tools breakIfLock];
NSManagedObjectContext *moc = [self managedObjectContext];
NSThread *thread = [NSThread currentThread];
DLog(#"threadKey commit%#" , [[self class]threadKey]);
if ([thread isMainThread] == NO) {
// only observe notifications other than the main thread
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextDidSave:) name:NSManagedObjectContextDidSaveNotification object:moc];
}
NSError *error;
if (![moc save:&error]) {
CLog(#"Error in Saving %#", error);
DLog(#"What the hell error is it");
}
else{
}
if ([thread isMainThread] == NO) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:moc];
}
//[GrabClass StopNetworkActivityIndicatorVisible];
}
+(void)contextDidSave:(NSNotification*)saveNotification {
dispatch_async(dispatch_get_main_queue(), ^{
BadgerNewAppDelegate *delegate = [BNUtilitiesQuick appDelegate];
DLog (#"currentThreadinContextDidSave: %#",[self threadKey]);
NSManagedObjectContext *moc = delegate.managedObjectContext; //delegate for main object
CLog(#"saveNotification : %#",saveNotification);
[moc mergeChangesFromContextDidSaveNotification:saveNotification];
});
//[moc performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:) withObject:saveNotification waitUntilDone:YES];
}
I break point and see that distances did get updated. Everything is fine
However the NSFetchedResultsController fetchedObjects doesn't seem to get updated and still use the old value.
How can that be?
Also the
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
DLog(#"controllerWillChangeContent: %#", controller);
[self.tableViewA beginUpdates];
}
is never called even though the NSManagedObjectContext has changes.
Well actually I wasn't sure if the managedObjectContext has changed or not. How do I know? I mean will change in managedObjectContext ensure changes in fetchController.fetchedObjects.
There is no caching as far as I know. How can I be sure of that too?
The NSFetchedResultsController documentation for fetchedObjects property states:
The results array only includes instances of the entity specified by
the fetch request (fetchRequest) and that match its predicate. (If the
fetch request has no predicate, then the results array includes all
instances of the entity specified by the fetch request.)
The results array reflects the in-memory state of managed objects in
the controller’s managed object context, not their state in the
persistent store. The returned array does not, however, update as
managed objects are inserted, modified, or deleted.
Availability Available in iOS 3.0 and later.
I can't say what the appropriate workaround is. My first thought is to call performFetch: in controllerDidChangeContent: in the delegate implementation.
The fetchedObjects array appears to update simply by overriding controllerDidChangeContent: with an empty implementation. This is the case using both the iPad and the iPad simulator for iOS 5.1.
There's clearly some discrepancy between the documentation and what I have observed. I have no explanation. Sorry. I can only suggest that you perform the fetch in controllerDidChangeContent: just to be safe.

Action Trigger when I hold UIButton for 2 second in iPhone

I have a UIButton in my application and an action which is triggered when I TouchDown UIButton.
Is it possible to detect a Touch-and-Hold on the UIButton on the iPhone? I want my action to trigger when the user holds the button for 2 seconds or more.
Any Ideas?
UILongPressGestureRecognizer is what you need. For example,
UILongPressGestureRecognizer *longPress_gr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(doAction:)];
[longPress_gr setMinimumPressDuration:2]; // triggers the action after 2 seconds of press
[yourButton addGestureRecognizer:longPress_gr];
To let your action get triggered only once(ie., when the 2 seconds duration is over), make sure you have your doAction: method looks something like this,
- (void)doAction:(UILongPressGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
// Your code here
}
}
On the other way you can use this NBTouchAndHoldButton. This is exactly what you want, and it is very easy to implement it:
TouchAndHoldButton * pageDownButton = [TouchAndHoldButton buttonWithType:UIButtonTypeCustom];
[pageDownButton addTarget:self action:#selector(pageDownAction:) forTouchAndHoldControlEventWithTimeInterval:0.2];
Good luck!

CLLocation manager updates from background thread

I'm launching a localization request using Grand Central Dispatch :
- (void) findGroceriesNearMe {
dispatch_queue_t downloadQueue = dispatch_queue_create("Groceries downloader", NULL);
dispatch_async(downloadQueue, ^{
CLLocationCoordinate2D userLocation = [LocationManagerController findMeWithCaller:self];
dispatch_async(dispatch_get_main_queue(), ^{
[self userSuccessFullyFound:userLocation];
});
});
dispatch_release(downloadQueue);
}
It calls a static method in my Singleton class LocationManager Controller :
+ (CLLocationCoordinate2D) findMeWithCaller: (UIViewController *) viewController {
LocationManagerController *locationManagerController = [LocationManagerController locationManagerController];
[locationManagerController startUpdates];
while(![locationManagerController getterDone]){
//mystique pour nous-- a approfondir
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
In the startUpdates method, the CLLocationManager, property of LocationManagerController, is initialized and asked to startUpdatingLocation.
Finally, the method when location updates happen :
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
locationDenied = NO;
NSLog(#"%f,%f",newLocation.coordinate.latitude,newLocation.coordinate.longitude);
NSDate* eventDate = newLocation.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
// On vérifie que la newLocation est récente
if (abs(howRecent) > 10.0) {
return;
}
// Test if it's not an invalid measurement
if (newLocation.horizontalAccuracy < 0) return;
// Test the measurement to see if it meets the desired accuracy
if (newLocation.horizontalAccuracy <= manager.desiredAccuracy)
{
latitude = newLocation.coordinate.latitude;
longitude = newLocation.coordinate.longitude;
locationDefined = YES;
[self setterDone:YES];
}
}
My problem is that the locationManager only send 3 location updates and then stops sending updates even though I didn't ask it to stop. So basically, I never get out of the while(![locationManagerController getterDone]) loop.
By the way, before trying to implement this using GCD, it was working fine so I guess the issue has to do with my implementation of multi-threading.
Any idea ?
Edit
I don't get any error in the console. The program just keeps running but I'm stuck in that while loop and nothing else happens after the 3 first location updates.
Thanks !
From CLLocationManager class reference:
Configuration of your location manager object must always occur on a
thread with an active run loop, such as your application’s main
thread.
A guess. If you are sitting at your desk and testing with your simulator the accuracy may not get better what you want
if (newLocation.horizontalAccuracy <= manager.desiredAccuracy)
So you may get stuck in your loop. Try with higher accuracy while at your desk. Also consider if the accuracy is never better that what you want since it maybe that the gps reception is not good.
Let me know if that helps or if I was way off the mark :-)
-- Fasttouch

Why is my app crashing when I modify a Core Data relationship in an NSOperation subclass?

Background
I've got the following tree of objects:
Name Project
Users nil
John nil
Documents nil
Acme Project Acme Project <--- User selects a project
Proposal.doc Acme Project
12:32-12:33 Acme Project
13:11-13:33 Acme Project
...thousands more entries here...
The user can assign a group to a project. All descendants get set to that project.
This locks up the main thread so I'm using NSOperations.
I'm using the Apple approved way of doing this, watching for NSManagedObjectContextDidSaveNotification and merging into the main context.
The Problem
My saves have been failing with the following error:
Failed to process pending changes before save. The context is still dirty after 100 attempts. Typically this recursive dirtying is caused by a bad validation method, -willSave, or notification handler.
What I've Tried
I've stripped all the complexities of my app away, and made the simplest project I could think of. And the error still occurs. I've tried:
Setting the max number of operations on the queue to 1 or 10.
Calling refreshObject:mergeChanges: at several points in the NSOperation subclass.
Setting merge policies on the managed object context.
Build and Analyze. It comes up empty.
My Question
How do I set relationships in an NSOperation without my app crashing? Surely this can't be a limitation of Core Data? Can it?
The Code
Download my project: http://synapticmishap.co.uk/CDMTTest1.zip
Main Controller
#implementation JGMainController
-(IBAction)startTest:(id)sender {
NSManagedObjectContext *imoc = [[NSApp delegate] managedObjectContext];
JGProject *newProject = [JGProject insertInManagedObjectContext:imoc];
[newProject setProjectName:#"Project"];
[imoc save];
// Make an Operation Queue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1]; // Also crashes with a higher number here (unsurprisingly)
NSSet *allTrainingGroupsSet = [imoc fetchAllObjectsForEntityName:#"TrainingGroup"];
for(JGTrainingGroup *thisTrainingGroup in allTrainingGroupsSet) {
JGMakeRelationship *makeRelationshipOperation = [[JGMakeRelationship alloc] trainGroup:[thisTrainingGroup objectID] withProject:[newProject objectID]];
[queue addOperation:makeRelationshipOperation];
makeRelationshipOperation = nil;
}
}
// Called on app launch.
-(void)setupLotsOfTestData {
// Sets up 10000 groups and one project
}
#end
Make Relationship Operation
#implementation JGMakeRelationshipOperation
-(id)trainGroup:(NSManagedObjectID *)groupObjectID_ withProject:(NSManagedObjectID *)projectObjectID_ {
appDelegate = [NSApp delegate];
imoc = [[NSManagedObjectContext alloc] init];
[imoc setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
[imoc setUndoManager:nil];
[imoc setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:imoc];
groupObjectID = groupObjectID_;
projectObjectID = projectObjectID_;
return self;
}
-(void)main {
JGProject *project = (JGProject *)[imoc objectWithID:projectObjectID];
JGTrainingGroup *trainingGroup = (JGTrainingGroup *)[imoc objectWithID:groupObjectID];
[project addGroupsAssignedObject:trainingGroup];
[imoc save];
trainingGroupObjectIDs = nil;
projectObjectID = nil;
project = nil;
trainingGroup = nil;
}
-(void)mergeChanges:(NSNotification *)notification {
NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
}
-(void)finalize {
appDelegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
imoc = nil;
[super finalize];
}
#end
#implementation NSManagedObjectContext (JGUtilities)
-(BOOL)save {
// If there's an save error, I throw an exception
}
#end
Data Model
Update 1
I've experimented some more, and even without the merge, the exception is still thrown. Just saving the managed object context in another thread after modifying a relationship is enough.
I have a shared persistent store coordinator with the app delegate. I've tried making a separate NSPersistentStoreCoordinator for the thread with the same URL as my data store, but Core Data complains.
I'd love to suggestions on how I can make a coordinator for the thread. The core data docs allude to there being a way of doing it, but I can't see how.
You are crossing the streams (threads in this case) which is very bad in CoreData. Look at it this way:
startTest called from a button (is IBAction, assuming button tap) on Main thread
Your for loop creates a JGMakeRelationship object using the initializer trainGroup: withProject: (this should be called init, and probably call super, but that's not causing this issue).
You create a new managed object context in the operation, on the Main thread.
Now the operation queue calls the operations "main" method from a worker thread (put a breakpoint here and you'll see it's not on the main thread).
Your app goes boom because you've accessed a Managed object Context from a different thread than the one you created it on.
Solution:
Initialize the managed object context in the main method of the operation.

Resources