NSApp nextEventMatchingMask with dispatch inside dispatch - objective-c

I am currently developing an application that runs its own event loop. If i use dispatch async when not enqueued from another dispatch, its block runs correctly.
Example:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"This should appear!");
});
for (;;)
{
NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
untilDate:[NSDate distantPast]
inMode:NSDefaultRunLoopMode
dequeue:YES];
[NSApp sendEvent:event];
}
If I run this, the log will be called as expected.
But let's say I put a wrap around that dispatch, like this:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"This should appear!");
});
for (;;)
{
NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
untilDate:[NSDate distantPast]
inMode:NSDefaultRunLoopMode
dequeue:YES];
[NSApp sendEvent:event];
}
});
If i run this code, the log will not be executed. I can imagine that since extEventMatchingMask: is being called inside a enqueued block, it will not be able to handle future blocks that are dispatched. But the weird thing is that if I use performSelector: it gets executed even inside a enqueued block.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"This should appear!");
});
[self performSelector:#selector(logAppear) withObject:nil afterDelay:2];
for (;;)
{
NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
untilDate:[NSDate distantPast]
inMode:NSDefaultRunLoopMode
dequeue:YES];
[NSApp sendEvent:event];
}
});
-(void) logAppear {
NSLog(#"This should appear!");
}
If I run this example the method logAppear will be called as expected.
So, in short I wanted to ask why the second case doesn't work, and how (if possible) can I run a event loop inside an enqueued block that is able to run dispatch blocks.

After some experimenting I realized that what I needed to do was to make sure that that event loop was executed outside any enqueued block. We can do this by putting the loop inside a method and make sure it is called by a performSelector.
Example:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"This should appear!");
});
[self performSelector:#selector(logAppear) withObject:nil afterDelay:2];
[self performSelector:#selector(mainLoop) withObject:nil afterDelay:0];
});
-(void) mainLoop {
for (;;)
{
NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
untilDate:[NSDate distantPast]
inMode:NSDefaultRunLoopMode
dequeue:YES];
[NSApp sendEvent:event];
}
}
-(void) logAppear {
NSLog(#"This should appear!");
}
By running this example we now get both logs as expected

Related

Run methods in background on certain intervel

(void)applicationWillResignActive:(UIApplication *)application {
NSLog(#"to background");
UIApplication *app = [UIApplication sharedApplication];
// Request permission to run in the background. Provide an
// expiration handler in case the task runs long.
NSAssert(bgTask == UIBackgroundTaskInvalid, nil);
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
// Synchronize the cleanup call on the main thread in case
// the task actually finishes at around the same time.
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task.
[self performSelector:#selector(hai) withObject:nil afterDelay:30];
NSLog(#"App staus: applicationDidEnterBackground");
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
});
}

Run method only after other

i need to make this methods:
1) find and remove matches
2) fill empty cells
3) do 1 and 2 while no matches found
4) only when no matches found - find reshuffle method
How i do this :
[self launchAsyncCheckAndFill:^{
[self isReshuffleNeeded];
}];
-(void)launchAsyncCheckAndFill:(void(^)(void))completionHandler {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performSelectorOnMainThread:#selector(check) withObject:nil waitUntilDone:true];
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler();
});
});
}
- (void) check {
[self performSelector:#selector(fillEmptyCells) withObject:nil afterDelay:0.4];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
if ([self checkMapForMatchesWithFindMatchesMode:NO isCopyVector:NO]) {
[self check];
}
});
}
First of all i'm remove matches, then i go this methods : first of all i go launchAsyncCheckAndFill method, that on call back must start reshuffle method.
launchAsyncCheckAndFill must send callback only when check method is done. Check method is done in loop, this method must always fillEmptyCells, then checkmatches and while matches not found, only then must work callback on launchAsyncCheckAndFill .
But now behavior, that in loop always removing match, but method on reshuffle works after first remove and fill.
btw time 0.4 , 0.5 it's for animation, for this time figures moves and changes position. unfortunately in cocos i can only use
[elem.sprite runAction:[CCMoveTo actionWithDuration:0.4f position:elemPos1]];
that put position only after delay.
UPDATE:
[self launchAsyncCheckAndFills];
[self isReshuffleNeeded];
-(void)launchAsyncCheckAndFills {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
[self check];
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
}
- (void) check {
[self performSelector:#selector(fillEmptyCells) withObject:nil afterDelay:0.4];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
if ([self checkMapForMatchesWithFindMatchesMode:NO isCopyVector:NO]) {
[self check];
}
});
}

Activity Indicator Continues after Block

got this code:
[self setQRCodeScannerMode:false];
[self.activityIndicator startAnimating];
TBXMLSuccessBlock successBlock = ^(TBXML *tbxmlDocument) {
if ([[TBXML elementName:tbxmlDocument.rootXMLElement] isEqualToString:#"xxxxxx"]){
[self setQRCodeScannerMode:true];
} else {
[self setQRCodeScannerMode:true];
}
};
The thing is that when I set my scan mode to true inside the method [setQRCodeScannerMode] I'm stopping the activity indicator.
But surprise!!! the activity indicator is still working and messing with my view after some seconds.
What can I do?
You need to stop the activity indicator in your completion block:
[self setQRCodeScannerMode:false];
[self.activityIndicator startAnimating];
TBXMLSuccessBlock successBlock = ^(TBXML *tbxmlDocument) {
if ([[TBXML elementName:tbxmlDocument.rootXMLElement] isEqualToString:#"xxxxxx"]){
[self setQRCodeScannerMode:true];
} else {
[self setQRCodeScannerMode:true];
}
[self.activityIndicator stopAnimating];
};
This assumes the completion block is called on the main thread. If there is no guarantee that the completion block is called on the main thread you can do this:
[self setQRCodeScannerMode:false];
[self.activityIndicator startAnimating];
TBXMLSuccessBlock successBlock = ^(TBXML *tbxmlDocument) {
if ([[TBXML elementName:tbxmlDocument.rootXMLElement] isEqualToString:#"xxxxxx"]){
[self setQRCodeScannerMode:true];
} else {
[self setQRCodeScannerMode:true];
}
if ([NSThread isMainThread]) {
[self.activityIndicator stopAnimating];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndicator stopAnimating];
});
}
};
You are supposed to call [self.activityIndicator stopAnimating]; after you are finished with it, if thats not working for you, try this!
[self.activityIndicator performSelector:#selector(stopAnimating) withObject:nil afterDelay:0.1];
Hope that Helps!
One thing that you can do is working with the indicator on a different thread i.e. not on the Main Thread. You can use NSOperationQueue and NSInvocationOperation
simply add this code after the block
[self.activityIndicator stopAnimating];

How do I post a NSNotification when using Grand Central Dispatch?

I found that as predicted when I was writing an image to file that my UI was blocked for the duration, which was not acceptable. When I write the image to file I then post an NS Notification so that I can do some other specific jobs related to that completion. Original working but UI blocking code:
-(void)saveImageToFile {
NSString *imagePath = [self photoFilePath];
BOOL jpgData = [UIImageJPEGRepresentation([[self captureManager] stillImage], 0.5) writeToFile:imagePath atomically:YES];
if (jpgData) {
[[NSNotificationCenter defaultCenter] postNotificationName:kImageSavedSuccessfully object:self];
}
To avoid the UI blocking I have put the writeToFile: into a Grand Central Dispatch queue so it runs as a concurrent thread. But when the write is completed and the thread is done, I want to post an NSNotification. I cannot as the code is shown here because it is in a background thread. But that is the functionality I want to accomplish, realizing this is not workable code:
-(void)saveImageToFile {
NSString *imagePath = [self photoFilePath];
// execute save to disk as a background thread
dispatch_queue_t myQueue = dispatch_queue_create("com.wilddogapps.myqueue", 0);
dispatch_async(myQueue, ^{
BOOL jpgData = [UIImageJPEGRepresentation([[self captureManager] stillImage], 0.5) writeToFile:imagePath atomically:YES];
dispatch_async(dispatch_get_main_queue(), ^{
if (jpgData) {
[[NSNotificationCenter defaultCenter] postNotificationName:kImageSavedSuccessfully object:self];
}
});
});
}
What is the correct mechanism here to post this notification to gain the functionality I want ?
A couple possibilities here.
1)
How about [NSObject performSelectorOnMainThread: ...] ?
E.G.
-(void) doNotification: (id) thingToPassAlong
{
[[NSNotificationCenter defaultCenter] postNotificationName:kImageSavedSuccessfully object:thingToPassAlong];
}
-(void)saveImageToFile {
NSString *imagePath = [self photoFilePath];
// execute save to disk as a background thread
dispatch_queue_t myQueue = dispatch_queue_create("com.wilddogapps.myqueue", 0);
dispatch_async(myQueue, ^{
BOOL jpgData = [UIImageJPEGRepresentation([[self captureManager] stillImage], 0.5) writeToFile:imagePath atomically:YES];
dispatch_async(dispatch_get_main_queue(), ^{
if (jpgData) {
[self performSelectorOnMainThread: #selector(doNotification:) withObject: self waitUntilDone: YES];
}
});
});
}
More details at http://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/occ/instm/NSObject/performSelectorOnMainThread:withObject:waitUntilDone:
or 2)
Completion Callbacks
as seen at How can I be notified when a dispatch_async task is complete?
-(void)saveImageToFile {
NSString *imagePath = [self photoFilePath];
// execute save to disk as a background thread
dispatch_queue_t myQueue = dispatch_queue_create("com.wilddogapps.myqueue", 0);
dispatch_async(myQueue, ^{
BOOL jpgData = [UIImageJPEGRepresentation([[self captureManager] stillImage], 0.5) writeToFile:imagePath atomically:YES];
dispatch_async(dispatch_get_main_queue(), ^{
if (jpgData) {
[[NSNotificationCenter defaultCenter] postNotificationName:kImageSavedSuccessfully object:self];
}
});
});
}
That is already correct. However why do you need to use notification if you already dispatch_get_main_queue()?
Just use
dispatch_async(dispatch_get_main_queue(), ^{
if (jpgData) {
//Do whatever you want with the image here
}
});
Anyway your original solution is fine. It's not blocking. Basically it'll save the file at other thread and once it's done it'll do what it takes.
What ever you do will be done on the same thread with the thread that call [[NSNotificationCenter defaultCenter] postNotificationName:kImageSavedSuccessfully object:self]; namely main thread.

Problem with UIImageView loading via OperationQueue

I load a UIImageView using an NSOperationQueue.
The load fetches an image from the Internet and then adds it to an image view. The problem I have is that the method finishes but it takes about 3 seconds later for the image view to actually either show the image or remove the image view from the superview...
- (void)viewDidLoad { NSLog(#"AirportDetailView: viewDidLoad");
[super viewDidLoad];
[self.activity startAnimating];
self.queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(loadImage) object:NULL];
[self.queue addOperation:operation];
[operation release];
}
-(void)loadImage {
[myAp ImageForAp];
NSLog(#"ImageFor Ap Ended, %#",myAp.ApDiagram);
[self.activity stopAnimating];
if (myAp.ApDiagram==NULL) {
NSLog(#"Finally Gets Here");
[self.Diagram removeFromSuperview];
}
else {
NSLog(#"Finally Gets Here with Diag");
[self.Diagram setBackgroundImage:myAp.ApDiagram forState:UIControlStateNormal];
}
}
The NSLOG shows a delay between the first two log statements of about 3 seconds can't understand why....
Updated with my Latest Code......
-(void)loadImage {
[myAp ImageForAp];
NSLog(#"ImageFor Ap Ended, %#",myAp.ApDiagram);
[self performSelectorOnMainThread:#selector(UpdateUI) withObject:nil waitUntilDone:NO];
}
-(void)UpdateUI {
[self.activity stopAnimating];
if (myAp.ApDiagram==NULL) {
NSLog(#"Finally Gets Here");
[self.Diagram removeFromSuperview];
}
else {
NSLog(#"Finally Gets Here with Diag");
[self.Diagram setBackgroundImage:myAp.ApDiagram forState:UIControlStateNormal];
}
}
Make sure that the loadImage method is being run on the main thread. All UI operations need to happen on the main thread to work as expected. Your code will need to look similar to this.
-(void)loadImage {
[myAp ImageForAp];
[self performSelectorOnMainThread:#selector(UpdateUI) withObject:nil waitUntilDone:NO];
}
-(void)UpdateUI {
[self.activity stopAnimating];
if (myAp.ApDiagram==NULL) {
[self.Diagram removeFromSuperview];
}
else {
[self.Diagram setBackgroundImage:myAp.ApDiagram forState:UIControlStateNormal];
}
}
There is also a possible memory leak inside of viewDidLoad.
self.queue = [NSOperationQueue new]; should be changed to
self.queue = [[[NSOperationQueue alloc] init] autorelease];