I'm working on an iPad app that needs to spawn a dialog mid-function for some user interaction. In order to wait for the dialog, I run an NSRunLoop, however, this is preventing events on the dialog from being processed. This is how I spawn the dialog:
NSArray* listOfCompatibleTypes = [[NSArray alloc] initWithArray:[listOfCompatibleTypesAndSizesAsSet allObjects]];
[secondaryImplantChooserDialog setModalPresentationStyle:UIModalPresentationFormSheet];
[secondaryImplantChooserDialog setDefinesPresentationContext:YES];
[self presentViewController:secondaryImplantChooserDialog animated:NO completion:nil];
And the runloop is like this:
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
while (secondaryImplantChooserDialog.fDialogDone != YES)
{
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
The dialog box has a popover that contains a tableview. When I comment out the runloop, the table view's didSelectRowAtIndexPath(…) method gets called. When the loop is active, however, that method doesn't get called until the dialog gets dismissed.
I realize this isn't exactly an iOS-kosher design, so I should probably refactor it, but I was wondering if there was any way to process UI events while the runloop is active.
Polling is very rarely a good solution, particularly in a user-interactive environment like an iOS app. The functionality you are looking for is a semaphore - where you can block execution in one part of your app until another has completed. You can do this with Grand Central Dispatch but this is probably adding complexity when a better solution is to re-factor.
From what I understand you have a method which is performing some calculation or business-logic and it determines that additional information is needed. You could refactor something like:
if ([self haveEnoughInfo]) {
[self performFinalCalc];
} else {
[self gatherMoreInformation]; // Use a delegate or completion block to invoke [self performFinalCalc] once more information is gathered
}
Related
I have created a UIButton and on click event, I am showing an image in the web view. Also, I am refreshing the image in every 30 sec. But when I click on button multiple times, refresh method get called multiple time as well.
I want it to work like, It saves last click time and refreshes as per that time instead of multiple times.
What can I do for it?
I tried to kill all previous thread instead of the current thread but that's not working.
Please help if anyone already know the answer.
Below is my image refresh code:
- (void)refreshBanner:(id)obj {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (![SNRunTimeConfiguration sharedInstance].isInternetConnected) {
[self removeBannerAdWithAdState:kADViewStateNotConnectedToInternet];
return;
}
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
self.bannerPaused = YES;
return;
}
self.adView.hidden = YES;
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
topController = [SNADBannerView topViewControllerWithRootViewController:topController];
if ([self checkInViewHierarchy:self parentView:topController.view]) {
// NSLog(#"Visible View Is: %#", self.adId);
SNADMeta *meta = [[SNADDataBaseManager singletonInstance] adToShowWithBanner:YES excludeTyrooAd:YES audio:NO zoneId:self.adSoptZoneId fixedView:NO condition:nil contextualKeyword:nil onlyFromAJ:NO];
SNADAdLocationType type = SNADAdLocationTypeHeader;
if (self.bannerType == SmallViewTypeFooter) {
type = SNADAdLocationTypeFooter;
}
if (self.isFromCustomEvent) {
type = SNADAdLocationTypeAdMobBanner;
}
NSString *message = meta ? nil : kSNADOppMissReason_NoAdToShow;
[SNRunTimeConfiguration fireOpportunityForAdLocation:type zoneId:self.adSoptZoneId reason:message];
NSLog(#"******************* Opportuninty fired for refresh banner ***************************");
if (meta) {
self.meta = meta;
[self updateContentForWebAd:nil];
[self updateStatsForAd];
//fireImpression
[SNADBannerView fireImpression:self.meta];
if ([meta.adSource isEqualToString:kSNADParameter_APC]) {
self.sdkMediation = [[SdkMediation alloc] init];
[self.sdkMediation fireTrackingAdType:self.meta.type isFill:YES];
}
// Ad Height Delegate.
if ([self.meta.displayType isEqualToString:kSNADDisplayType_web]) {
self.adHeightDelegateCalled = YES;
NSInteger height = self.meta.height.integerValue;
self.bannerCH.constant = height;
if ([self.callBackDelegate respondsToSelector:#selector(adWillPresentWithHeight:adId:adType:)]) {
[self.callBackDelegate adWillPresentWithHeight:height adId:self.adId adType:SeventynineAdTypeMainStream];
}
}
} else {
[self removeBannerAdWithAdState:kADViewStateNoAdToShow];
if ([meta.adSource isEqualToString:kSNADParameter_APC]) {
[self.sdkMediation fireTrackingAdType:self.meta.type isFill:NO];
}
return;
}
} else {
// NSLog(#"View Which Is Not Visible Now: %#", self.adId);
}
SNAdConfiguration *configuration = [SNAdConfiguration sharedInstance];
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:configuration.autoRefRate target:self selector:#selector(refreshBanner:) userInfo:nil repeats:NO];
}];
}
Use GCD, and not NSOperationQueue.
Then you step away from your immediate task. You do lots and lots of complicated things inside refreshBanner. And you will do more complicated things to make it work when the user taps multiple times.
Think about what exactly you need. Abstract the "refresh automatically, and when the button is clicked, but not too often" into a class. Then you create a class that takes a dispatch_block_t as an action, where a caller can trigger a refresh anytime they want, and the class takes care of doing it not too often. Then you create an instance of the class, set all the needed refresh actions as its action block, refreshBanner just triggers a refresh, and that class takes care of the details.
You do that once. When you've done it, you actually learned stuff and are a better programmer than before, and you can reuse it everywhere in your application, and in new applications that are coming.
NSOperationQueue have cancelAllOperations method. But for the main queue it's not a good decision to use this method, cause main queue is shared between different application components. You can accidentally cancel some iOS/other library operation together with your own.
So you can create NSOperation instances and store them in an array. Then you can call cancel for all scheduled operations by iterating trough this array, and it will only affect your operations.
Note that block operations doesn't support cancellation. You will need to create your own NSOperation subclass, extract code from your execution block into that subclass main method. Also, you'll need to add [self isCancelled] checks that will abort your logic execution at some points.
I forgot to mention that currently your execution block is fully performed on the main queue. So, you'll need to move any heavy-lifting to background thread if you want to cancel your operation in the middle of processing from main thread.
I need to add that I agree with #gnasher729 - this doesn't look like an optimal solution for the problem.
I have resolved the issue.
Multiple threads created because a new view is created every time I call the API to display image. So now I am removing views if any available before displaying image, then only last object remains and refresh is called as per last called time.
Every View has it's own object that's why multiple threads has created.
By removing views my issue has been resolved.
Thanks everyone for replying.
I'm using the MBProgressHUD library in my app, but there are times that the progress hud doesn't even show when i query extensive amount of data, or show right after the processing of data is finished (by that time i don't need the hud to be displayed anymore).
In another post i found out that sometimes UI run cycles are so busy that they don't get to refresh completely, so i used a solution that partially solved my problem: Now every request rises the HUD but pretty much half the times the app crashes. Why? That's where I need some help.
I have a table view, in the delegate method didSelectRowAtIndexPath i have this code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[NSThread detachNewThreadSelector:#selector(showHUD) toTarget:self withObject:nil];
...
}
Then, I have this method:
- (void)showHUD {
#autoreleasepool {
[HUD show:YES];
}
}
At some other point I just call:
[HUD hide:YES];
And well, when it works it works, hud shows, stays and then disappear as expected, and sometimes it just crashes the application. The error: EXC_BAD_ACCESS . Why?
By the way, the HUD object is already allocated in the viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
...
// Allocating HUD
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
HUD.labelText = #"Checking";
HUD.detailsLabelText = #"Products";
HUD.dimBackground = YES;
}
You need to perform your processing on another thread, otherwise the processing is blocking MBProgressHud drawing until it completes, at which point MBProgressHud is hidden again.
NSThread is a bit too low-level for just offloading processing. I'd suggest either Grand Central Dispatch or NSOperationQueue.
http://jeffreysambells.com/2013/03/01/asynchronous-operations-in-ios-with-grand-central-dispatch
http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
/* Prepare the UI before the processing starts (i.e. show MBProgressHud) */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/* Processing here */
dispatch_async(dispatch_get_main_queue(), ^{
/* Update the UI here (i.e. hide MBProgressHud, etc..) */
});
});
This snippet will let you do any UI work on the main thread, before dispatching the processing to another thread. It then returns to the main thread once the processing is done, to allow you to update the UI.
Sorry, it's a bit wordy, but I wanted to make sure I was clear! ;-)
I have an iOS app that uses FFMPEG for streaming RTSP. I've multi-threaded FFMPEG using NSOperationQueue such that most its work, other than painting the image to the screen, of course, happens in background threads.
Works great! ...except for the fact that threads the NSOperationQueue creates never die!
I init the Queue in the class' init method with:
self->opQ = [[NSOperationQueue alloc] init];
[self->opQ setMaxConcurrentOperationCount:1];
I add methods to the Queue using blocks:
[self->opQ addOperationWithBlock:^{
[self haveConnectedSuccessfullyOperation];
}];
Or
[self->opQ addOperationWithBlock:^{
if (SOME_CONDITION) {
[self performSelectorOnMainThread:#selector(DO_SOME_CRAP) withObject:nil waitUntilDone:NO];
}
}];
Later, when I need to tear down the RTSP stream, in addition to telling FFMPEG to shut down, I call:
[self->opQ cancelAllOperations];
Which does indeed stop the threads from doing any work , but never actually destroys them. Below, you'll see a screen shot of threads that are doing nothing at all. This is what my threads look like after starting/stoping FFMPEG several times.
I seem to remember reading in Apple's documentation that NSOperations and the threads they are run on are destroyed once they are done executing, unless otherwise referenced. This doesn't appear to be the case.
Do I just need to destroy the NSOperationQueue, then re-init it when I need to start up FFMPEG again (I just realized I haven't tried this)? Anyone know how I need to kill these extra threads?
THANKS!
I solved it by creating NSBlockOperations so that I could monitor the isCancelled state, while also making the new NSBlockOperations' content more intelligent, such that I simplified the routine that would add the operations to the queue.
... Plus, I made an NSOperationQueue n00b mistake: I was adding operations to the queue on a looping basis, which fired up to 30 times per second (matching the video's frame rate). Now, however, the operation is added to the queue only once and the looping behavior is contained within the operation instead of having the loop add the operation to the queue.
Previously, I had something like this (pseudo code, since I don't have the project with me):
NSTimer *frameRateTimeout = [NSTimer scheduledTimerWithTimeInterval:1/DESIRED_FRAMES_PER_SECOND target:self selector:#selector(ADD_OPERATION_TO_QUEUE_METHOD:) userInfo:nil repeats:YES];
-(void)ADD_OPERATION_TO_QUEUE_METHOD:(NSTimer *)timer {
[opQ addOperation:displayFrame];
}
Which worked well, as the OS would correctly manage the queue, but it was not very efficient, and kept those threads alive forever.
Now, it's more like:
-(id)init {
self = [super init];
if (self) {
// alloc/init operation queue
...
// alloc/init 'displayFrame'
displayFrame = [NSBlockOperation blockOperationWithBlock:^{
while (SOME_CONDITION && ![displayFrame isCancelled]) {
if (playVideo) {
// DO STUFF
[NSThread sleepForTimeInterval:FRAME_RATE];
}
else { // teardown stream
// DO STUFF
break;
}
}
}];
}
return self;
}
- (void)Some_method_called_after_getting_video_ready_to_play {
[opQ addOperation:displayFrame];
}
Thanks, Jacob Relkin, for responding to my post.
If anyone needs further clarification, let me know, and I'll post better code once I have the project in my hands again.
I have an app that I'm accessing a remote website with NSURLConnection to run some code and then save out some XML files. I am then accessing those XML Files and parsing through them for information. The process works fine except that my User Interface isn't getting updated properly. I want to keep the user updated through my UILabel. I'm trying to update the text by using setBottomBarToUpdating:. It works the first time when I set it to "Processing Please Wait..."; however, in the connectionDidFinishLoading: it doesn't update. I'm thinking my NSURLConnection is running on a separate thread and my attempt with the dispatch_get_main_queue to update on the main thread isn't working. How can I alter my code to resolve this? Thanks! [If I need to include more information/code just let me know!]
myFile.m
NSLog(#"Refreshing...");
dispatch_sync( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self getResponse:#"http://mylocation/path/to/file.aspx"];
});
[self setBottomBarToUpdating:#"Processing Please Wait..."];
queue = dispatch_queue_create("updateQueue", DISPATCH_QUEUE_CONCURRENT);
connectionDidFinishLoading:
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Contacts..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Emails..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}
In my connectionDidFinishLoading: I would try something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^ {
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Contacts..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Emails..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}
});
Then all that file access is happening in a background queue so the main queue is not locked up. The main queue will also complete this call to connectionDidFinishLoading much more quickly, since you're throwing all the hard work onto the default queue instead, which should leave it (and the main thread) ready to accept your enqueuing of the updates to the UI which will be done by the default queue as it processes the block you just enqueued to it.
The queue handover becomes
main thread callback to connectionDidFinishLoad:
rapid handoff to default global queue releasing main thread
eventual hand off to main queue for setBottomBarToUpdating: calls
performing main queue blocks on main thread to properly update UI
eventual completion of blocks on main queue
eventual completion of blocks on default queue
You've increased concurrency (good where you've good multi-core devices) and you've taken the burden of I/O off the main thread (never a good place for it) and instead got it focused on user interface work (the right place for it).
Ideally you woud run the NSURLConnection run loop off the main thread too, but this will might be enough for you to get going.
Which run loop are you running the NSURLConnection in? If it's the main loop, you're queueing up the setBottomBarToUpdating: calls behind the work you're already doing, hence the probable reason why you're not seeing the UI update.
You could also give performSelectorOnMainThread try like so:
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
[self performSelectorOnMainThread:#selector(setBottomBarToUpdating) withObject:#"Updating Contacts..." waitUntilDone:false];
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
[self performSelectorOnMainThread:#selector(setBottomBarToUpdating) withObject:#"Updating Emails..." waitUntilDone:false];
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}
For example in a thread (because I can't wait in main loop) I have this code :
-(void) game {
for (Players player in players) {
if (player.type == IA) { // computer plays
answer = [player play];
else {
[ui showQuestion]; // user plays with the touch screen
// here waiting for the answer touch
answer = ???????????????? // return from waiting after touch callback
}
[answersArray addObject:answer];
}
answer = [self bestAnswer : answersArray];
[ui showTheBestAnswer : answer];
}
Is there a solution to wait for an UI Event in a fixed code place ?
without blocking the main loop of course.
Thank you very much for your help,
jpdms
First of all, I highly recommend that you read Apple's Concurrency Programming Guide, included with the Xcode documentation. In general, there are much better alternatives to threads, especially in the example you provide.
However:
If the game method is executing on a separate thread, then the proper way to signal the thread is using NSCondition. Create an instance and make sure both the code above and the touch handler has access to it.
NSCondition *playerDidTouchCondition = [[NSCondition alloc] init];
In the game method, you wait on the condition like this:
[ui showQuestion];
[playerDidTouchCondition lock];
[playerDidTouchCondition wait];
[playerDidTouchCondition unlock];
// do something with answer
Your game thread will sleep until the condition has been signaled. In your touch handler you would do this:
answer = whatever the user did
[playerDidTouchCondition lock];
[playerDidTouchCondition signal]; // wake up one of the sleeping threads
[playerDidTouchCondition unlock];
The example code you have above really doesn't demonstrate the need for a separate thread, however. You could very easily store a currentPlayerIndex somewhere and proceed to the next player inside of the button handler for the answer button.
Also, you MUST ensure that any UI updates are actually happening on the main thread. I hope that your lines like [ui showQuestion] are queuing calls on the main thread. In Cocoa you can do this easily with something like: [ui performSelectorOnMainThread:#selector(showQuestion)];
You really, really, really should not be using a separate thread for this.