iOS - App Freeze When I Fetch Request Deadlock - ios

Hi good people I'm trying to prevent the freezing with
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{ CODE });
but I don't know how to use function.. I don't know where to put the managedObjectContext and how to use this dispatch_async my code is:
- (void)updateFacebookFriendsHighScore{
NSFetchRequest *requestche =[NSFetchRequest fetchRequestWithEntityName:#"Time"];
[requestche setReturnsObjectsAsFaults:NO];
NSPredicate *predicate=[NSPredicate predicateWithFormat:#"timeid==1"];
requestche.predicate=predicate;
NSArray *getIDTime = [self.managedObjectContext executeFetchRequest:requestche error:nil];
NSString *getTheTime = [[getIDTime valueForKey:#"time"] componentsJoinedByString:#""];
NSNumber *timeInInt = [NSNumber numberWithInteger: [getTheTime intValue]];
int timeFromDB = [timeInInt intValue];
timeFromDB = timeFromDB + 509;
int timeNow = [[NSDate date] timeIntervalSince1970];
if(timeNow > timeFromDB){
NSFetchRequest *updateHighScoreRequest = [NSFetchRequest fetchRequestWithEntityName:#"Friends"];
[updateHighScoreRequest setReturnsObjectsAsFaults:NO];
NSArray *friendsToUpdate = [self.managedObjectContext executeFetchRequest:updateHighScoreRequest error:nil];
for(NSArray *friendId in friendsToUpdate){
NSString *getFriendId = [friendId valueForKey:#"fbid"] ;
NSString *siteURL = [NSString stringWithFormat:#"http://www.example.com/example.php?fbid=%#", getFriendId];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:siteURL]];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSString *resultsFromDB = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSNumber *theScoreForUpdate = [NSNumber numberWithInt:[resultsFromDB intValue]];
NSFetchRequest *updateTheHighScoreRequest = [NSFetchRequest fetchRequestWithEntityName:#"Friends"];
NSPredicate *updateTheHighScorePredicate = [NSPredicate predicateWithFormat:#"fbid==%#",getFriendId];
updateTheHighScoreRequest.predicate=updateTheHighScorePredicate;
Friends *setScore = [[self.managedObjectContext executeFetchRequest:updateTheHighScoreRequest error:nil] lastObject];
NSLog(#"%#", setScore);
[setScore setValue:theScoreForUpdate forKey:#"score"];
[self.managedObjectContext save:nil];
data = nil;
resultsFromDB = nil;
theScoreForUpdate = nil;
setScore = nil;
}];
updateHighScoreRequest = nil;
}
}
requestche = nil;
}
This code gets the time from database and update the highscore after 509 seconds from the CD result and when I run this request my app freeze ( DEADLOCK ).
I am from Bulgaria and I'm trying to learn Objective C. Here we don't have schools for this our country is very bad in all instance and Bulgaria is last in Europe Union...
Can some serious and good person help me with my code or explane how works everything in Objective C or only help me with this ?

Try this code. To keep the application as simple as possible, never take the Core data code out of the main thread i.e. any thing related to self.managedObjectContext such as save or executing fetch requests. It is because Core data is not thread safe and you will have to device a strategy to handle that. I am assuming that your application is straight forward and you don't need such a strategy. So, please try to keep it as simple as possible and always perform the core data operations (save, execute) on main thread. dispatch_async(dispatch_get_main_queue(), ^{ code }); will execute it on main thread.
-(void) updateFacebookFriendsHighScore
{
dispatch_async(dispatch_get_main_queue(), ^{
NSFetchRequest *requestche =[NSFetchRequest fetchRequestWithEntityName:#"Time"];
[requestche setReturnsObjectsAsFaults:NO];
NSPredicate *predicate=[NSPredicate predicateWithFormat:#"timeid==1"];
requestche.predicate=predicate;
NSArray *getIDTime = [self.managedObjectContext executeFetchRequest:requestche error:nil];
NSString *getTheTime = [[getIDTime valueForKey:#"time"] componentsJoinedByString:#""];
NSNumber *timeInInt = [NSNumber numberWithInteger: [getTheTime intValue]];
int timeFromDB = [timeInInt intValue];
timeFromDB = timeFromDB + 509;
int timeNow = [[NSDate date] timeIntervalSince1970];
if(timeNow > timeFromDB){
NSFetchRequest *updateHighScoreRequest = [NSFetchRequest fetchRequestWithEntityName:#"Friends"];
[updateHighScoreRequest setReturnsObjectsAsFaults:NO];
NSArray *friendsToUpdate = [self.managedObjectContext executeFetchRequest:updateHighScoreRequest error:nil];
for(NSArray *friendId in friendsToUpdate){
NSString *getFriendId = [friendId valueForKey:#"fbid"] ;
NSString *siteURL = [NSString stringWithFormat:#"http://www.example.com/example.php?fbid=%#", getFriendId];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:siteURL]];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSString *resultsFromDB = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSNumber *theScoreForUpdate = [NSNumber numberWithInt:[resultsFromDB intValue]];
NSFetchRequest *updateTheHighScoreRequest = [NSFetchRequest fetchRequestWithEntityName:#"Friends"];
NSPredicate *updateTheHighScorePredicate = [NSPredicate predicateWithFormat:#"fbid==%#",getFriendId];
updateTheHighScoreRequest.predicate=updateTheHighScorePredicate;
Friends *setScore = [[self.managedObjectContext executeFetchRequest:updateTheHighScoreRequest error:nil] lastObject];
NSLog(#"%#", setScore);
[setScore setValue:theScoreForUpdate forKey:#"score"];
[self.managedObjectContext save:nil];
data = nil;
resultsFromDB = nil;
theScoreForUpdate = nil;
setScore = nil;
}];
updateHighScoreRequest = nil;
}
}
requestche = nil;
});
}

Related

Return NSMutableArray from completionHandler (Objective C)

I did post request to a web service and get response. I convert the response to NSMutableArray. My response in NSURLSessionDataTask and now I want to return NSMutableArray for using outside of NSURLSessionDataTask. Here is my code:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"url"]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
NSString *postString = #"params";
NSString *postLength = [NSString stringWithFormat:#"%lu", ( unsigned long )[postString length]];
[request setValue:postLength forHTTPHeaderField:#"Content-Length" ];
[request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *task = [[self getURLSession] dataTaskWithRequest:request completionHandler:^( NSData *data, NSURLResponse *response, NSError *error )
{
dispatch_async( dispatch_get_main_queue(),
^{
NSDictionary *dicData = [NSJSONSerialization
JSONObjectWithData:data
options:NSJSONReadingAllowFragments
error:nil];
NSDictionary *values = [dicData valueForKeyPath:#"smth"];
NSArray * dataArr = [dicData objectForKey:#"smth"];
NSArray * closeArr = [values objectForKey:#"0"];
NSUInteger dataCount = [dataArr count] ;
NSUInteger closeCount = [closeArr count] ;
NSMutableArray * newData = [NSMutableArray new] ; //<-- THIS ARRAY
for(int i = 0 ; i<dataCount && i<closeCount ; i++)
{
NSMutableDictionary * temp = [NSMutableDictionary new] ;
NSString * dataString = [dataArr objectAtIndex:i];
NSString * closeString = [closeArr objectAtIndex:i];
[temp setObject:dataString forKey:#"smth"];
[temp setObject:closeString forKey:#"smth"];
[newData addObject:temp];
}
NSLog(#"%#", newData);
} );
}];
[task resume];
I need return NSMutableArray * newData = [NSMutableArray new];
Long story short, I get json data from web service, then transform it to appropriate json format for displaying it in the chart(I use shinobicontrols). Now, I display chart with the help of local json. Here is the code:
_timeSeries = [NSMutableArray new];
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"AppleStockPrices" ofType:#"json"];
NSData* json = [NSData dataWithContentsOfFile:filePath];
NSArray* data = [NSJSONSerialization JSONObjectWithData:json
options:NSJSONReadingAllowFragments
error:nil];
for (NSDictionary* jsonPoint in data) {
SChartDataPoint* datapoint = [self dataPointForDate:jsonPoint[#"smth1"]
andValue:jsonPoint[#"smth2"]];
[_timeSeries addObject:datapoint];
}
When I am trying to implement this code in NSURLSessionDataTask, the chart doesn't appear. So I need return NSMutableArray(where my data in appropriate json format) outside.
How can I do this? Any ideas?
Thank you!
You cannot add a return statement in the completion handler since it may not be called if the session return an error. As a matter of fact, Xcode will give you an "Incompatible pointer type" error if you try to do that.
The best way I found to go around it is to set up your newData array as a property and make it available to the other methods in the class. If a specific method will need to handle this array when the url session task is over, you can call that method from the completion handler or use a notification.
Alternatively, if for some reason you do not want to user a class property, you can use the NSNotificationCenter, and pass the newData to the listener in the notification object.
EDIT: code example using a property
If you need the newData outside the completion block, an easy way is declaring the array as a property. This is not the only approach and probably not the best. But it doesn't add much complexity to the code.
You can declare the newData array in your .m class file:
#interface "whatever class you are using"
#property (nonatomic, strong) NSMutableArray *newData;
#end
Your can initialize the array in your viewDidLoad method:
- (void)viewDidLoad {
_newdata = [[NSMutableArray alloc] init];
}
In Your completion block, you would remove the initialization and add data to the array.
//NSMutableArray * newData = [NSMutableArray new] ; // REMOVE THE INTIALIZATION
for(int i = 0 ; i<dataCount && i<closeCount ; i++) {
NSMutableDictionary * temp = [NSMutableDictionary new] ;
NSString * dataString = [dataArr objectAtIndex:i];
NSString * closeString = [closeArr objectAtIndex:i];
[temp setObject:dataString forKey:#"smth"];
[temp setObject:closeString forKey:#"smth"];
[_newData addObject:temp];
}
Again, this is not the best approach but it is relatively simple. One problem with doing this, is that since you have a strong reference to the array, if you need to perform another URL call, and load new data into the array, you will need to empty it. Otherwise, the new data will be appended to the old one. You can do it by calling [_newData removeAllObjects]; before the URL session is called again.
EDIT 2: changed code based on the user comment:
- (void)loadChartData {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"url"]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
NSString *postString = #"params";
NSString *postLength = [NSString stringWithFormat:#"%lu", ( unsigned long )[postString length]];
[request setValue:postLength forHTTPHeaderField:#"Content-Length" ];
[request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *task = [[self getURLSession] dataTaskWithRequest:request completionHandler:^( NSData *data, NSURLResponse *response, NSError *error ) {
dispatch_async( dispatch_get_main_queue(), ^{
NSDictionary *dicData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
NSDictionary *values = [dicData valueForKeyPath:#"smth"];
NSArray * dataArr = [dicData objectForKey:#"smth"];
NSArray * closeArr = [values objectForKey:#"smth0"];
NSUInteger dataCount = [dataArr count] ;
NSUInteger closeCount = [closeArr count] ;
NSMutableArray * newData = [NSMutableArray new] ;
for(int i = 0 ; i<dataCount && i<closeCount ; i++) {
NSMutableDictionary * temp = [NSMutableDictionary new] ;
NSString * dataString = [dataArr objectAtIndex:i];
NSString * closeString = [closeArr objectAtIndex:i];
[temp setObject:dataString forKey:#"smth"];
[temp setObject:closeString forKey:#"smth"];
[newData addObject:temp];
}
NSLog(#"%#", newData);
_timeSeries = [NSMutableArray new];
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"AppleStockPrices" ofType:#"json"];
NSData* json = [NSData dataWithContentsOfFile:filePath];
NSArray* data = [NSJSONSerialization JSONObjectWithData:json options:NSJSONReadingAllowFragments error:nil];
for (NSDictionary* jsonPoint in data) {
SChartDataPoint* datapoint = [self dataPointForDate:jsonPoint[#"smth"] andValue:jsonPoint[#"smth"]];
[_timeSeries addObject:datapoint];
}
});
}];
[task resume];
// Code here has a good chance of being executed before the completion block is complete
// _newdata = [[NSMutableArray alloc] init];
// NSLog(#"%#", _newdata);
}

Load UILabel Asynchronously using Code Block

I'm stuck with an issue on how to best load a couple of UILabels asynchronously.
Here is my cellForRowAtIndexPath method:
UPDATE:
Based on the answer below, I've made changes:
Here is my new cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Retrieve cell
NSString *cellIdentifier = #"BuildingItem";
BuildingsTableViewCell *cell = [[tableView dequeueReusableCellWithIdentifier:cellIdentifier] initWithFrame:CGRectMake(10, 10,580, 100)];
// Get the area to be shown
Buildings *item = _feedItems[indexPath.row];
NSURL *MyURL = [NSURL URLWithString:item.buildingPointerImage];
UIImage *placeholder = [UIImage imageNamed:#"placeholder"];
NSString *path = [MyURL absoluteString];
NSString *key = [path MD5Hash];
[cell.buildingImageView loadImageFromURL:(NSURL*)MyURL placeholderImage:(UIImage*)placeholder cachingKey:(NSString*)key];
cell.buildingName.text = item.buildingPointerName;
cell.buildingYear.text = item.buildingPointerYear;
NSString *URL = #"http://rets.miamiresidential.com/ios/condos/buildings.php?action=get_range";
NSString *streetNumberURL = [NSString stringWithFormat:#"&street_number=%#",item.buildingStreetNumber];
NSString *streetNameURL = [NSString stringWithFormat:#"&street_name=%#",item.buildingStreetName];
NSString *ZipcodeURL = [NSString stringWithFormat:#"&zipcode=%#",item.buildingZipcode];
NSString *P1 = [URL stringByAppendingString:streetNumberURL];
NSString *P2 = [P1 stringByAppendingString:streetNameURL];
NSString *P3 = [P2 stringByAppendingString:ZipcodeURL];
NSURL *jsonFileUrl = [NSURL URLWithString:P3];
NSLog(#"%#",P3);
[cell.buildingSalesRange loadSalesRangeFromURL:(NSURL*)jsonFileUrl];
[cell.buildingRentalsRange loadRentalsRangeFromURL:(NSURL*)jsonFileUrl];
cell.layer.borderColor = [[UIColor whiteColor]CGColor];
cell.layer.backgroundColor = [[UIColor clearColor]CGColor];
cell.layer.borderWidth = 2;
return cell;
}
and here is my new .m file:
#import "PriceRanges.h"
#import <objc/runtime.h>
#implementation UILabel(Prices)
-(void) loadSalesRangeFromURL:(NSURL*)url {
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
NSNumberFormatter *currencyStyle = [[NSNumberFormatter alloc] init];
[currencyStyle setNumberStyle:NSNumberFormatterCurrencyStyle];
[currencyStyle setLocale:locale];
[currencyStyle setMaximumFractionDigits:0];
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:url];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (error)
{
NSLog(#"Error,%#", [error localizedDescription]);
}
else
{
NSArray *priceArray = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
NSNumber *number = [priceArray valueForKeyPath:#"sales.number"];
NSString *lowest = [priceArray valueForKeyPath:#"sales.lowest"];
NSDecimalNumber *lowestDecimal = [NSDecimalNumber decimalNumberWithString:lowest];
NSString *lowestPrice = [currencyStyle stringFromNumber:lowestDecimal];
if (![number isEqualToNumber:[NSNumber numberWithInt:0]]) {
dispatch_async(dispatch_get_main_queue(), ^{
UILabel *labelFromData = [[UILabel alloc] init];
[labelFromData setText:[NSString stringWithFormat:#"%# for Sale from %#",number,lowestPrice]];
if (labelFromData) {
if ([self.text isEqualToString:labelFromData.text]) {
} else {
dispatch_async(dispatch_get_main_queue(), ^{
self.text = labelFromData.text;
});
}
}
self.text = [NSString stringWithFormat:#"%# for Sale from %#",number,lowestPrice];
});
}
};
}];
}
-(void) loadRentalsRangeFromURL:(NSURL*)url {
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
NSNumberFormatter *currencyStyle = [[NSNumberFormatter alloc] init];
[currencyStyle setNumberStyle:NSNumberFormatterCurrencyStyle];
[currencyStyle setLocale:locale];
[currencyStyle setMaximumFractionDigits:0];
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:url];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (error)
{
NSLog(#"Error,%#", [error localizedDescription]);
}
else
{
NSArray *priceArray = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
NSNumber *number = [priceArray valueForKeyPath:#"rentals.number"];
NSString *lowest = [priceArray valueForKeyPath:#"rentals.lowest"];
NSDecimalNumber *lowestDecimal = [NSDecimalNumber decimalNumberWithString:lowest];
NSString *lowestPrice = [currencyStyle stringFromNumber:lowestDecimal];
if (![number isEqualToNumber:[NSNumber numberWithInt:0]]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setText:[NSString stringWithFormat:#"%# for Rent from %#",number,lowestPrice]];
});
}
}
}];
}
#end
As you might see, I have two different methods, but both still send the async request everytime the tables are scrolled.
I guess I am not clear on what to do now?
That's the problem with not having a data model independent of your UI, and a good example of when Model-View-Controller makes sense. You'll need another layer (preferably a separate class) responsible for keeping all the data you've fetched from the network, deciding when it's old enough to discard, etc. The cells should populate themselves from the cached data that the Model keeps, and if the data's not yet present, the model fetches it asynchronously and then notifies the View Controller when the new data arrive. If the cells are still visible, they get populated. If they've scrolled offscreen, been reused, etc, then the data updates don't result in any immediate UI change.

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);

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"];
}];

EXEC_BAD_INSTRUCTION at dispatch_get_current_queue()

I'm doing a project using core data, and as far as I know anything done in the core data thread has to remain in said thread.
I do a call to an API to download some news items and then load them into the database:
[self.database.managedObjectContext performBlock:^{
for (NSDictionary *itemInfo in result) {
NSLog(#"%#", itemInfo);
[Item createItemWithInfo:itemInfo inManagedObjectContext:self.database.managedObjectContext];
}
[self.database.managedObjectContext save:nil];
}];
In my create method, besides setting up all the data in the objects I have an additional call to get an image related to the news item in question:
+ (Item *)createItemWithInfo:(NSDictionary *)info inManagedObjectContext:(NSManagedObjectContext *)context {
Item *item;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Item"];
request.predicate = [NSPredicate predicateWithFormat:#"itemId = %#", [info valueForKeyPath:#"News.id_contennoticias"]];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"itemId" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *matches = [context executeFetchRequest:request error:&error];
if (!matches || ([matches count] > 1)) {
// handle error
} else if ([matches count] == 0) {
item = [NSEntityDescription insertNewObjectForEntityForName:#"Item" inManagedObjectContext:context];
item.itemId = [NSNumber numberWithInteger:[[info valueForKeyPath:#"News.id_contennoticias"] integerValue] ];
item.title = [info valueForKeyPath:#"News.titulo_contennoticias"];
item.summary = [info valueForKeyPath:#"News.sumario_contennoticias"];
item.content = [info valueForKeyPath:#"News.texto_contennoticias"];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
dateFormat.dateFormat = #"yyyy-MM-dd hh:mm:ss";
NSDate *creationDate = [dateFormat dateFromString:[info valueForKeyPath:#"News.fechacre_contennoticias"]];
item.creationDate = creationDate;
dispatch_queue_t imageDownloadQueue = dispatch_queue_create("image downloader", NULL);
dispatch_async(imageDownloadQueue, ^{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/files/imagenprincipal/%#", BASE_PATH, [info valueForKeyPath:#"News.imgprincipal"]]];
NSData *imageData = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_current_queue(), ^{
item.image = imageData;
});
});
} else {
item = [matches lastObject];
}
return item;
}
On this part:
dispatch_async(dispatch_get_current_queue(), ^{
item.image = imageData;
});
I'm getting the error, my app just dies there. Also it says that dispatch_get_current_queue() is deprecated in iOS 6.
Since dispatch_get_current_queue() is called from within a block on the imageDownloadQueue, why aren't you using imageDownloadQueue directly? If you want to run it on the moc queue, then make sure not to use dispatch_get_current_queue().
In general be careful with using dispatch_get_current_queue(). Quoting from the Dispatch Queues page in the Apple Concurrency Programming guide:
Use the dispatch_get_current_queue function for debugging purposes or
to test the identity of the current queue.

Resources