How to pause/stop function until SOAP request is complete - iphone

EDIT: The problem i am having is that i am using the TapKu calendar, so i am relying on the provided delegates. Here is the problm:
- (NSArray*) calendarMonthView:(TKCalendarMonthView*)monthView marksFromDate:(NSDate*)startDate toDate:(NSDate*)lastDate{
//SOAP Request has NSURLConnection which runs asychonrous delegate methods.
//SOAP Request will return before the data array has been populated
[self SOAPRequest:startDate];
//i need to do something like this, however the SOAPRequest will return and get stuck into the loop instead of the asychronos delegates firing
//therefore i can never set bGettingData to NO.
int iWait;
while (bGettingData) {
iWait++;
}
return dataArray;
}
Hello,
In the app i am creating, i rely on SOAP requests to retrieve data, parse the XML and populate an array.
The problem i have, is that when i check the array it is empty, because the SOAP request has not completed. How do i stop my code from executing until the SOAP request is complete and resume the code? Can this be done through a callback or threading?
Thanks

Don't temporarily stop, sleep or wait, instead simply exit/quit/return from the current routine/function/method.
Break your current "stuff" into multiple fragments of code, each fragment in its own method.
Use subsequent method(s) to do whatever comes next, and have that method called by the completion routine of your async network/SOAP request.
Basically, your problem is that you are still thinking in terms of procedural coding. The proper paradigm is to use event driven coding: have the OS call your code, rather than having your code call the OS and waiting.

Yes,
start here: http://ondotnet.com/pub/a/dotnet/2005/08/01/async_webservices.html

You want indeed to wait for the answer to be complete - a callback is usually easiest. Exactly how depends on the programming library/language you are using (is above in javascript, objectiveC, did you hand code or start with an example).
Check out the answers to Iphone SOAP request step by step tutorial - such as http://macresearch.org/interacting-soap-based-web-services-cocoa-part-1 and http://brismith66.blogspot.com/2010/05/iphone-development-accesing-soap.html. Or follow https://developer.omniture.com/node/321 - which simply waits until the answer has fully arrived.

Unfortunately when using the TapKu calendar, you can not asynchronously load from a database via SOAP. You must synchronously load the calendar, because their is not way to refresh the calendar view once the data has finished loading. If you have 40+ records per month, this will create a huge 5-6 second delay.

This is indeed possible, as an example for the day calendar view, modify _refreshDataPageWithAtIndex to be like this:
- (void) _refreshDataWithPageAtIndex:(NSInteger)index{
UIScrollView *sv = self.pages[index];
TKTimelineView *timeline = [self _timelineAtIndex:index];
CGRect r = CGRectInset(self.horizontalScrollView.bounds, HORIZONTAL_PAD, 0);
r.origin.x = self.horizontalScrollView.frame.size.width * index + HORIZONTAL_PAD;
sv.frame = r;
timeline.startY = VERTICAL_INSET;
for (UIView* view in sv.subviews) {
if ([view isKindOfClass:[TKCalendarDayEventView class]]){
[self.eventGraveYard addObject:view];
[view removeFromSuperview];
}
}
if(self.nowLineView.superview == sv) [self.nowLineView removeFromSuperview];
if([timeline.date isTodayWithTimeZone:self.timeZone]){
NSDate *date = [NSDate date];
NSDateComponents *comp = [date dateComponentsWithTimeZone:self.timeZone];
NSInteger hourStart = comp.hour;
CGFloat hourStartPosition = hourStart * VERTICAL_DIFF + VERTICAL_INSET;
NSInteger minuteStart = round(comp.minute / 5.0) * 5;
CGFloat minuteStartPosition = roundf((CGFloat)minuteStart / 60.0f * VERTICAL_DIFF);
CGRect eventFrame = CGRectMake(self.nowLineView.frame.origin.x, hourStartPosition + minuteStartPosition - 5, NOB_SIZE + self.frame.size.width - LEFT_INSET, NOB_SIZE);
self.nowLineView.frame = eventFrame;
[sv addSubview:self.nowLineView];
}
if(!self.dataSource) return;
timeline.events = [NSMutableArray new];
[self.dataSource calendarDayTimelineView:self eventsForDate:timeline.date andEvents:timeline.events success:^{
[timeline.events sortUsingComparator:^NSComparisonResult(TKCalendarDayEventView *obj1, TKCalendarDayEventView *obj2){
return [obj1.startDate compare:obj2.startDate];
}];
[self _realignEventsAtIndex:index];
if(self.nowLineView.superview == sv)
[sv bringSubviewToFront:self.nowLineView];
}];
}
and then change your eventsForDate function to look like this:
- (void) calendarDayTimelineView:(TKCalendarDayView*)calendarDayTimeline eventsForDate:(NSDate *)eventDate andEvents:(NSMutableArray *)events success:(void (^)())success {
[Model doSomethingAsync andSuccess:^(NSArray *classes) {
// .. Add stuff to events..
success();
}];
}
I'm assuming the pattern for the other controls is very similar. The premise is you're waiting to continue the formatting/layout flow til you get your data.

Related

What is preferred: implement method with GCD inside and then just simple call, or implement method and then call it later with GCD?

what's is more prefered way to write multi threaded apps. I see two ways.
Implement method with GCD inside and then just simple call (myMethodA), or just implement method and then call it with GCD? Thanks in advance.
My point:
ClassA / method implementation
- (void)myMethodA
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// doSomething1
// doSomething2
});
}
- (void)myMethodB
{
// doSomething1
// doSomething2
}
ClassB / method call
{
[myClassA methodA];
// or
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[myClassA methodB];
};
}
IMHO, neither.
The preferred way should be having an object which knowns where to execute its actions:
completion_block_t completionHandler = ^(id result) { ... };
AsyncOperation* op = [AsyncOperation alloc] initWithCompletion:completionHandler];
[op start]; // executes its actions on a private execution context
Then, one can wrap those AsyncOperation objects into a convenient method:
- (void) fetchUsersWithCompletion:(completion_block_t)completionHandler
{
NSDictionary* params = ...;
self.currentOperation = [[HTTPOperation alloc] initWithParams:params
completion:completionHandler];
[self.currentOperation start];
}
The client may only be interested in specifying where its completionHandler should be executed. The API may be enhanced as follows:
- (void) fetchUsersWithQueue:(NSOperationQueue*)handlerQueue
withCompletion:(completion_block_t)completionHandler
{
NSDictionary* params = ...;
self.currentOperation = [[HTTPOperation alloc] initWithParams:params
completion:^(id result){
// As per the documentation of HTTPOperation, the handler will be executed
// on an _unspecified_ execution context.
// Ensure to execute the client's handler on the specified operation queue:
[handlerQueue:addOperationWithBlock:^{
completionHandler(result);
}];
}];
[self.currentOperation start];
}
The latter API can be used as this:
[self fetchUsersWithQueue:[NSOperation mainQueue] completion:^(id result){
self.users = result;
[self.tableView reloadData];
}];
Personal preference. Choose whichever makes the code more readable / understandable / obvious. Also, consideration of whether the code should be possible to run on the 'current' thread or whether it should always be run on a background thread. You need to design your threading configuration, describe it and then implement with that in mind. If you're calling methods between classes like in your example then I'd generally say that any threading should be handled inside that class, not inside the calling class. But that's about distribution of knowledge.
It doesn't make much of a difference - it just depends on what you want to do.
If you want to execute the method on different queues each time, then the myMethodB system is more appropriate. If, however, you always want to run the method on the same queue, then myMethodA will save you time writing code (you only have to write the GCD code once).

How to call a function in applicationDidEnterBackground?

I want to call a function in applicationDidEnterBackground this function is defined in other controller.
I have made an object to access it but it seems that function is getting killed when called.
Here is the function it basically calculates the distance and postes a notification
-(void)calculateDistance
{
for (NSMutableDictionary *obj in placeName) {
CLLocation *userLocation = [[AppHelper appDelegate] mLatestLocation];
CLLocation *annotation1 = [[CLLocation alloc] initWithLatitude:[[obj objectForKey:#"Lat"]doubleValue] longitude:[[obj objectForKey:#"long"]doubleValue]];
CGFloat distanceTemp = [annotation1 getDistanceFrom:userLocation];
[obj setObject:[NSNumber numberWithFloat:distanceTemp] forKey:#"distance"];
[annotation1 release];
}
if ([placeName count])
{
NSArray *sortedArray=[placeName sortedArrayUsingFunction:intSort context:NULL];
self.placeName = [NSMutableArray arrayWithArray:sortedArray];
NSMutableArray *arrayTemp = [[NSMutableArray alloc] initWithArray:placeName];
for (int i =0; i < [placeName count]; i++)
{
// NSArray *sortedArray=[placeName sortedArrayUsingFunction:intSort context:NULL];
NSMutableArray *tempArray = [sortedArray objectAtIndex:i];
//DLog(#"sortedArray%#", sortedArray);8=
NSNumber *DistanceNum = [tempArray objectForKey:#"distance"];
NSLog(#"distance%#:::",DistanceNum);
NSInteger intDistance = (int)[DistanceNum floatValue];
if(intDistance<500)
{
NSLog(#"ho gaya bhai");
NSString *notifications =#"Yes";
[[AppHelper mDataManager] setObject:notifications forKey:#"notifications"];
NSLog(#"notifications:%#",notifications);
RemindMeViewController *object = [[RemindMeViewController alloc] initWithNibName:#"RemindMeViewController" bundle:nil];
// RemindMeViewController *object=[[RemindMeViewController alloc]initWithNibName];
NSLog(#"notifications set");
[object scheduleNotification];
}
else
{
// [arrayTemp removeObjectAtIndex:i];
}
}
//after for loop is ended
self.placeName= arrayTemp;
DLog(#"remaining",arrayTemp);
[arrayTemp release];
[mTableView reloadData];
}
}
How long is your function taking to complete? You only have 5 seconds to perform tasks in applicationDidEnterBackground: and return.
From Apple's UIApplicationDelegate Protocol Reference:
Your implementation of this method has approximately five seconds to
perform any tasks and return. If you need additional time to perform
any final tasks, you can request additional execution time from the
system by calling beginBackgroundTaskWithExpirationHandler:. In
practice, you should return from applicationDidEnterBackground: as
quickly as possible. If the method does not return before time runs
out your application is terminated and purged from memory.
You should perform any tasks relating to adjusting your user interface
before this method exits but other tasks (such as saving state) should
be moved to a concurrent dispatch queue or secondary thread as needed.
Because it's likely any background tasks you start in
applicationDidEnterBackground: will not run until after that method
exits, you should request additional background execution time before
starting those tasks. In other words, first call
beginBackgroundTaskWithExpirationHandler: and then run the task on a
dispatch queue or secondary thread.
As far as I know you should not call any time consuming functions in applicationDidEnterBackground since the app will get suspended after a short amount of time.
From Apple's IOS Programming Guide
Most applications that enter the background state are moved to the suspended state shortly thereafter. While in this state, the application does not execute any code and may be removed from memory at any time. Applications that provide specific services to the user can request background execution time in order to provide those services.
Gool luck :)
have you tried this, for example using a NSThread or make some logic to call this method
- (void)applicationDidEnterBackground:(UIApplication *)application {
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, called instead of applicationWillTerminate: when the user quits. */}
// inside this method try to call the calculate position method may be it will works(try nsthread here)

Delegate functions not being called

Long time lurker, first time poster.
I'm making a ServerConnection module to make it a whole lot modular and easier but am having trouble getting the delegate called. I've seen a few more questions like this but none of the answers fixed my problem.
ServerConnection is set up as a protocol. So a ServerConnection object is created in Login.m which makes the call to the server and then add delegate methods in Login to handle if there's an error or if it's done, these are called by ServerConnection like below.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if( [self.delegate respondsToSelector:#selector(connectionDidFinish:)]) {
NSLog(#"DOES RESPOND");
[self.delegate connectionDidFinish:self];
} else {
NSLog(#"DOES NOT RESPOND");
}
self.connection = nil;
self.receivedData = nil;
}
It always "does not respond". I've tried the CFRunLoop trick (below) but it still doesn't work.
- (IBAction)processLogin:(id)sender {
// Hide the keyboard
[sender resignFirstResponder];
// Start new thread
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Acutally call the server
[self authenticate];
// Prevent the thread from exploding before we've got the data
CFRunLoopRun();
// End thread
[pool release];
}
I copied the Apple URLCache demo pretty heavily and have compared them both many times but can't find any discrepancies.
Any help would be greatly appreciated.
Here are the questions to ask:
Does your delegate respond to connectionDidFinishLoading:?
Does the signature match, i.e. it takes another object?
Is the delegate set at all or is it nil? (Check this in that very method)
If any of those are "NO", you will see "doesn't respond"... and all equally likely to happen in your application, but all are easy to figure out.

NSOperations or NSThread for bursts of smaller tasks that continuously cancel each other?

I would like to see if I can make a "search as you type" implementation, against a web service, that is optimized enough for it to run on an iPhone.
The idea is that the user starts typing a word; "Foo", after each new letter I wait XXX ms. to see if they type another letter, if they don't, I call the web service using the word as a parameter.
The web service call and the subsequent parsing of the result I would like to move to a different thread.
I have written a simple SearchWebService class, it has only one public method:
- (void) searchFor:(NSString*) str;
This method tests if a search is already in progress (the user has had a XXX ms. delay in their typing) and subsequently stops that search and starts a new one. When a result is ready a delegate method is called:
- (NSArray*) resultsReady;
I can't figure out how to get this functionality 'threaded'.
If I keep spawning new threads each time a user has a XXX ms. delay in the typing I end up in a bad spot with many threads, especially because I don't need any other search, but the last one.
Instead of spawning threads continuously, I have tried keeping one thread running in the background all the time by:
- (void) keepRunning {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
SearchWebService *searchObj = [[SearchWebService alloc] init];
[[NSRunLoop currentRunLoop] run]; //keeps it alive
[searchObj release];
[pool release];
}
But I can't figure out how to access the "searchFor" method in the "searchObj" object, so the above code works and keeps running. I just can't message the searchObj or retrieve the resultReady objects?
Hope someone could point me in the right direction, threading is giving me grief:)
Thank you.
Ok, I spend the last 8 hours reading up on every example out there.
I came to realize that I would have to do some "Proof of Concept" code to see if there even would be a speed problem with building a new thread for "each" keystroke.
It turns out that using NSOperation and NSOperationQueue is more than adequate, both in terms of speed and especially in terms of simplicity and abstraction.
Is called after each keystroke:
- (void) searchFieldChanged:(UITextField*) textField {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
NSString *searchString = textField.text;
if ([searchString length] > 0) {
[self performSelector:#selector(doSearch:) withObject:textField.text afterDelay:0.8f];
}
}
This is mainly to stop the code form initiating a search for keystrokes that are less than 800 ms. apart.
(I would have that a lot lower if it where not for the small touch keyboard).
If it is allowed to time out, it is time to search.
- (void) doSearch:(NSString*) searchString {
[queue cancelAllOperations];
ISSearchOperation *searchOperation = [[ISSearchOperation alloc] initWithSearchTerm:searchString];
[queue addOperation:searchOperation];
[searchOperation release];
}
Cancel all operations that is currently in the queue. This is called every time a new search is
started, it makes sure that the search operation already in progress gets closed down in an orderly fashion, it also makes sure that only 1 thread is ever in a "not-cancelled" state.
The implementation for the ISSearchOperation is really simple:
#implementation ISSearchOperation
- (void) dealloc {
[searchTerm release];
[JSONresult release];
[parsedResult release];
[super dealloc];
}
- (id) initWithSearchTerm:(NSString*) searchString {
if (self = [super init]) {
[self setSearchTerm:searchString];
}
return self;
}
- (void) main {
if ([self isCancelled]) return;
[self setJSONresult:/*do webservice call synchronously*/];
if ([self isCancelled]) return;
[self setParsedResult:/*parse JSON result*/];
if ([self isCancelled]) return;
[self performSelectorOnMainThread:#selector(searchDataReady:) withObject:self.parsedResult waitUntilDone:YES];
}
#end
There are two major steps, the downloading of the data from the web service and the parsing.
After each I check to see if the search has been canceled by [NSOperationQueue cancelAllOperations] if it has, then we return and the object is nicely cleaned up in the dealloc method.
I will probably have to build in some sort of time out for both the web service and the parsing, to prevent the queue from choking on a KIA object.
But for now this is actually lightning fast, in my test I am searching an 16.000 entries dictionary and having Xcode NSLog it to the screen (slows things down nicely), each 800 ms. I issue a new search string via a timer and thereby canceling the old before it has finished its NSLog results to screen loop.
NSOperationQueue handles this with no glitches and never more that a few ms. of two threads being executed. The UI is completely unaffected by the above tasks running in the background.

connectionDidFinishLoading - how to force update UIView?

I am able to download a ZIP file from the internet. Post processing is done in connectionDidFinishLoading and works OK except no UIView elements are updated. For example, I set statusUpdate.text = #"Uncompressing file" but that change does not appear until after connectionDidFinishLoading has completed. Similarly, the UIProgressView and UIActivityIndicatorView objects are not updated until this method ends.
Is there any way to force an update of the UIView from within this method? I tried setting [self.view setNeedsDisplay] but that didn't work. It appears to be running in the main thread. All other commands here work just fine - the only problem is updating the UI.
Thanks!
Update: here is the code that is NOT updating the UIVIEW:
-(void)viewWillAppear:(BOOL)animated {
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(processUpdate:) userInfo:nil repeats:YES];
downloadComplete = NO;
statusText.text = #"";
}
-(void)processUpdate:(NSTimer *)theTimer {
if (! downloadComplete) {
return;
}
[timer invalidate];
statusText.text = #"Processing update file.";
progress.progress = 0.0;
totalFiles = [newFiles count];
for (id fileName in newFiles) {
count++;
progress.progress = (float)count / (float)totalFiles;
// ... process code goes here ...
}
}
At then end of processUpdate, I set downloadComplete = YES. This builds & runs without errors and works as intended except nothing updates in the UIVIEW until after processUpdate completes, then everything updates at once.
Thanks for your help so far!
As Niels said, you must return control to the run loop if you want to see views update. But don't start detaching new threads unless you really need to. I recommend this approach:
- (void)connectionDidFinishLoading:(NSConnection *)connection {
statusUpdate.text = #"Uncompressing file";
[self performSelector:#selector(doUncompress) withObject:nil afterDelay:0];
}
- (void)doUncompress {
// Do work in 100 ms chunks
BOOL isFinished = NO;
NSDate *breakTime = [NSDate dateWithTimeIntervalSinceNow:100];
while (!isFinished && [breakTime timeIntervalSinceNow] > 0) {
// do some work
}
if (! isFinished) {
statusUpdate.text = // here you could update with % complete
// better yet, update a progress bar
[self performSelector:#selector(doUncompress) withObject:nil afterDelay:0];
} else {
statusUpdate.text = #"Done!";
// clean up
}
}
The basic idea is that you do work in small chunks. You return from your method to allow the run loop to execute periodically. The calls to performSelector: will ensure that control eventually comes back to your object.
Note that a risk of doing this is that a user could press a button or interact with the UI in some way that you might not expect. It may be helpful to call UIApplication's beginIgnoringInteractionEvents to ignore input while you're working... unless you want to be really nice and offer a cancel button that sets a flag that you check in your doUncompress method...
You could also try running the run loop yourself, calling [[NSRunLoop currentRunLoop] runUntilDate:...] every so often, but I've never tried that in my own code.
While you are in connectionDidFinishLoading nothing else happens in the application run loop. Control needs to be passed back to the run loop so it can orchestrate the UI updating.
Just flag the data transfer as complete and the views for updating. Defer any heavy processing of the downloaded data to it's own thread.
The application will call your views back letting them refresh their contents later in the run loop. Implement drawRect on your own custom views as appropriate.
If you're receiving connectionDidFinishLoading in the main thread, you're out of luck. Unless you return from this method, nothing will be refreshed in the UI.
On the other hand, if you run the connection in a separate thread, then you can safely update the UI using the following code:
UIProgressView *prog = ... <your progress view reference> ...
[prog performSelectorOnMainThread:#selector(setProgress:)
withObject:[NSNumber numberWithFloat:0.5f]
waitUntilDone:NO];
Be careful not to update the UI from a non-main thread - always use the performSelectorOnMainThread method!
Do exactly what you're doing with the timer, just dispatch your processing code to a new thread with ConnectionDidFinish:. Timers can update the UI since they're run from the main thread.
The problem turned out to that the UI isn't updated in a for() loop. See the answer in this thread for a simple solution!