Getting date from web, returns nil - ios

Doing a check to get the current time and date from Google. The first option works although not the best way to do this as it's using a depreciated method and waiting for everything to finish with the synchronous method is not good UX.
-(NSDate*)timeAndDateFromWeb{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL:[NSURL URLWithString:#"https://google.co.uk"]];
[request setHTTPMethod:#"GET"];
NSHTTPURLResponse *httpResponse = nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&httpResponse error:nil];
NSString *dateString = [[httpResponse allHeaderFields] objectForKey:#"Date"];
DebugLog(#" *** GOOGLE DATE: %# ****",dateString);
if (httpResponse){
hasDataConnection = YES;
}
else{
hasDataConnection = NO;
}
// Convert string to date object
NSDateFormatter *dateformatted = [NSDateFormatter new];
[dateformatted setDateFormat:#"E, d MMM yyyy HH:mm:ss zzz"];
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en"];
[dateformatted setLocale:locale];
return [dateformatted dateFromString:dateString];
}
Trying to adapt it is almost there although I'm returning nil for my date string: [dateformatted dateFromString:dateString];
NSURL *url = [NSURL URLWithString:#"https://google.co.uk"];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSHTTPURLResponse *httpResponse = nil;
NSString *dateString = [[httpResponse allHeaderFields] objectForKey:#"Date"];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
hasDataConnection = NO;
//NSLog(#"\n\n ----> Not connected Error,%#", [error localizedDescription]);
}
else {
//NSLog(#"\n\n -----> connected: %#", [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
hasDataConnection = YES;
}
}];
// Convert string to date object
NSDateFormatter *dateformatted = [NSDateFormatter new];
[dateformatted setDateFormat:#"E, d MMM yyyy HH:mm:ss zzz"];
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en"];
[dateformatted setLocale:locale];
DebugLog(#" *** GOOGLE DATE: %# ****",[dateformatted dateFromString:dateString]);
return [dateformatted dateFromString:dateString];

When you're switching from sync to async, can't return value the same way as before.
When you call sendAsynchronousRequest method, it starts a background task, but your thread continues work instantly. That's why both httpResponse and dateString are null.
So instead of that you should change your return type to void, because you can't return result instantly, and add a callback, which will be run when the job is done. And process your formatting in the completion of the task too:
- (void)timeAndDateFromWeb:(void (^)(NSDate *))completion {
NSURL *url = [NSURL URLWithString:#"https://google.co.uk"];
NSURLSessionTask *task = [NSURLSession.sharedSession
dataTaskWithURL:url
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
// hasDataConnection = NO;
//NSLog(#"\n\n ----> Not connected Error,%#", [error localizedDescription]);
}
else if ([response isKindOfClass:NSHTTPURLResponse.class] ) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSString *dateString = [[httpResponse allHeaderFields] objectForKey:#"Date"];
// Convert string to date object
NSDateFormatter *dateformatted = [NSDateFormatter new];
[dateformatted setDateFormat:#"E, d MMM yyyy HH:mm:ss zzz"];
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en"];
[dateformatted setLocale:locale];
// DebugLog(#" *** GOOGLE DATE: %# ****",[dateformatted dateFromString:dateString]);
completion([dateformatted dateFromString:dateString]);
//NSLog(#"\n\n -----> connected: %#", [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
// hasDataConnection = YES;
}
}];
[task resume];
}
And so the result of this method will be returned to the block, same as result of the background task.
Don't forget, that it will be called on the background thread, same as the download task completion block. So if you wanna change some UI you need to move back to the main thread:
[self timeAndDateFromWeb:^(NSDate *date) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#", date);
// UI changes
});
}];
Alternatively you can move it to the main thread inside your function, right before returning the result:
NSDate *date = [dateformatted dateFromString:dateString];
dispatch_async(dispatch_get_main_queue(), ^{
completion(date);
});

Related

Pull to refresh functionality isn't working

I've put the code below in, and it looks close but I can't figure out why it's not doing the actual refresh. It has the "Pull to refresh" and the updated text displaying properly, but it's not updating the actual data. Am I missing something obvious, or do I have it misplaced or something?
I edited to add the self tableview call to reload the data. Still no luck.
FINAL EDIT___User below solved it with calling the data feed.
- (void)viewDidLoad
{
[super viewDidLoad];
UIRefreshControl *refresh = [[UIRefreshControl alloc] init];
refresh.attributedTitle = [[NSAttributedString alloc] initWithString:#"Pull to refresh"];
[refresh addTarget:self action:#selector(refreshmytable:) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refresh;
NSURLSessionConfiguration *config =
[NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config
delegate:self
// delegate:nil
delegateQueue:nil];
[self fetchFeed];
}
- (void)refreshmytable:(UIRefreshControl *)refreshControl{
[self fetchFeed]; //Added 12:12 9.16.14
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Updating"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMM d, h:mm a"];
NSString *updated = [NSString stringWithFormat:#" Last Update: %#", [formatter stringFromDate:[NSDate date]]];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:updated];
[refreshControl endRefreshing];
[self.tableView reloadData]; //Added this 11:32 9.16.14
}
- (void)fetchFeed
{
NSString *userEID = MAP_getUsername();
//NSLog(userEID);
NSString *requestString1 = [#"URL" stringByAppendingString:userEID];
NSString *requestString2 = #"&status=pending";
NSString *requestString = [requestString1 stringByAppendingString:requestString2];
//NSLog(requestString);
/*NSString *requestString = #"http://URL";
*/
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask =
[self.session dataTaskWithRequest:req
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
self.changeList = jsonObject[#"List"];
//self.changeList=nil; //tried to add here to remove duplicate data
NSLog(#"%#", self.changeList);
//- add code here to populate BNRItemStore with the change order list.
// - following code should be rewritten in fetchFeed that will load BNRItemStore.
if (self.changeList.count>0) {
for (int i = 0; i < self.changeList.count; i++) {
NSDictionary *coItem = self.changeList[i];
[[BNRItemStore sharedStore]
addItemWithApproverEid:coItem[#"approverEid"]
assignmentGroup:coItem[#"assignmentGroup"]
changeOrder:coItem[#"changeOrder"]
subcategory:coItem[#"subCatagory"]
title:coItem[#"title"]
];
}
}
//NSLog(#"sizeof(NSInteger) = %#", #(sizeof(NSInteger)));
//- end comment
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
//self.changeList=nil; //trying to null out list for refresh non duplicate data
// NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// NSLog(#"%#", json);
}];
[dataTask resume];
}
You are not fetching the new data. You have a method/message call fetchFeed that you call in the viewDidLoad but you never call it in the refresh method/message. I assume that if you refresh, then you need to fetch new data. Call `[self fetchFeed];' before reloading the table view. If you are fetch the data asynchronously, then you need to have the table view reload in the completion block when fetching the new data is complete.
You need to call to reloadData somewhere in refreshmytable method for the table view to update the data
- (void)refreshmytable:(UIRefreshControl *)refreshControl{
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Updating"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMM d, h:mm a"];
NSString *updated = [NSString stringWithFormat:#" Last Update: %#", [formatter stringFromDate:[NSDate date]]];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:updated];
[refreshControl endRefreshing];
[self.tableView reloadData]
}

Asynchronous request returning nil

i've made following Asynchronous request, the problem is that its empty i've tried in the bottom NSLog the fixtures where its empty. I've checked that the nsstring home, away, league and so on returns values and it does. How come the values are not added to the fixtures NSMutableArray
[ProgressHUD show:#"Loading..."];
NSURL *url = [NSURL URLWithString:#"API_URL"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
jsonResult = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
int subObjects = ((NSArray *)jsonResult[#"match"]).count;
for (int i = 0; i <= subObjects-1; i++) {
NSString *date = [NSString stringWithFormat:#"%# %#",[[[jsonResult valueForKey:#"match"] valueForKey:#"playdate"] objectAtIndex:i], [[[jsonResult valueForKey:#"match"] valueForKey:#"time"] objectAtIndex:i]];
NSString *identifier = [[NSLocale currentLocale] localeIdentifier];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setTimeZone: [NSTimeZone timeZoneWithName:#"US/Arizona"]];
[df setLocale:[NSLocale localeWithLocaleIdentifier:identifier]];
[df setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate *myDate = [df dateFromString:[NSString stringWithFormat:#"%#", date]];
NSArray *items = [[NSString stringWithFormat:#"%#", myDate] componentsSeparatedByString:#" "];
NSString *home = [[[jsonResult valueForKey:#"match"] valueForKey:#"hometeam"] objectAtIndex:i];
NSString *away = [[[jsonResult valueForKey:#"match"] valueForKey:#"awayteam"] objectAtIndex:i];
NSString *league = [[[jsonResult valueForKey:#"match"] valueForKey:#"league"] objectAtIndex:i];
[fixtures addObject:
[[NSMutableDictionary alloc] initWithObjectsAndKeys:
items[0], #"date",
items[1], #"time",
home, #"home",
away, #"away",
league, #"league",
nil]];
[sections addObject:
[[NSMutableDictionary alloc] initWithObjectsAndKeys:
items[0], #"date",
nil]];
}
}
];
[self.theTableView reloadData];
[ProgressHUD dismiss];
NSLog(#"%#", fixtures);
The problem is that the request is an asynchronous function
If the function is asynchronous, the function will create another thread and return immediately to execute the next line after the one that invoked the asynchronous function. Meanwhile the new thread will execute some code and, eventually execute the block passed as parameter, and finally the thread is killed and doesn't exist any more.
This means that
NSLog(#"%#", fixtures);
will most likely be executed before the sendAsynchronousRequest has finished it's job, that's why it is returning nil.
Everything you need to do to process the downloaded information should happen inside the completionHandler block, including the call to [self.theTableView reloadData];
It is a non-blocking operation. It means that by calling this method it returns immediatelly while the actual request is performing somewhere in background and then calls the handler block on queue specified in queue parameter.
You should reload tableview from the completion-handler block.
// 1 before request
NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
// 3 request completed
// some processing
...
[self.theTableView reloadData];
[ProgressHUD dismiss];
}
// 2 immediate return
update
Although you passing the main queue as queue parameter the handler block will be performed on next run loop iteration after you reloading table and logging the values.
// current run loop iteration
NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
// next run loop iteration
}
// current run loop iteration
if(!fixtures) {
fixtures = [[NSMutableArray alloc] init];
}
[fixtures addObject:#{
#"date": items[0],
#"time": items[1],
#"home": home,
#"away": away,
#"league": league
}];
if(!sections) {
sections = [[NSMutableArray alloc] init];
}
[sections addObject:#{
#"date": items[0]
}];
[self.theTableView reloadData];
[ProgressHUD dismiss];
NSLog(#"%#", fixtures);

Parse.com and AFNetworking 2.0 Date Queries

I'm trying to use AFNetworking 2.0 to get records from a Parse.com backend. I want to get records updated after a certain date. Parse.com documentation states that comparison queries against a date field need to be url encoded in the format:
'where={"createdAt":{"$gte":{"__type":"Date","iso":"2011-08-21T18:02:52.249Z"}}}'
This works perfectly using curl.
In my app, I am using AFNetworking 2.0 to run the query as below. I first set the request and response serializer when initializing the shared client:
+ (CSC_ParseClient *)sharedClient {
static CSC_ParseClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURL *baseURL = [NSURL URLWithString:#"https://api.parse.com"];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
[config setHTTPAdditionalHeaders:#{ #"Accept":#"application/json",
#"Content-type":#"application/json",
#"X-Parse-Application-Id":#"my app ID",
#"X-Parse-REST-API-Key":#"my api key"}];
NSURLCache *cache = [[NSURLCache alloc] initWithMemoryCapacity:10 * 1024 * 1024
diskCapacity:50 * 1024 * 1024
diskPath:nil];
[config setURLCache:cache];
_sharedClient = [[CSC_ParseClient alloc] initWithBaseURL:baseURL
sessionConfiguration:config];
_sharedClient.responseSerializer = [AFJSONResponseSerializer serializer];
_sharedClient.requestSerializer = [AFJSONRequestSerializer serializer];
});
return _sharedClient;
}
- (NSURLSessionDataTask *)eventsForSalesMeetingID:(NSString *)meetingID sinceDate:(NSDate *)lastUpdate completion:( void (^)(NSArray *results, NSError *error) )completion {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSTimeZone *gmt = [NSTimeZone timeZoneWithAbbreviation:#"GMT"];
[dateFormatter setTimeZone:gmt];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
NSString *dateString = [dateFormatter stringFromDate:lastUpdate];
NSLog(#"date = %#", dateString);
NSDictionary *params = #{#"where": #{#"updatedAt": #{#"$gte": #{#"__type":#"Date", #"iso": dateString}}}};
NSURLSessionDataTask *task = [self GET:#"/1/classes/SalesMeetingEvents"
parameters:params
success:^(NSURLSessionDataTask *task, id responseObject) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
NSLog(#"Response = %#", httpResponse);
if (httpResponse.statusCode == 200) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(responseObject[#"results"], nil);
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil, nil);
});
NSLog(#"Received: %#", responseObject);
NSLog(#"Received HTTP %d", httpResponse.statusCode);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil, error);
});
}];
return task;
}
But this yields a 400 error from the server. The url encoded query string returned looks like this after decoding:
where[updatedAt][$gte][__type]=Date&where[updatedAt][$gte][iso]=2014-01-07T23:56:29.274Z
I tried hard-coding the back end of the querystring like this:
NSString *dateQueryString = [NSString stringWithFormat:#"{\"$gte\":{\"__type\":\"Date\",\"iso\":\"%#\"}}", dateString];
NSDictionary *params = #{#"where":#{#"updatedAt":dateQueryString}};
This gets me closer, but still a 400 error; the returned query string from the server looks like this:
where[updatedAt]={"$gte":{"__type":"Date","iso":"2014-01-07T23:56:29.274Z"}}
How do I get the proper query string from AFNetworking? I started out using the ParseSDK, which made this query super easy, but their SDK is way to heavy (30+ MB).
From the parse.com REST documentation here:
The value of the where parameter should be encoded JSON. Thus, if you look at the actual URL requested, it would be JSON-encoded, then URL-encoded
You are so close with your hardcoded string, you just need to URL-encode the whole query. I have had success with the following:
NSString *dateQueryString = [NSString stringWithFormat:#"{\"updatedAt\":{\"$gte\":{\"__type\":\"Date\",\"iso\":\"%#\"}}}", dateString];
NSDictionary *parameters = #{#"where": dateQueryString};
The following uses an NSDictionary to construct the dateQueryString:
NSString *dateQueryString;
NSDictionary *query = #{ #"updatedAt": #{ #"$gte": #{#"__type":#"Date",#"iso":dateString}}};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:query
options:nil
error:&error];
if (!jsonData) {
NSLog(#"Error: %#", [error localizedDescription]);
}
NSString *dateQueryString = [[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding];
NSDictionary *parameters = #{#"where": dateQueryString};
For completeness - I am using a shared instance of an AFHTTPSessionManager subclass:
#implementation ParseAPISessionManager
+ (instancetype)sharedSession {
static ParseAPISessionManager *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[ParseAPISessionManager alloc] initWithBaseURL:[NSURL URLWithString:parseAPIBaseURLString]];
});
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (self) {
self.requestSerializer = [AFJSONRequestSerializer serializer];
[self.requestSerializer setValue:parseAPIApplicationId forHTTPHeaderField:#"X-Parse-Application-Id"];
[self.requestSerializer setValue:parseRESTAPIKey forHTTPHeaderField:#"X-Parse-REST-API-Key"];
}
return self;
}
And calling it like this:
ParseAPISessionManager *manager = [ParseAPISessionManager sharedSession];
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setDay:13];
[comps setMonth:2];
[comps setYear:2014];
[comps setHour:16];
[comps setMinute:0];
NSDate *feb13 = [[NSCalendar currentCalendar] dateFromComponents:comps];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.'999Z'"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:#"GMT"]];
NSString *feb13str = [dateFormatter stringFromDate: feb13];
NSString *queryStr = [NSString stringWithFormat:#"{\"updatedAt\":{\"$gte\":{\"__type\":\"Date\",\"iso\":\"%#\"}}}", feb13str];
NSDictionary *parameters = #{#"where": queryStr};
[manager GET:#"classes/TestClass" parameters:parameters success:^(NSURLSessionDataTask *operation, id responseObject) {
NSLog(#"%#", responseObject);
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
Sorry for the long post, hope it helps

How to load JSON asynchronously (iOS)

My app parses information from a Rails app using JSON. I'm looking for a way to load the JSON asynchronously, but I'm having trouble getting my code to work with examples I have found because of the complexity of my code. What do I have to do to make my JSON load asynchronously? Thanks.
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *upcomingReleaseURL = [NSURL URLWithString:#"http://obscure-lake-7450.herokuapp.com/upcoming.json"];
NSData *jsonData = [NSData dataWithContentsOfURL:upcomingReleaseURL];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
NSArray *upcomingReleasesArray = [dataDictionary objectForKey:#"upcoming_releases"];
//This is the dateFormatter we'll need to parse the release dates
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
NSTimeZone *est = [NSTimeZone timeZoneWithAbbreviation:#"EST"];
[dateFormatter setTimeZone:est];
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:#"en_US"]]; //A bit of an overkill to avoid bugs on different locales
//Temp array where we'll store the unsorted bucket dates
NSMutableArray *unsortedReleaseWeek = [[NSMutableArray alloc] init];
NSMutableDictionary *tmpDict = [[NSMutableDictionary alloc] init];
for (NSDictionary *upcomingReleaseDictionary in upcomingReleasesArray) {
//We find the release date from the string
NSDate *releaseDate = [dateFormatter dateFromString:[upcomingReleaseDictionary objectForKey:#"release_date"]];
//We create a new date that ignores everything that is not the actual day (ignoring stuff like the time of the day)
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components =
[gregorian components:(NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit) fromDate:releaseDate];
//This will represent our releases "bucket"
NSDate *bucket = [gregorian dateFromComponents:components];
//We get the existing objects in the bucket and update it with the latest addition
NSMutableArray *releasesInBucket = [tmpDict objectForKey:bucket];
if (!releasesInBucket){
releasesInBucket = [NSMutableArray array];
[unsortedReleaseWeek addObject:bucket];
}
UpcomingRelease *upcomingRelease = [UpcomingRelease upcomingReleaseWithName:[upcomingReleaseDictionary objectForKey:#"release_name"]];
upcomingRelease.release_date = [upcomingReleaseDictionary objectForKey:#"release_date"];
upcomingRelease.release_price = [upcomingReleaseDictionary objectForKey:#"release_price"];
upcomingRelease.release_colorway = [upcomingReleaseDictionary objectForKey:#"release_colorway"];
upcomingRelease.release_date = [upcomingReleaseDictionary objectForKey:#"release_date"];
upcomingRelease.thumb = [upcomingReleaseDictionary valueForKeyPath:#"thumb"];
upcomingRelease.images = [upcomingReleaseDictionary objectForKey:#"images"];
[releasesInBucket addObject:upcomingRelease];
[tmpDict setObject:releasesInBucket forKey:bucket];
}
[unsortedReleaseWeek sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSDate* date1 = obj1;
NSDate* date2 = obj2;
//This will sort the dates in ascending order (earlier dates first)
return [date1 compare:date2];
//Use [date2 compare:date1] if you want an descending order
}];
self.releaseWeekDictionary = [NSDictionary dictionaryWithDictionary:tmpDict];
self.releaseWeek = [NSArray arrayWithArray:unsortedReleaseWeek];
}
One simple approach is to use NSURLConnection's convenient class method sendAsynchronousRequest:queue:error.
The following code snippet is an example how to load a JSON from a server, and where the completion handler executes on a background thread which parses the JSON. It also performs all recommended error checking:
NSURL* url = [NSURL URLWithString:#"http://example.com"];
NSMutableURLRequest* urlRequest = [NSMutableURLRequest requestWithURL:url];
[urlRequest addValue:#"application/json" forHTTPHeaderField:#"Accept"];
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest
queue:queue
completionHandler:^(NSURLResponse* response,
NSData* data,
NSError* error)
{
if (data) {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
// check status code and possibly MIME type (which shall start with "application/json"):
NSRange range = [response.MIMEType rangeOfString:#"application/json"];
if (httpResponse.statusCode == 200 /* OK */ && range.length != 0) {
NSError* error;
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (jsonObject) {
dispatch_async(dispatch_get_main_queue(), ^{
// self.model = jsonObject;
NSLog(#"jsonObject: %#", jsonObject);
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
//[self handleError:error];
NSLog(#"ERROR: %#", error);
});
}
}
else {
// status code indicates error, or didn't receive type of data requested
NSString* desc = [[NSString alloc] initWithFormat:#"HTTP Request failed with status code: %d (%#)",
(int)(httpResponse.statusCode),
[NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode]];
NSError* error = [NSError errorWithDomain:#"HTTP Request"
code:-1000
userInfo:#{NSLocalizedDescriptionKey: desc}];
dispatch_async(dispatch_get_main_queue(), ^{
//[self handleError:error]; // execute on main thread!
NSLog(#"ERROR: %#", error);
});
}
}
else {
// request failed - error contains info about the failure
dispatch_async(dispatch_get_main_queue(), ^{
//[self handleError:error]; // execute on main thread!
NSLog(#"ERROR: %#", error);
});
}
}];
Although, it appears somewhat elaborate, IMO this is a minimalistic and still naïve approach. Among other disadvantages, the main issues are:
it lacks the possibility to cancel the request, and
there is no way to handle more sophisticated authentication.
A more sophisticated approach needs to utilize NSURLConnection delegates. Usually, third party libraries do implement it in this manner, encapsulating the a NSURLConnection request and other relevant state info into a subclass of NSOperation. You may start with your own implementation, for example using this code as a template.
If you just want to get this only json data, you do not need to set up a lot of things.
use the code below. Create jsonParse method which gets a NSData Object.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *data = [[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:#"http://obscure-lake-7450.herokuapp.com/upcoming.json"]];
dispatch_sync(dispatch_get_main_queue(), ^{
[self jsonParse:data];
});
});
Download your data async as in this answer: Object-c/iOS :How to use ASynchronous to get a data from URL?
Then run it through the json parser.
To generically run code in a background thread you can use this method:
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Code here is run on a background thread
dispatch_async( dispatch_get_main_queue(), ^{
// Code here is run on the main thread (the UI thread) after your code above has completed so you can update UI after the JSON call has completed if you need to.
});
});
But remember that Apple does not allow you to update UI elements in a background thread. Also, they do not allow you to spawn more threads from a background thread, it must be done from the main thread.
NSString *urlstr=#"http://itunes.apple.com/in/rss/topsongs/limit=25/json";
NSMutableURLRequest *request=[[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:urlstr]];
[NSURLConnection sendAsynchronousRequest:request
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse* response,
NSData* data, NSError* error)
{
NSError *myError = nil;
NSDictionary *dic1 = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&myError];
if (myError ==nil) {
NSDictionary*feed =[dic1 objectForKey:#"feed"];
NSArray*arrayofentry =[feed objectForKey:#"entry"];
for(NSDictionary *dic2 in arrayofentry) {
requestReply=[dic2 objectForKey:#"title"];
[arr1 addObject:requestReply];
}
[self.table reloadData];
}
}];
Try this code:
NSURL * inkURL = [NSURL URLWithString:#"your url"];
NSURLRequest * request = [[NSURLRequest alloc]initWithURL:inkURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse * response, NSData * data, NSError * error) {
NSData * jsonData = [NSData dataWithContentsOfURL:inkURL];
NSDictionary * dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
self.inkArray = [dataDictionary objectForKey:#"users"];
}];

How to download file using asynchronous request?

Now, when my app detect that file was updated on server, it download file and user interface stuck for downloading time. I have ASIHTTPRequest wrapper in my app, but I doesn't know how to change my download request to asynchronous.
My code:
- (void)downloadFileIfUpdated
{
NSString *urlString = #"http://www.mysite.com/data.plist";
NSLog(#"Downloading HTTP header from: %#", urlString);
NSURL *url = [NSURL URLWithString:urlString];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachedPath = [[documentPaths lastObject] stringByAppendingPathComponent:#"data.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL downloadFromServer = NO;
NSString *lastModifiedString = nil;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"HEAD"];
NSHTTPURLResponse *response;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error: NULL];
if ([response respondsToSelector:#selector(allHeaderFields)]) {
lastModifiedString = [[response allHeaderFields] objectForKey:#"Last-Modified"];
}
NSDate *lastModifiedServer = nil;
#try {
NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.dateFormat = #"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
df.locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
df.timeZone = [NSTimeZone timeZoneWithAbbreviation:#"GMT"];
lastModifiedServer = [df dateFromString:lastModifiedString];
}
#catch (NSException * e) {
NSLog(#"Error parsing last modified date: %# - %#", lastModifiedString, [e description]);
}
NSLog(#"lastModifiedServer: %#", lastModifiedServer);
NSDate *lastModifiedLocal = nil;
if ([fileManager fileExistsAtPath:cachedPath]) {
NSError *error = nil;
NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:cachedPath error:&error];
if (error) {
NSLog(#"Error reading file attributes for: %# - %#", cachedPath, [error localizedDescription]);
}
lastModifiedLocal = [fileAttributes fileModificationDate];
NSLog(#"lastModifiedLocal : %#", lastModifiedLocal);
[activityIndicator stopAnimating];
}
// Download file from server if we don't have a local file
if (!lastModifiedLocal) {
downloadFromServer = YES;
}
// Download file from server if the server modified timestamp is later than the local modified timestamp
if ([lastModifiedLocal laterDate:lastModifiedServer] == lastModifiedServer) {
[activityIndicator startAnimating];
downloadFromServer = YES;
}
if (downloadFromServer) {
NSLog(#"Downloading new file from server");
NSData *data = [NSData dataWithContentsOfURL:url];
if (data) {
// Save the data
if ([data writeToFile:cachedPath atomically:YES]) {
NSLog(#"Downloaded file saved to: %#", cachedPath);
}
// Set the file modification date to the timestamp from the server
if (lastModifiedServer) {
NSDictionary *fileAttributes = [NSDictionary dictionaryWithObject:lastModifiedServer forKey:NSFileModificationDate];
NSError *error = nil;
if ([fileManager setAttributes:fileAttributes ofItemAtPath:cachedPath error:&error]) {
NSLog(#"File modification date updated");
[NSThread detachNewThreadSelector:#selector(loadPList) toTarget:self withObject:nil];
[activityIndicator stopAnimating];
}
if (error) {
NSLog(#"Error setting file attributes for: %# - %#", cachedPath, [error localizedDescription]);
}
}
}
}
}
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
}];
[request setFailedBlock:^{
NSError *error = [request error];
}];
[request startAsynchronous];
For more details look at http://allseeing-i.com/ASIHTTPRequest/How-to-use#using_blocks

Resources