I know that the pattern in iPhone is to use ASync connection calls (using the informal protocols that is implemented by the current class).
In my case, I've created a utility class to do the networking staff and then return the data to the ViewController.
I find is inadequate to implement the connection model as Async in a utility class because r I will write a block of code in the ViewControlle such following: (which IMHO is bad)
MyUtilityConnection* utilConn = ....
while (true)
{
if ([utilConn checkUnderlyingAsyncConnectionFinishedLoading]) break;
}
NSData* dataFromUrl = [utilConn dataFromUnderlayingConn];
So, the question is, Does using Sync connection model in iPhone could causes problem? and solutions?
(What about the drawing will stril hanging until the data come???)
AVOID by all means to do synchronous connections! This will obviously freeze your UI (and it gets worse if you don't have a good bandwidth of course).
What you could do is to use the blocks syntax to write more readable code when you need to download data. Create a class that implements the NSURLConnection delegate methods, and then call the block when the data is done.
See my OHURLLoader class on github for example that does exactly that (and that's only one solution).
Usage example:
NSURL* url = ...
NSURLRequest* req = [NSURLRequest requestWithURL:url];
OHURLLoader* loader = [OHURLLoader URLLoaderWithRequest:req];
[loader startRequestWithCompletion:^(NSData* receivedData, NSInteger httpStatusCode) {
NSLog(#"Download of %# done (statusCode:%d)",url,statusCode);
outputTextView.text = loader.receivedString;
} errorHandler:^(NSError *error) {
NSLog(#"Error while downloading %#: %#",url,error);
outputTextView.text = [error localizedDescription];
}];
During sync methods (sendSynchronousRequest:returningResponse:error:) the UI is non-responsive (assuming that the sync method is called on the main thread).
But they are fine on background threads, the easiest way to accomplish sync calls on a background thread is with GCD.
Related
I have a toolkit that I need to work with (to interface with a remote service). This toolkit queries the remote service and asks for results. It does this asynchronously, which in most cases is good, but not for creating concise methods. I want to make methods similar to the following:
-(NSArray *)getAllAccounts {
NSString *query = #"SELECT name FROM Account";
//Sets "result" to the query response if no errors.
//queryResult:error:context: is called when the data is received
[myToolkit query:query target:self selector:#selector(queryResult:error:context:) context:nil];
//Wait?
return result.records;
}
The problem is, inside the toolkit the methods call each other using #selector, not direct calls, so getting return values is difficult. Further, the actual query uses:
NSURLConnection *connection = [[[NSURLConnection alloc] initWithRequest:aRequest delegate:self] autorelease];
Which is asynchronous. By the time the data has been received from the service, my method has long ago returned... without the information. So my question is this: Is there a way to pause execution until the data has been returned? Could I accomplish this using a second thread to get the data while the main thread rests (or using 3 threads so the main thread doesn't rest?)
I don't want to edit the toolkit to change their method (or add a new one) to be synchronous, so is there a way to make a method as I want?
You might want to consider NOT making it all synchronous, especially if the sample code in your post is run on your main application thread. If you do that, the main thread will block the UI and the application will cease to respond until the remote transaction is complete.
Therefore, if you really insist on the synchronous approach, then you should definitely do it in a background thread so that the UI does not become unresponsive, which can actually lead to your App getting killed by the OS on iphone.
To do the work in a background thread, I would strongly recommend using the Grand Central Dispatch stuff, namely NSBlockOperation. It will free you from having to actually create and manage threads and makes your code pretty neat.
To do the synchronous thing, take a look at the NSCondition class documentation. You could do something like the following:
NSCondition* condition = ...;
bool finished = NO;
-(NSArray *)getAllAccounts {
[condition lock];
NSString *query = #"SELECT name FROM Account";
//Sets "result" to the query response if no errors.
//queryResult:error:context: is called when the data is received
[myToolkit query:query target:self selector:#selector(queryResult:error:context:) context:nil];
while (!finished)
[condition wait];
[condition unlock];
return result.records;
}
Then in the method called by the toolkit to provide the results you'd do:
- (void) queryResult:error:context: {
// Deal with results
[condition lock]
finished = YES;
[condition signal];
[condition unlock];
}
You'd probably want to encapsulate the "condition" and "finished" variables in your class declaration.
Hope this helps.
UPDATE: Here is some code to offload the work to a background thread:
NSOperationQueue* queue = [NSOperationQueue new];
[queue addOperationWithBlock:^{
// Invoke getAllAccounts method
}];
Of course, you can keep the queue around for later use and move the actual queuing of the work to inside your method call to make things neater.
The way to wait is to return from your current code. Finish up doing what you want done after the wait, in the asynchronous callback method you specify. What's so difficult about that?
Any synchronous waits in the main UI thread will block the UI and make the user think your app has locked up, which is likely far worse than your thinking the code isn't concise enough.
I have a library project that uses ASIHTTPRequest to make URL requests and parse the responses. The library will be used by a separate iPhone app project.
If my iPhone controller code responds to a touch event, then calls into the library to make URL requests, how do I best perform the requests asynchronously?
In the library, if I use the delegate pattern for asynchronous requests as shown in the ASIHTTPRequest sample code, how do I return data from the library back to the calling code in the iPhone controller?
If I instead make synchronous URL requests with ASIHTTPRequest inside the library, what's the easiest way to put the calls to the library from the iPhone controller on a separate thread to avoid tying up the UI thread?
I'm no ASIHTTPRequest expert (NSURLRequest has always done me fine), but from a quick poke at the code, it looks like you'd use its delegate and didFinishSelector properties to give it someone to tell when the URL request is finished. So, for example:
- (void)startURLRequest
{
ASIHTTPRequest *myRequest;
/* code to set the request up with your target URL, etc here */
myRequest.delegate = self;
myRequest.didFinishSelector = #selector(HTTPRequestDidFinish:);
/* ... */
[myRequest startAsynchronous];
}
- (void)HTTPRequestDidFinish:(ASIHTTPRequest *)request
{
NSLog(#"Request %# did finish, got data: %#", request, request.data);
[myTargetForData didReceiveData:request.data fromURL:request.originalURL];
}
Apple explicitly recommend that you use the built-in runloop style mechanisms for asynchronous HTTP fetching, not separate threads. Using separate threads is likely to result in worse performance — at least in terms of battery life and/or device heat, even if it's still fast enough.
That said, as a learning point, by far the quickest way to switch something onto a separate thread and have it report back to the main thread (remember: UIKit objects may be messaged only from the main thread) is by changing this:
- (void)postResult:(NSString *)result
{
instanceOfUILabel.text = result;
}
- (void)doExpensiveOperationOn:(NSString *)source
{
/* lots of expensive processing here, and then... */
[self postResult:result];
}
- (IBAction)userWantsOperationDone:(id)sender
{
[self doExpensiveOperationOn:#"some value or another"];
}
Into this:
- (void)postResult:(NSString *)result
{
instanceOfUILabel.text = result;
}
- (void)doExpensiveOperationOn:(NSString *)source
{
/* we're on a thread without an autorelease pool now, probably we'll want one */
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
/* lots of expensive processing here, and then... */
/* in this simplified example, we assume that ownership of 'result' is here on this thread, possibly on the autorelease pool, so wait until postResult has definitely finished before doing anything that might release result */
[self performSelectorOnMainThread:#selector(postResult:) withObject:result waitUntilDone:YES];
[pool release];
}
- (IBAction)userWantsOperationDone:(id)sender
{
[self performSelectorOnBackgroundThread:#selector(doExpensiveOperationOn:) withObject:#"some value or another"];
}
There's about a million possible concurrency errors you can make by just going threaded without thinking about it though, and in that example an obvious problem is that whatever triggered the IBAction can [probably] trigger it several more times before doExpensiveOperationOn has finished. Multithreading is not something to be dashed into lightly.
For anyone's future reference, the easiest approach I found is to use the async request functionality built into ASIHTTPRequest, setting my library object as the delegate and setting the didFinishSelector: and didFailSelector: values to different methods inside my library for each request.
At the end of processing each response, I assign the parsed response (an NSString* or NSArray*) to a property of my library object instead of returning a value.
When my iOS view controller delegate is loaded, I add a change observer to each of the properties in the library using Key-Value Observing. When the response is parsed and assigned to the property in the library, the observeValueForKeyPath:ofObject:change:context: method is called in the code of my view controller delegate, and from there I can figure out which property was changed and therefore what UI needs to be updated.
How can I create a new file containing just it's own delegate so that I can make an ASIHTTPRequest with its own asynchronous ending, and something easy enough where I just need to set [request setDelegate:self]; to something like [request setDelegate:AlternateDelegate]; and just add an include at the begining of the document to reference the AlternateDelegate delegate
I know this question is old, but in case anyone comes across it:
#Hankweb seemes to be talking about using a request as its own delegate. There are certainly situations where this works. For example, I'm working on a project that uses ASIHTTPRequest to fetch JSON from a remote source and import it into a Core Data store.
This operation (literally, as ASIHTTPRequest is a subclass of NSOperation) is almost entirely self-contained; I have a custom request on a background thread using a streaming JSON parser to import objects into a NSManagedObjectContext, which, when saved, triggers a notification that I catch internally and pass to the main thread's context using performSelectorOnMainThread:waitUntilDone:.
I'm using ASIHTTPRequest's block support to accomplish this; in my custom initWithURL: method, I set up the relevant callbacks (dataReceivedBlock, completionBlock, failureBlock, etc.). The traditional delegation pattern (using the ASIHTTPRequestDelegate protocol) should also work, though.
One gotcha: you should make sure the request doesn't retain itself too many times, or else you'll end up with a memory leak. This can be easy to miss when using multiple threads, and especially when using blocks. Instead of:
- (id)initWithURL:(NSURL *aURL) {
//...
[self setCompletionBlock:^{
[self doSomething];
}];
//...
return self;
}
Use the __weak attribute (or __block if you're not using ARC) when referencing self within the blocks:
- (id)initWithURL:(NSURL *aURL) {
//...
__weak id blockSelf = self;
[self setCompletionBlock:^{
[blockSelf doSomething];
}];
//...
return self;
}
If you don't know why this is important, make sure to read Apple's guide to blocks in Objective-C, and the ASIHTTPRequest block API documentation.
A delegate for ASIHTTPRequest is just a standard objective C object. Just create a new class, include it's header, create/get the object and set the delegate to be that object.
Have you tried this and run into a problem? If so what is the problem?
I'm facing very annoying problem. My iPhone app is loading it's data from a network server. Data are sent as plist and when parsed, it neeeds to be stored to SQLite db using CoreData.
Issue is that in some cases those datasets are too big (5000+ records) and import takes way too long. More on that, when iPhone tries to suspend the screen, Watchdog kills the app because it's still processing the import and does not respond up to 5 seconds, so import is never finished.
I used all recommended techniques according to article "Efficiently Importing Data" http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/CoreData/Articles/cdImporting.html and other docs concerning this, but it's still awfully slow.
Solution I'm looking for is to let app suspend, but let import run in behind (better one) or to prevent attempts to suspend the app at all. Or any better idea is welcomed too.
Any tips on how to overcome these issues are highly appreciated!
Thanks
Instead of pushing plist files to the phone, you might want to send ready to use sqlite files. This has many advantages:
no need to import on the phone
more compact
If you always replace the whole content simply overwrite the persistent store in the device. Otherwise you may want to maintain an array as plist with all sqlites you have downloaded and then use this to add all stores to the persistentStoreCoordinator.
Bottom line: use several precompiled sqlite files and add them to the persistentStoreCoordinator.
You can use the iPhone Simulator to create those CoreData-SQLite-Stores or use a standalone Mac app. You will need to write both of those yourself.
First, if you can package the data with the app that would be ideal.
However, assuming you cannot do that then I would do then following:
Once the data is downloaded break it into multiple files before import.
Import on a background thread, one file at a time.
Once a file has been imported and saved, delete the import file.
On launch, look for those files waiting to be processed and pick up where you left off.
Ideally sending the data with the app would be far less work but the second solution will work and you can fine-tune the data break up during development.
I solved a similar problem by putting the insert processing in a background thread. But first I created a progress alert so the user couldn't manipulate the data store while it was inserting the entries.
This is basically the ViewControllers viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
// Only insert those not imported, here I know it should be 2006 entries
if ([self tableView:nil numberOfRowsInSection:0] != 2006) {
// Put up an alert with a progress bar, need to implement
[self createProgressionAlertWithMessage:#"Initilizing database"];
// Spawn the insert thread making the app still "live" so it
// won't be killed by the OS
[NSThread detachNewThreadSelector:#selector(loadInitialDatabase:)
toTarget:self
withObject:[NSNumber numberWithInt:[self tableView:nil
numberOfRowsInSection:0]]];
}
}
The insert thread was done like this
- (void)loadInitialDatabase:(NSNumber*)number
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int done = [number intValue]+1; // How many done so far
// I load from a textfile (csv) but imagine you should be able to
// understand the process and make it work for your data
NSString *file = [NSString stringWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:#"filename"
ofType:#"txt"]
encoding:NSUTF8StringEncoding
error:nil];
NSArray *lines = [file componentsSeparatedByString:#"\n"];
float num = [lines count];
float i = 0;
int perc = 0;
for (NSString *line in lines) {
i += 1.0;
if ((int)(i/(num*0.01)) != perc) {
// This part updates the alert with a progress bar
// setProgressValue: needs to be implemented
[self performSelectorOnMainThread:#selector(setProgressValue:)
withObject:[NSNumber numberWithFloat:i/num]
waitUntilDone:YES];
perc = (int)(i/(num*0.01));
}
if (done < i) // keep track of how much done previously
[self insertFromLine:line]; // Add to data storage...
}
progressView = nil;
[progressAlert dismissWithClickedButtonIndex:0 animated:YES];
[pool release];
}
It's a bit crude this way, it tries to init the data storage from where it left of if the user happend to stop it the previous times...
I had a similar problem importing many objects into CoreData. Initially i was doing a save on the managed object context after every object i wished to create & insert.
What you should do is create/initialize each object you want to save in CoreData, and after you have looped through all your remote data + created the objects, do a managed object context save.
I guess you could look at this as doing doing a transaction in a SQLite database: begin transaction, do lots of inserts/updates, end transaction.
if this still is too lengthy, just thread the darn task and prevent user interaction until complete
Is there any way you can pack the data ahead of time - say during development? And when you push the app to the store, some of the data is already there? That'll cut down on the amount of data you have to pull, thus helping to solve this issue?
If the data is time sensitive, or not ready, or for whatever reason you can't do that, could you compress the data using zlib compression before you ship it over the network?
Or is the problem that the phone dies doing 5K+ inserts?
I imagine you aren't showing all 5K records to the client? I'd recommend doing all of the aggregation you need on the server, and then only sending the necessary data to the phone. Even if this involves generating a few different data views, it'll still be orders of magnitude faster than sending (and then processing) all those rows in the iPhone.
Are you also processing the data in a separate (non event/ui) thread?
Any chance you can setup your server side to expose a RESTful web service for processing your data? I had a similar issue and was able to expose my information through a RESTful webservice. There are some libraries on the iphone that make reading from a webservice like that very easy. I chose to request JSON from the service and used the SBJSON library on the iphone to quickly take the results I got and convert them to dictionaries for easy use. I used the ASIHTTP library for making the web requests and queueing up follow up requests and making them run in the background.
The nice thing about REST is that it a built in way for you to grab batches of information so that you don't need to arbitrarily figure out how to break up your files you want to input. You just setup how many records you want to get back, and the next request you skip that many records. I don't know if that is even an option for you, so I'm not going into a lot of code examples right now, but if it is possible, it may be a smooth way to handle it.
Lets accept that Restful (lazy loading) is not an option... I understand you want to replicate. If the load problem is of the type 'less and less rows loading in more and more time) then in psuedo code...
[self sQLdropIndex(OffendingIndexName)]
[self breathInOverIP];
[self breathOutToSQLLite];
[self sQLAddIndex(OffendingIndexName)]
This should tell you lots.
I work on an app that regularly has to process 100K inserts, deletes, and updates with Core Data. If it is choking on 5K inserts, there is some optimization to be done.
Firstly, create some NSOperation subclass for processing the data. Override its -main method to do the processing. This method is, however, not guaranteed to run on the main thread. Indeed, its purpose is to avoid executing costly code on the main thread which would affect the user experience by making it freeze up grossly. So within the -main method, you need to create another managed object context which is the child of your main thread's managed object context.
- (void)main
{
NSManagedObjectContext *ctx = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[ctx setPersistentStoreCoordinator:mainManagedObjectContext.persistentStoreCoordinator];
[ctx setUndoManager:nil];
// Do your insertions here!
NSError *error = nil;
[ctx save:&error];
}
Given your circumstances, I don't believe you need an undo manager. Having one will incur a performance penalty because Core Data is tracking your changes.
Use THIS context to perform all of your CRUD actions in the -main method, then save that managed object context. Whatever owns your main thread's managed object context must register to respond to the NSNotification named NSManagedObjectContextDidSaveNotification. Register like so:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(mocDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:nil];
Then define that selector:
- (void)mocDidSaveNotification:(NSNotification *)notification
{
NSManagedObjectContext *ctx = [notification object];
if (ctx == mainManagedObjectContext) return;
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
When all of this comes together, it will allow you to perform long-running operations on background threads without blocking the UI thread. There are several variations of this architecture, but the central theme is this: processing on BG thread, merge on main thread, update your UI. Some other things to keep in mind: (1) keep an autorelease pool around during your processing and drain it every so often to keep your memory consumption down. In our case, we do it every 1000 objects. Adjust for your needs, but keep in mind that draining can be expensive depending on the amount of memory required per object, so you don't want to do it too often. (2) try to pare your data down to the absolute minimum that you need to have a functional app. By reducing the amount of data to parse, you reduce the amount of time required to save it. (3) by using this multithreaded approach, you can concurrently process your data. So create 3-4 instances of your NSOperation subclass, each of which processes only a portion of the data so that they all run concurrently, resulting in a smaller amount of real time consumed for parsing the data set.
I am intending to create 2 requests using NSURLConnection. When the server responds and calls connectionDidFinishLoading it passes in the connection as the parameter, but how do I identify which connection is passed in?
Save both NSURLConnection objects as member variables of whatever delegate object you passed to connectionWithRequest:delegate:. Then you can just compare each of those to the NSURLConnection passed to connectionDidFinishLoading:, and respond appropriately:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if (connection == firstConnection) {
// do something
}
else if (connection == secondConnection) {
// do something else
}
}
Another slightly more object-oriented option would be to create two different delegate objects, each of which knows how to deal with each each type of connection. Then just pass the appropriate delegate when you create each connection. That way you don't need to check to see which connection you have, because each delegate will only receive connectionDidFinishLoading: for its own connection.
I prefer different delegates for each connection, too. Although it's a bit of overhead. Fortunately, you can simplify things by using blocks. It's a new feature that doesn't exist in standard SDK yet, but there is 3rd-party framework called PLBlocks that you can use already. Here is an article on how to use them, it also contains example for NSURLConnection.
This is the client code making HTTP request with block callback:
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.com"]];
[NSURLConnection sendAsynchronousRequest:req onCompletionDo: ^(NSData *data, NSURLResponse *res, NSError *err) {
NSLog(#"data: %ld bytes. res: %#, error: %#", (long)[data length], res, err);
[cell.activity stopAnimating];
}];
I used to create a custom wrapper around NSURLConnection, too, but I've now switched over to ASIHTTPRequest. This is a fantastic library providing much more flexibility and features than NSURLConnection. Have a look and give it a try, it's really worth it.
What I do in my projects is create a wrapper class for the connection. This way, you can keep a new instance for each connection you need, and maintain these classes in another manager class.
Something like [AsynchronousConnection initWithURL:delegate:selector:]
Then you can be ensure the right thing is called when the NSURLConnection is done/failed.
Please do not refer https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html
GO to the NSURLConnection.h file and you will find the following.
When created, an NSURLConnection performs a deep-copy of the
NSURLRequest. This copy is available through the -originalRequest
method. As the connection performs the load, this request may change
as a result of protocol canonicalization or due to following
redirects. -currentRequest can be used to retrieve this value.
Ultimately [connection currentRequest].URL absoluteURL might help.
Regards,
PRASANNA.