Reputation: 14835
When my iOS app starts, I need to get some critical settings from my server (example: http://www.example.com/critical_app_settings.php). I need to get this data before I load up the user's data.
What is the proper way to make this call, basically "pause" the app, and yet still not block the main thread and be in good compliance?
Currently I am doing this example, which obviously isn't correct, because it completely blocks everything:
NSData *myRequestData = [NSData dataWithBytes:[myRequestString UTF8String] length:[myRequestString length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL: [NSURL URLWithString: myURLString]];
[request setHTTPMethod: @"POST"];
[request setHTTPBody:myRequestData];
NSURLResponse *response;
NSError *error;
NSString *returnString = [[NSString alloc] init];
returnString = [[NSString alloc] initWithData:[NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error]
encoding: NSASCIIStringEncoding];
Upvotes: 0
Views: 137
Reputation: 519
/**
You can use a protocol to handle that for you, for example you can create a class that connect to the URL you can use NSURLSession but it seems that you are familiar with NSURLConnection. So lets create your class that will connect and receive the information from your URL something like this:
The H file
*/
#import <Foundation/Foundation.h>
/* Here is the custome protocol to manage the response at will */
@protocol OnResponseDelegate;
/* Here the class is implementing the protocol to receive the callbaks from the NSURLConnection when the request is processing */
@interface WgetData : NSObject <NSURLConnectionDataDelegate>
@property (strong, nonatomic) id<OnResponseDelegate>handler;
@property (strong, nonatomic) NSMutableData *responseData;
@property (strong, nonatomic) NSURLConnection *connection;
@property (strong, nonatomic) NSMutableURLRequest *request;
-(id)initWithUrl:(NSString*)url postBody:(NSString*)body delegate:(id)delegate;
-(void)execute;
@end
/* here is the real definition of the protocol to manage the response */
@protocol OnResponseDelegate <NSObject>
-(void)onPreStart;
-(void)onResponse:(NSData*)response;
@end
/* End H file
The code above is using a protocol to handle the data that you want to receive from the URL. lets do the implementation file it should look like this:
The M file
*/
#import "WgetData.h"
#define TIMEOUT 700
static NSString * const CONTENT_TYPE_VALUE = @"application/json";
static NSString * const CONTENT_TYPE_HEADER = @"Content-Type";
static NSString * const GET = @"GET";
static NSString * const POST = @"POST";
@implementation WgetData
@synthesize handler,responseData,connection,request;
-(id)initWithUrl:(NSString *)url postBody:(NSString *)body delegate:(id)delegate{
NSURL *requestUrl = [NSURL URLWithString:[url stringByAddingPercentEncodingWithAllowedCharacters: NSCharacterSet.URLQueryAllowedCharacterSet]];
[self setRequest:[NSMutableURLRequest requestWithURL:requestUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:TIMEOUT]];
[self setResponseData:[NSMutableData new]];
[self setHandler:delegate];
[request setHTTPMethod:POST];
[request addValue:CONTENT_TYPE_VALUE forHTTPHeaderField:CONTENT_TYPE_HEADER];
[request setHTTPBody: [body dataUsingEncoding:NSUTF8StringEncoding]];
return self;
}
-(void)execute{
//here is the moment to prepare something in your main class before send the request to your URL
[handler onPreStart];
[self setConnection:[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]];
}
/* this method belongs to the implementation of the NSURLConnectionDataDelegate to receive the data little by little */
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.responseData appendData:data];
}
/* this method belongs to the implementation of the NSURLConnectyionDataDelegate that is runned only when the data received is complete */
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
/* if this method happen it means that the data is ready to delivery */
/* sending the information to the class that implements this class through the handler or delegate */
[handler onResponse:responseData];
}
@end
/*
End M file
Then you only need to implement this class with the next piece of code For example in a ViewController inside a viewDidLoad
remember to call the protocol as a delegate to implement the methods in the right way
H file ViewController example
*/
#import <UIKit/UIKit.h>
#import "WgetData.h"
@interface ViewController : UIViewController <OnResponseDelegate>
@end
/*
End H ViewController file
M ViewController file
*/
@interface MainViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
WgetData *getData = [[WgetData alloc] initWithUrl:@"http://myurl.net" delegate:self];
[getData execute];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void) onPreStart{
// prepare something before run the request to your URL
}
- (void) onResponse:(NSData *)response{
// receive the data when the data is ready and convert o parse to String or JSON or Bytes or whatever
}
@end
/* End M file
So with all this example you can manage the right moment when you will receive the data to manage at your own will, and your main thread app can wait while your data will be received to be ready for use. */
Upvotes: 1
Reputation: 131418
You should NEVER use sendSynchronousRequest
. You should pretend that method does not exist. It's a very bad idea to call that on the main thread, and the system will likely kill your app if the remote server takes more than a few seconds to serve your request.
As Vishnu suggests, use NSURLSession
instead.
If you want to "pause the app" while your content loads, display some sort of modal alert/view while the load takes place. One option is to use a UIAlertController
with no actions added, and then call dismissViewController:animated:
on it once the load is complete.
Another thing I've done is to display a view that completely covers the screen, filled in 50% opaque black, and put a view inside that with a progress indicator inside it, plus a please wait message (and any other cosmetic stuff you want.) The 50% opaque view both dims the rest of the screen and prevents the user from clicking on anything, and the progress indicator lets the user know that your app is working.
Both of these approaches work because your download is taking place asynchronously, so you can display an alert or a progress indicator and it animates as expected.
Upvotes: 3
Reputation: 3956
Instead of using NSURLConnection sendSynchronousRequest
you can use NSURLSessionDataTask
. Also sendSynchronousRequest
is deprecated from iOS 9. Code will be as follows
NSData *myRequestData = [NSData dataWithBytes:[myRequestString UTF8String] length:[myRequestString length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL: [NSURL URLWithString: myURLString]];
[request setHTTPMethod: @"POST"];
[request setHTTPBody:myRequestData];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
if ([httpResponse statusCode]==200) {
dispatch_async(dispatch_get_main_queue(), ^{
NSString* returnString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//do whatever operations necessary on main thread
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
//action on failure
});
}
}];
[dataTask resume];
Since it is async operation your main thread will not be blocked. As far as pausing app is concerned you can either show some kind of activity indicator or disable user interaction before making call. Once response comes hide indicator or enable the UI.
Upvotes: 3