My code calls HTTP post call to remote server and obtains results in JSON format, I have extracted this JSON, but now I need to store these results to SQLite. Based on my reading NSURLSessionDataTask is background thread, so my confusion is, should I call SQL open and insert inside completionHandler (or) is there any other best practice to handle this type of requirements?
EDIT 1
The point I am struggling more is, is it valid to write SQLite operations inside "completionHandler"? does "completionHandler" will be considered as method on separate thread (which is executing SessionDataTask) or main thread?
EDIT 2
I am open to CoreData related solutions too.
Any help would be appreciated.
NSURL *loginUrl = [NSURL URLWithString:#"myurl"];
NSURLSession *session = [NSURLSession sharedSession];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:loginUrl];
request.HTTPMethod = #"POST";
NSString *ipData = [NSString stringWithFormat:#"uName=%#&pwd=%#",self.userName.text,self.userPwd.text];
request.HTTPBody = [ipData dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *jsonError) {
NSLog(#"Inside post data task......");
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)response;
if(httpResp.statusCode == 200)
{
NSLog(#"Response succesfull.........");
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
if(!jsonError)
{
//No Json error
NSString *uName = jsonDict[#"userName"];
NSString *uID = jsonDict[#"uID"];
//HOW CAN I INSERT THESE DETAILS TO SQLITE DB BEFORE CALLING SEGUE TO MOVE TO NEXT SCREEN?
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"mysegueID" sender:self];
});
}
}else
{
NSLog(#"Response is not succesfulll...");
}
}];
[postDataTask resume];
A lot of people use FMDB as objective-c wrapper around sqlite.
In case of NSURLSession, the block of the completion handler will be executed on the "delegate queue" (see delegateQueue property of NSURLSession).
It is valid to do SQLite in completion handler as long as you follow SQLite threading rules. I recommend FMDB her again because it has helpers for this. See Using FMDatabaseQueue and Thread Safety.
So your example would look like:
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
NSURL *loginUrl = [NSURL URLWithString:#"myurl"];
NSURLSession *session = [NSURLSession sharedSession];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:loginUrl];
request.HTTPMethod = #"POST";
NSString *ipData = [NSString stringWithFormat:#"uName=%#&pwd=%#",self.userName.text,self.userPwd.text];
request.HTTPBody = [ipData dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *jsonError) {
NSLog(#"Inside post data task......");
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)response;
if(httpResp.statusCode == 200)
{
NSLog(#"Response succesfull.........");
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
if(!jsonError)
{
//No Json error
NSString *uName = jsonDict[#"userName"];
NSString *uID = jsonDict[#"uID"];
//HOW CAN I INSERT THESE DETAILS TO SQLITE DB BEFORE CALLING SEGUE TO MOVE TO NEXT SCREEN?
[queue inDatabase:^(FMDatabase *db) {
NSDictionary *argsDict = #{ #"uName" : uName, #"uID" : uID};
[db executeUpdate:#"INSERT INTO myTable (name) VALUES (:name)" withParameterDictionary:argsDict];
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"mysegueID" sender:self];
});
}
}
else
{
NSLog(#"Response is not succesfulll...");
}
}];
[postDataTask resume];
A SQLite DB can be accessed from any thread in an app. The only restriction is that SQLite does not happily tolerate simultaneous access from multiple threads (and "simultaneous" here applies to the duration of a "transaction", not simply the duration of a call to SQLite methods).
So you must somehow assure that there is never simultaneous access. A simple way to do this is to always use the same thread (eg, the main thread) for access. Or you can implement "soft" protocols such that you know that two actions are not simultaneously trying to use the DB because they are separated in time. Or you can make use of Objective-C lock or other synchronization mechanisms in the software/OS.
Related
I am using GET method on Json. The GET method is inside a for loop, and the issue is it is not finishing the task or not getting the result. Instead the loop increments. I've placed a breakpoint inside the block where I am setting the result data to a NSDictionary but it never goes there.
Is it possible for the GET method to be directly called. I mean the code will be read line by line. And it will not skip or wait for the json to finish processing?
Here's what I've done:
- (void)downloadJsonFeed
{
for(int i = 1;i < self.numberOfEpisodes;i++)
{
NSString *endPoint = [[[[baseJsonUrl stringByAppendingString:getEpisodes]stringByAppendingString:self.title]stringByAppendingString:#"&episodeNumber="]stringByAppendingString:[NSString stringWithFormat:#"%i",i]];
NSLog(#"End point %#",endPoint);
[JsonDownload getJson:token andEndpointString:endPoint WithHandler:^(__weak id result)
{
NSArray *episodeArray =result;
//will do some task here
}];
}
}
- (void)getJson:(NSString *)authData andEndpointString:(NSString *)urlString WithHandler:(void(^)(__weak id result))handler
{
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
NSURL * url = [NSURL URLWithString:urlString];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
//NSString *auth = [NSString stringWithFormat:#"Bearer {%#}", authData];
[urlRequest setValue:#"application/json" forHTTPHeaderField:#"Content-type"];
[urlRequest setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[urlRequest setValue:authData forHTTPHeaderField:#"Cookie"];
[urlRequest setHTTPMethod:#"GET"];
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error == nil)
{
id returnedObject = [NSJSONSerialization JSONObjectWithData: data options: NSJSONReadingMutableLeaves error:nil];
handler(returnedObject);
}
else{
NSLog(#"error %#",error);
}
}];
[dataTask resume];
}
I've placed a break point in this line NSArray *episodeArray =result; and it never goes there. But when I put the break point on [JsonDownload getJson:token andEndpointString:endPoint WithHandler:^(__weak id result) line it is responding
And on the commented line //will do some task here I need to a task there before getting another json again. But I can't cause it never go inside the code block
Fixed the issue by replacing white space characters in my endPoint by %20
I must be making this harder than it is... or implementing the solutions I see online incorrectly.
I have an array of URLs which I would like to loop through and push the results to a dictionary in order or the array. How can I make it wait for the dictionary to be updated before running the next request? Basically I want to make the calls synchronously in a background thread.
Here is where I call the download:
for (NSString *path in paths) {
NSURLSession *session = [NSURLSession sessionWithConfiguration [NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:path]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error)
{
}
else
{
NSError *parsingError = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingAllowFragments
error:&error];
if (parsingError)
{
}
else
{
[myDictionary addObject:dict];
}
}
}];
[task resume];
}
Unless one request really requires the results of the prior request before being issued (which is not the case here), you should not run them sequentially. It may feel more logical to issue the sequentially, but you pay a huge performance penalty to do so. Issue them concurrently, save the results in some unordered structure (like a dictionary), and then when all done, build your ordered structure.
NSMutableDictionary *results = [NSMutableDictionary dictionaryWithCapacity:[paths count]];
// don't create new session for each request ... initialize this outside of the loop
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; // or since you're not doing anything special here, use `sharedSession`
// since we're going to block a thread in the process of controlling the degree of
// concurrency, let's do this on a background queue; we're still blocking
// a GCD worker thread as these run (which isn't ideal), but we're only using
// one worker thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// only do for requests at a time, so create queue a semaphore with set count
dispatch_semaphore_t semaphore = dispatch_semaphore_create(4); // only do four requests at a time
// let's keep track of when they're all done
dispatch_group_t group = dispatch_group_create();
// now let's loop through issuing the requests
for (NSString *path in paths) {
dispatch_group_enter(group); // tell the group that we're starting another request
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // wait for one of the four slots to open up
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:path]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
// ...
} else {
NSError *parsingError = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (parsingError) {
} else {
// synchronize updating of dictionary
dispatch_async(dispatch_get_main_queue(), ^{
results[path] = dict;
});
}
}
dispatch_semaphore_signal(semaphore); // when done, flag task as complete so one of the waiting ones can start
dispatch_group_leave(group); // tell the group that we're done
}];
[task resume];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// trigger whatever you want when they're all done
// and if you want them in order, iterate through the paths and pull out the appropriate result in order
for (NSString *path in paths) {
// do something with `results[path]`
}
});
});
I tried to reduce the amount of extra dependencies here, so I used dispatch groups and semaphores. In the above, I use semaphores to constrain the degree of concurrency and I use dispatch group to identify when it's all done.
Personally, I wouldn't use semaphores and groups, but rather I'd wrap these requests in asynchronous NSOperation subclass, but I was trying to limit the changes I made to your code. But the NSOperation idea is logically the same as the above: Run them concurrently, but constrain the degree of concurrency so you don't end up with them timing out on you, and trigger the retrieval of the results only when all the results are retrieved.
My Code works just fine. What I need help, or clarification on is Nested NSURLSessionDataTask instances.
I'm making two asynchronously calls, the second call is dependent on the first.
So I make the first NSURLSessionDataTask (firstUrlCall) call which returns an array of objects. For each object in my array I then call the second NSURLSessionDataTask (secondUrlCall) and pass in a dataID.
As I mentioned before, it works. I just see alot of lines repeated and REPEATED CODE IS NOT SEXY!!!
So is there anything I can do to prevent this catastrophe? I need my code to be SEXY!
#property (nonatomic, strong) NSURLSession *Session;
FIRST CALL
-(void) firstUrlCall {
NSString *urlString = #"https://api.FIRSTURLCALL.com";
NSURLSessionDataTask *dataTask = [session
dataTaskWithURL:[NSURL URLWithString:urlString]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!error) {
NSDictionary *returnData = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
[returnData enumerateKeysAndObjectsUsingBlock:^(id dataID, id obj, BOOL *stop) {
/*
-->here is where I call secondUrlCall<--
*/
[self secondUrlCall:dataID];
}];
}
});
}];
[dataTask resume];
}
SECOND CALL
-(void) secondUrlCall:(NSString *)dataID {
NSString *urlString = [NSString stringWithFormat:#"https://api.SECONDURLCALL.com?dataID=%#",dataID];
NSURLSessionDataTask *dataTask = [session
dataTaskWithURL:[NSURL URLWithString:urlString]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
if ([[json objectForKey:#"type"] isEqualToString:#"sexy"]) {
[tableArray addObject:json];
// Reload table data
[self.tableView reloadData];
}
}
});
}];
[dataTask resume];
}
PS: Sorry if you were offended from my extensive use of the word SEXY :)
Oh my goodness! What if the network is intermittent or goes down half way through?
I would take the results of the first call and put each one into an operation queue, then when processing each operation if it fails you can re-queue it.
I'm getting JSON data from the web, parse it, then using it to diplay pins on a map.
Here is method one, which there is no problem with:
NSString *CLIENT_ID = #"SECRET_ID";
NSString *CLIENT_SECRET = #"CLIENT_SECRET";
NSString *SEARCH = [NSString stringWithFormat:#"https://api.foursquare.com/v2/venues/search?near=gjovik&query=cafe&client_id=%#&client_secret=%#&v=20140119", CLIENT_ID, CLIENT_SECRET];
NSURL *searchResults = [NSURL URLWithString:SEARCH];
NSData *jsonData = [NSData dataWithContentsOfURL:searchResults];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
self.venues = dataDictionary[#"response"][#"venues"];
[self loadAnnotationsAndCenter:YES];
[self loadAnnotationsAndCenter:YES]; retrieves the lat and lng from the JSON file and uses it to display pins on the map.
I decided to change my code uing NSURLSession. Here's what it looks like:
NSString *CLIENT_ID = #"SECRET_ID";
NSString *CLIENT_SECRET = #"CLIENT_SECRET";
NSString *SEARCH = [NSString stringWithFormat:#"https://api.foursquare.com/v2/venues/search?near=gjovik&query=cafe&client_id=%#&client_secret=%#&v=20140119", CLIENT_ID, CLIENT_SECRET];
NSURL *URL = [NSURL URLWithString:SEARCH];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
self.venues = dataDictionary[#"response"][#"venues"];
[self loadAnnotationsAndCenter:YES];
}];
[task resume]
NSURLSession is between 10 and 30 seconds slower than the first method. I don't understand why that is. The search string, in this case, returns 27 different locations, if that matters
Cheers.
You need to ensure that UIKit methods will be executed on the main thread.
The block of the completion handler will be executed on the "delegate queue" (see delegateQueue property of NSURLSession).
You can accomplish this in two ways: either setup the delegate queue which is the main queue ([NSOperationQueue mainQueue]) or use
dispatch_async(dispatch_get_main_queue(), ^{
[self loadAnnotationsAndCenter:YES];
});
in your completion block.
In Swift:
dispatch_async(dispatch_get_main_queue()) {
}
I am trying to pull some data from my local node server. The server is getting the get request and logging it, but for some reason my iOS app will not execute any of the code that I have in the completion handler. Here is the code:
- (IBAction) buttonPressed{
NSURL *url = [NSURL URLWithString:#"http://127.0.0.1:3000/"];
NSURLSessionDataTask *dataTask =
[self.session dataTaskWithURL:url
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error){
nameLabel.text = #"yay!";
/*
if (!error){
nameLabel.text = #"noerr";
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)response;
if (httpResp.statusCode == 200){
NSError *jsonErr;
NSDictionary *usersJSON =
[NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingAllowFragments
error:&jsonErr];
if (!jsonErr){
// nameLabel.text = usersJSON[#"username"];
nameLabel.text = #"nojerr";
}
else{
nameLabel.text = #"jsonErr";
}
}
}
else{
nameLabel.text = #"Err";
}
*/
}];
[dataTask resume];
}
When the program is run, the nameLabel is not changed to "yay". However if I try to change the nameLabel before the NSURLSessionDataTask line, it changes.
NSURLSessionDataTask runs in a background thread. To update anything in the user interface such as labels, buttons, table views, etc, you must do so on the main thread. If you want to update the label text from the completionHandler block then you need to update the label in the main thread like so:
dispatch_sync(dispatch_get_main_queue(), ^{
nameLabel.text = #"yay!";
});
try this magic:
static NSURLSession* sharedSessionMainQueue = nil;
if(!sharedSessionMainQueue){
sharedSessionMainQueue = [NSURLSession sessionWithConfiguration:nil delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
}
NSURLSessionDataTask *dataTask =
[sharedSessionMainQueue dataTaskWithURL:url completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error){
//now will be on main thread
}];
[dataTask resume];
This gives you the original behavior of NSURLConnection with the completing handler on the main thread so you are safe to update the UI. However, say you would like to parse the download or do some heavy processing, in that case you might benefit from the completion handler on the operation queue's background thread and then using dispatch_sync to the main thread as a final step.