In y iPhone App.
For search functionality. I am using UISearchBar and WebService call.
Whenever UISearchBar 'TextDidChange' happen the web service call happens.
Generally we are typing very fast, so there are many web service call happens, and I am using NSURLConnection, and I am loading table on Finished Loading.
eg,
WebService Call for M
WebService Call for Mo
WebService Call for Mor
Here, the problem is one webservice is going to finish, in between another web service called. This makes chaos.
Here, I solved the problem with writing.
**[connectionSearch cancel];**
connectionSearch=[[NSURLConnection alloc] initWithRequest:request delegate:self];
Write this code in textDidChange method for calling Webservice
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self webserviceCallMethod];
You can also call service like below after cancelPreviousPerformRequestsWithTarget:self
[self performSelector:#selector(webserviceCallMethod:) withObject:searchText afterDelay:0.3f]; // after delay can be an anything that helps.
Don't forget to remove all objects from your array, before you add objects to array again in response of your webservice. (Array that is used in cells of UITableView)
Hope it helps.
I changed the code like,
**[connectionSearch cancel];**
connectionSearch=[[NSURLConnection alloc] initWithRequest:request delegate:self];
So, whenever the same Search web service call happens, and if the call is not finished, call is cancelled and the new web service call happens.
Related
In my app, I need to make https calls to a restful web api and process the results upon return. The number of simultaneous service calls is never fixed, hence the related code has been written accordingly. The data fetched from the service is temporarily stored on an SQLite DB within the app. Following is the structure how it works.
When the user navigates to any screen or UI component thereof for which data needs to be fetched, the view controller calls a method on its designated model object. This method then checks whether the data is already present in the DB or it needs to be fetched. In case data is present, it returns the same to the view controller. Otherwise, it initiates an asynchronous service request and waits till the response comes, after which it returns the data to the VC. Therefore, the VC initialises a loading indicator before calling the specified model, and dismisses the same after control is returned from this function.
Here it is important that the function on the model waits till the response is received from the web api. This is done by registering for an NSNotification which will be issued by the service module once returned data is written to the DB. A boolean variable it set to false upon making the service request and set to true once the response is received. An NSRunLoop runs on the false condition of this boolean variable. Hence once the variable is set to true, the rest of the processing can continue.
Following are the relevant pieces of code in which all this is implemented:
[serviceModule initServiceCall:#"25" withDictionary:[NSDictionary dictionaryWithObjects:#[asOfDate] forKeys:#[#"toDate"]]];
dataReady=NO;
NSString *notificationName = #"dataReady";
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(useNotificationFromServiceModule:) name:notificationName object:nil];
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (!dataReady && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
The rest of the function continues after this.
This is the function that handles the notification:
-(void)useNotificationFromServiceModule:(NSNotification *)notification {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
dataReady=YES;
});}
The usual process is that once the notification is sent, the NSRunLoop quits and the rest of the method completes, returning to the view controller which then dismissed the loading indicator. The problem is that sometimes this does not happen. While the notification is issued (I can see the console log), the NSRunLoop does not end. The loading indicator continues to appear on the screen and stays that way until the screen is tapped once. When the screen is tapped, the NSRunLoop ends and the rest of the process continues randomly.
This does not happen always. It happens quite randomly, maybe about 4-5 times out of 10. Kindly provide some inputs/pointers to indicate why this may be happening.
If you are using the run loop directly, you are either very clever or very stupid. In the first case, you'll find the answer yourself. In the second case, it would be much much better if you followed the same pattern as everyone else does, which is running your networking code on a background thread and using dispatch_async when the results arrive.
My first question is how do I get the didAcceptConnectionWithInputStream:outputStream: callback in NSNetServiceDelegate to get called?
Follow up question: can I still establish a connection between a client and server, although I never get a callback saying that a connection was accepted (via didAcceptConnectionWithInputStream:outputStream:)?
I understand that calling publishWithOptions, while passing in the NSNetServiceListenForConnections option is supposed to result in the NetServiceDelegate callback (didAcceptConnectionWithInputStream:outputStream:) to be called. However, that callback is not getting called.
Here are the steps I am taking, to publish:
Create NSNetService with
self.netService = [[NSNetService alloc] initWithDomain:#""
type:_serviceType
name:(_name == nil) ? #"" : _name
port:0];
Schedule service in current runloop, in default mode
Set the delegate to my Server wrapper object
call publishWithOptions:NSNetServiceListenForConnections
Here are the steps I take, to browse services:
Create an NSNetServiceBrowser, and set its delegate to my client wrapper object
Call searchForServicesOfType for the same service type and domain as NSNetService
List services in a UITableView for the UI, to allow a user to select a service
When a user selects a service, set the service's delegate to my client object, and call getInputStream:outputSteam: on the service
After getInputStream:outputSteam: returns success, I would expect didAcceptConnectionWithInputStream:outputStream: to get called. However this does not occur.
Thanks for your help!
The problem is that didAcceptConnectionWithInputStream:outputStream: must be called from the side accepting the connection.
Once the service is available, you call get the streams
[service getInputStream:&istream outputStream:&ostream]
Once this happens on the side receiving the request the delegate method
- (void)netService:(NSNetService *)sender didAcceptConnectionWithInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream
will be called
In my experience, it is not the act of calling getInputStream:outputStream: on the client that causes didAcceptConnectionWithInputStream:outputStream: to be called on the server.
On the client, after calling getInputStream:outputStream:, your client then needs to call [inputStream open] and [outputStream open] before the didAcceptConnectionWithInputStream:outputStream: will be called.
It's all a part of lazy initialization.
Calling getInputStream:outputStream: will give you back two perfectly good NSStreams ready to use. So, say, you want to write some data? First, open the write stream...
BAM! netService:didAcceptConnectionWithInputStream:outputStream: is called.
In the new iOS 7 SDK, you can perform background fetches, by implementing performFetchWithCompletionHandler (http://bit.ly/15ofOx8).
They say you are supposed to execute completionHandler when you are done. When exactly is that?
My performFetch looks like this:
NSString *result = [super.viewController.webView stringByEvaluatingJavaScriptFromString:#"loadFeeds();"];
loadFeeds is a JavaScript function in a WebView, that makes an AJAX call. Which means it's probably going to go to another thread and loose the completionHandler. How can I execute completionHandler when the Ajax call is done?
I thought about using UIWebView's shouldStartLoadWithRequest to catch a custom URL, but by then I have completely lost completionHandler.
I've subclassed NSURLProtocoland registered it in didFinishLaunchingWithOptions with this code:
+ (void) registerProtocol
{
static BOOL registered = NO;
if (!registered)
{
[NSURLProtocol registerClass:[MyURLProtocol class]];
registered = YES;
}
}
For the first UIWebView in my app (in the mainwindow) the method canInitWithRequest is triggered, and I can execute my custom code.
However I have a second UIWebView, that is inside an UIViewController which is pushed at some point in the app (presented modally). The canInitWithRequest will NOT be called for the second UIWebView, thus I cannot execute my custom code. This is even true when the protocol is registered after both instances of UIWebView are created.
Anyone knows why?
[edit]
d'oh! i just found it was just a plain error in the javascript that is loaded in the second webview :(
works like a charm in both webviews now!
Not sure if this is related to your situation and solution, but posting for the record. Discovered today after much trial and tribulation that an ajax call sending a message to objective c which is to be intercepted by a subclass of NSURLProtocol should have cache set to false if you expect to be sending the same url more than once. Like so:
$.ajax({
url:"atavistcommand://" + name,
data:JSON.stringify(data),
type:"GET",
processData:false,
cache:false )};
If cache = true then objective c will never receive subsequent requests on the same url.
How can I make my program wait for an asynchronous NSURLConnection to finish before going to the next line of code?
SetDelegate *sjd= [SetDelegate alloc];
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:post delegate:sjd];
[connection start];
This is how I start the connection and I handle the data received in the delegate but I want to wait for the connection to end before proceeding mainly because this is in a for loop and it has to run for each element in my database.
I need to put data from the phones database to a remote database and after the data was successfully put in the data in the phones database is deleted. I am going through each element in the phone's database and start a connection that's why I don't see how the next stuff can be done from the loop. I'm a beginner when it comes to objective-c programming so I'm not sure if this is the right way or not to do it
Making the call synchronous is not an option because it blocks the program and i have a progress bar that should show.
Your question is a bit odd. You have impossibly constrained the issue. You cannot have a line of code "wait" for a process to finish w/o it blocking something, in this case whatever thread the loop is running in.
You can use a synchronous call if you wanted to, it doesn't block your app, it only blocks the thread it is executed on. In your example, you have a loop that is continually getting remote data and you want your UI to reflect that until it is done. But you don't want your UI blocked. That means, this thread with your loop already MUST be on a background thread so you can feel free to do a synchronous call in the loop w/o blocking your UI thread. If the loop is on the UI thread you need to change this to do what you want.
You could also do this using an asynchronous connection. In that case, your operation may actual complete faster b/c you can have multiple requests in progress at the same time. If you do it that way, your loop can remain on the UI thread and you just need to track all of the connections so that when they are finished you can communicate that status to the relevant controllers. You'll need iVars to track the loading state and use either a protocol or NSNotification to communicate when loading is done.
EDIT: ADDED EXAMPLE OF SYNCHRONOUS CALL ON BACKGROUND THREAD
If you want the loop to finish completely only when all requests are finishes and not block your UI thread here's a simple example:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// post an NSNotification that loading has started
for (x = 0; x < numberOfRequests; x++) {
// create the NSURLRequest for this loop iteration
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
// do something with the data, response, error, etc
}
// post an NSNotification that loading is finished
});
Whatever objects need to show loading status should observe and handle the notifications you post here. The loop will churn through and make all your requests synchronously on a background thread and your UI thread will be unblocked and responsive. This isn't the only way to do this, and in fact, I would do it using async connections myself, but this is a simple example of how to get what you want.
If you just want to know when it's complete and don't really care about any data, simply use the NSNotificationCenter to post a notification and have your view subscribe to it.
Delegate - Post Notification upon completion
-(void) connectionDidFinishLoading:(NSURLConnection*)connection {
[[NSNotificationCenter defaultCenter] postNotificationName:#"NSURLConnectionDidFinish" object:nil];
}
View - Add observer and run some code when observed
-(void) viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(yourCleanupMethod)
name:#"NSURLConnectionDidFinish"
object:nil];
}
-(void) yourCleanupMethod {
// finish up
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Now, if you need to pass a simple object back as data you can try loading up the object parameter in your notification like this:
[[NSNotificationCenter defaultCenter] postNotificationName:#"NSURLConnectionDidFinish" object:yourDataObject];
Then change your view and cleanup signature like this:
-(void) viewDidLoad {
// Notice the addition to yourCleanupMethod
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(yourCleanupMethod:)
name:#"NSURLConnectionDidFinish"
object:nil];
}
-(void) yourCleanupMethod:(NSNotification *)notif {
// finish up
id yourDataObject = [notif object];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Now I found myself needing something a little more than this so I ended up creating a singleton to handle all of my requests. Since all of your delegate methods in NSURLConnectionDelegate give you and instance of the NSURLConnection for the specific connection, you can simply store a mutable data object in a dictionary and look it up each time by the connection. From there I have a method signature that takes and object and selector in that I associate with the connection so after everything has wrapped up, I can pass that mutable data object to the requestor by performing the selector on that object.
I won't include all of that code here but maybe that will help get you thinking about what is possible. I found that I had a lot of code tied up in making web service calls so wrapping everything up in a singleton gave me a nice clean way of getting data. Hope this helps!
If you really want it to wait, why use an asynchronous call at all? Use a synchronous call instead:
NSURLResponse* response = nil;
NSData* data = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:nil]
This approach will block the thread it's executed, so you should be sure you want to do it! Did I mention that it will block? :)
You say you want to wait for an asynchronous call to complete, so I'm assuming you're calling the code you posted up in a separate thread.
I would recommend having a look at the new sendAsynchronourRequest method. I've posted up an example of how you can wrap this up in a class which would inform its delegate when the connection has completed / timed out / failed. I'm only referring you to this post because it sounds like you're trying to achieve something very similar to what I was at the time, and this DownloadWrapper class worked flawlessly for me. It's new in iOS5, mind you.
This golden nugget helped me!
I was using synchronous NSURL just fine until I decided I needed SSL for my connection between my client and my server. I took the approach of key pinning which is comparing the cert on the device to the cert on the server (read more on link above) and in order for it to work I needed to add code to the NSURL methods, which from my research you can't do with NSURL synchronous.
Until I found this ridiculously simple solution which worked for me:
NSString *connectionRunLoopMode = #"connectionRunLoopMode";
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:urlRequest delegate:urlConnectionDelegate startImmediately:NO];
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
[connection unscheduleFromRunLoop:currentRunLoop forMode:NSDefaultRunLoopMode];
[connection scheduleInRunLoop:currentRunLoop forMode:connectionRunLoopMode];
[connection start];
while ([currentRunLoop runMode:connectionRunLoopMode beforeDate:[NSDate distantFuture]]);
NSURLConnection is already asynchronous. Simply implement the delegate methods. If you want to update the UI on the MainThread (e.g. a progress bar), you can do so in didReceiveData.
or look at this