ios async crash - ios

NSString *url = #"http://localhost/xml";
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
[NSURLConnection sendAsynchronousRequest:req queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
NSDictionary *xmlDictionary = [XMLReader dictionaryForXMLData:data error:&error];
int count = [[[xmlDictionary objectForKey:#"ArrayOfMessage"] objectForKey:#"Message"] count];
if(true && count > 5) count = 5;
UIScrollView *sv = [[UIScrollView alloc] initWithFrame:CGRectMake(58, 0, MESSAGE_PAGING_WIDTH, 493)];
[sv setBounces:YES];
[sv setClipsToBounds:NO];
[sv setPagingEnabled:YES];
[sv setShowsHorizontalScrollIndicator:FALSE];
[sv setContentSize:CGSizeMake(count * MESSAGE_PAGING_WIDTH, frame.size.height)];
int i = 0;
while (i < count) {
NSDictionary *data = [[[xmlDictionary objectForKey:#"ArrayOfMessage"] objectForKey:#"Message"] objectAtIndex:i];
[sv addSubview:[[MessageView alloc] initWithFrame:CGRectMake(i * MESSAGE_PAGING_WIDTH, 0, 838, 493) data:data]];
++i;
}
[self addSubview:sv];
}];
I get a crash message of:
bool _WebTryThreadLock(bool), 0x7d7d080: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
How can I remedy?

You're creating UIKit elements on a different thread than the main thread:
This may be a result of calling to UIKit from a secondary thread.
sendAsynchronousRequest:queue:completionHandler: performs on a background thread. Inside your block you need to use performSelectorOnMainThread:withObject:waitUntilDone: for UIKit objects.
See http://blog.jayway.com/2010/03/30/performing-any-selector-on-the-main-thread/

Related

iOS: Unrecognized selector sent to instance

I have a class where I request information from a provider class, in which after finalizing the job (asynchronous httpRequest block) needs to invoke a method [- (void) updateCountries] in the requester class. If I am not wrong this code worked in iOS 7, but now in iOS 8 it does not.
Can you please help me to understand why?
Methods in requester class:
- (void) viewWillAppear:(BOOL)animated {
//get countries to pickerView
webAPI = [[WebAPI alloc] init];
[webAPI retrieveCountries:self];
}
- (void) updateCountries {
//update countries content for pickerView
locationDAO = [[LocationDAO alloc] init];
countriesArray = [locationDAO getCountries];
[pickerView reloadAllComponents];
}
Lines in method in provider class where error happens:
SEL updateCountries = sel_registerName("updateCountries:");
[requester performSelectorOnMainThread:updateCountries withObject:nil waitUntilDone:YES];
If you need to checkout the entire method in the provider class, here it is:
- (void) retrieveCountries:(id)requester {
// NSLog(#"engine report: firing retrieveCountries http get");
NSString *urlAsString = kRetrieveCountriesListAPI;
NSURL *url = [NSURL URLWithString:urlAsString];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
[urlRequest setTimeoutInterval:30.0f];
[urlRequest setHTTPMethod:#"GET"];
[urlRequest setValue:#"application/json" forHTTPHeaderField:#"Content-type"];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ([data length] >0 && error == nil){
NSString *response = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"engine report: retrieveCountries server response: %#", response);
NSArray *level0 = [[NSArray alloc] initWithObjects:[NSJSONSerialization JSONObjectWithData:[[NSData alloc] initWithData:data] options:kNilOptions error:&error], nil];
NSArray *level1 = [level0 objectAtIndex:0];
LocationDAO *locationDAO = [[LocationDAO alloc] init];
[locationDAO deleteAllFromCountries];
for (int i = 0; i < [level1 count]; i++) {
CountryVO *countryVO = [[CountryVO alloc] init];
countryVO.myID = [[[level1 objectAtIndex:i] objectForKey:#"id"] integerValue];
countryVO.name = [[level1 objectAtIndex:i] objectForKey:#"country_name"];
[locationDAO saveCountryToDatabase:countryVO];
}
SEL updateCountries = sel_registerName("updateCountries:");
[requester performSelectorOnMainThread:updateCountries withObject:nil waitUntilDone:YES];
dispatch_async(dispatch_get_main_queue(), ^(void){
});
} else if ([data length] == 0 && error == nil){
NSLog(#"Nothing was downloaded.");
} else if (error != nil) {
NSLog(#"Error happened = %#", error);
} }];
}
THANK YOU A WHOLE LOT
Remove the : from the selector specification:
SEL updateCountries = sel_registerName("updateCountries");
Your method updateCountries doesn't take any arguments. So, when creating the selector, you should only write updateCountries (instead of updateCountries: which would indicate that this method takes an argument).
The reason why your app crashes is that when you try to perform this selector, the internals of your app are looking for a method called updateCountries on requester that takes one argument. This method doesn't exist, which is why the app crashes.

How can i reload the table after download json

I have a problem when reloading the table after downloading the data in JSON format.
Use the NSOperation to download data async.
The code that i use it's this
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadInformactionToSql];
}
-(void)loadInformactionToSql {
NSOperationQueue * queue = [NSOperationQueue new];
NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(downloadJSONBDD) object:nil];
[queue addOperation:operation];
}
-(void)downloadJSONBDD {
NSURL * url = [NSURL URLWithString:#"http://judokatium.com/index.php/api/Belts/getBeltsWithTechnicals"];
//Leer el JSON
NSData * allCinturonesTecnicasJson =
[[NSData alloc] initWithContentsOfURL:url];
NSError *error;
NSArray * allCinturonesJson =
[NSJSONSerialization JSONObjectWithData:allCinturonesTecnicasJson options:kNilOptions error:&error];
if(error) {
NSLog(#"%#, %#", [error localizedDescription], [error localizedFailureReason]);
} else {
NSDictionary * cintns;
cinturones = [[NSMutableArray alloc] init];
for(int i = 0; i < [allCinturonesJson count]; i++){
JLMCinturon * cinturon = [[JLMCinturon alloc] init];
cintns = [allCinturonesJson objectAtIndex:i];
cinturon.idCinturon = [cintns objectForKey:#"id"];
[cinturones addObject:cinturon];
}
[self.tablaCinturones reloadData];
self.tablaCinturones.hidden = NO;
}
}
The downloaded data are correct, but not shown in the table.
How can i fix it?
Thanks and Sorry for my bad english.
Put these lines
[self.tablaCinturones reloadData];
self.tablaCinturones.hidden = NO;
into a dispatch block that moves them to the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self.tablaCinturones reloadData];
self.tablaCinturones.hidden = NO;
});
The problem is that NSOperation moves your method calls to a different thread, and the UI cannot be updated in iOS from any thread but the main one.
Or, you could use NSOperation as you already have and as #JulianKról pointed out.
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
NSInvocationOperation *reloadOperation = [[NSInvocationOperation alloc] initWithTarget:self.tablaCinturones selector:#selector(reloadData) object:nil];
NSInvocationOperation *hiddenOperation = [[NSInvocationOperation alloc] initWithTarget:self.tablaCinturones selector:#selector(setHidden:) object:#(NO)];
[mainQueue addOperation:reloadOperation];
[mainQueue addOperation:hiddenOperation];

UI hanging on background rss parsing

I'm trying to create a simple rss reader. The code works okay, except the UI hangs when the feeds are being updated. I thought I cobbled together the code to get the feed and parse it on a background queue while updating the UI on the mainQueue, but the table hangs pretty badly. Code below:
-(void)refreshFeed2
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (NSString *feed in _feeds) {
// iterate over all feeds
NSLog(#"feed=%#", feed);
NSURL *url = [NSURL URLWithString:feed];
// Create url connection and fire request
NSURLConnection *conn = [[NSURLConnection alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
(void)[conn initWithRequest:request delegate:self];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if ([data length] == 0 && error == nil) {
// handle empty response
} else if (error != nil) {
// handle error
NSLog(#"Error %#", [error localizedDescription]);
} else if ([httpResponse statusCode] == 200) {
// data present and no errors
[queue addOperationWithBlock:^{
// parse feed on queue
RXMLElement *rss = [RXMLElement elementFromXMLData:data];
RXMLElement *rssChild = [rss child:#"channel"];
RXMLElement* title = [rssChild child:#"title"];
NSArray* items = [[rss child:#"channel"] children:#"item"];
NSMutableArray* result=[NSMutableArray array];
for (RXMLElement *e in items) {
// iterate over the articles
RSSArticle* article = [[RSSArticle alloc] init];
article.sourceTitle = [title text];
article.articleTitle = [[e child:#"title"] text];
article.articleDescription = [[e child:#"description"] text];
article.articleUrl = [NSURL URLWithString: [[e child:#"link"] text]];
NSString *articleDateString = [[e child:#"pubDate"] text];
article.articleDate = [NSDate dateFromInternetDateTimeString:articleDateString formatHint:DateFormatHintRFC822];
if (article.articleUrl != NULL) {
[result addObject:article];
}
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// update table on mainQueue
for (RSSArticle *article in result) {
// iterate over articles
int insertIdx = [_allEntries indexForInsertingObject:article sortedUsingBlock:^(id a, id b) {
RSSArticle *entry1 = (RSSArticle *) a;
RSSArticle *entry2 = (RSSArticle *) b;
return [entry1.articleDate compare:entry2.articleDate];
}];
[_allEntries insertObject:article atIndex:insertIdx];
[self.LeftTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:insertIdx inSection:0]]
withRowAnimation:UITableViewRowAnimationFade];
}
}];
}];
}
}];
// Stop refresh control
[refreshControl endRefreshing];
}
}
Code that calls refreshFeed2:
- (void)viewDidLoad {
[super viewDidLoad];
self.allEntries = [NSMutableArray array];
self.feeds = [NSArray arrayWithObjects:
#"http://feeds.washingtonpost.com/rss/politics",
#"http://rss.cnn.com/rss/cnn_allpolitics.rss",
#"http://www.npr.org/rss/rss.php?id=1012",
#"http://www.slatedigital.com/support/feeds/rss_kb.php?s=fd5aa35e773dc3177b85a2126583f002",
nil];
}
//add refresh control to the table view
refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self
action:#selector(refreshInvoked:forState:)
forControlEvents:UIControlEventValueChanged];
NSString* fetchMessage = [NSString stringWithFormat:#"Fetching Articles"];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:fetchMessage
attributes:#{NSFontAttributeName:[UIFont fontWithName:#"Helvetica" size:11.0]}];
[self.LeftTableView addSubview: refreshControl];
[self refreshInvoked:self forState:UIControlStateNormal];
}
-(void) refreshInvoked:(id)sender forState:(UIControlState)state {
NSOperationQueue *refreshQueue = [[NSOperationQueue alloc] init];
[refreshQueue addOperationWithBlock:^{
[self refreshFeed2];
}];
}
Any help?
Thanks!
Can you try this? replace
[self refreshInvoked:self forState:UIControlStateNormal];
by
[self performSelectorOnBackground:#selector(refreshFeed2) withObject:nil];
and replace the same instead of
-(void) refreshInvoked:(id)sender forState:(UIControlState)state {
[self performSelectorOnBackground:#selector(refreshFeed2) withObject:nil ];
}

Does using blocks auto create new threads?

JUST started doing work with blocks... very confusing. I am using a block like this:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *myDictionary = [[mySingleton arrayPeopleAroundMe] objectAtIndex:indexPath.row];
NSMutableString *myString = [[NSMutableString alloc] initWithString:#"http://www.domain.com/4DACTION/PP_profileDetail/"];
[myString appendString:[myDictionary objectForKey:#"userID"]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:[myString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection
sendAsynchronousRequest:urlRequest
queue:queue
completionHandler: ^( NSURLResponse *response,
NSData *data,
NSError *error)
{
[[mySingleton dictionaryUserDetail] removeAllObjects];
[[mySingleton arrayUserDetail] removeAllObjects];
if ([data length] > 0 && error == nil) // no error and received data back
{
NSError* error;
NSDictionary *myDic = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
[mySingleton setDictionaryUserDetail:[myDic mutableCopy]];
NSArray *myArray = [myDic objectForKey:#"searchResults"];
[mySingleton setArrayUserDetail:[myArray mutableCopy]];
[self userDetailComplete];
} else if
([data length] == 0 && error == nil) // no error and did not receive data back
{
[self serverError];
} else if
(error != nil) // error
{
[self serverError];
}
}];
}
Once the connection is completed, this is called:
-(void)userDetailComplete {
ViewProfile *vpVC = [[ViewProfile alloc] init];
[vpVC setPassedInstructions:#"ViewDetail"];
[[self navigationController] pushViewController:vpVC animated:YES];
}
which caused this error to pop up:
"Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread."
The only way I got rid of the error was by changing userDetailComplete to this:
-(void)userDetailComplete {
dispatch_async(dispatch_get_main_queue(), ^{
ViewProfile *vpVC = [[ViewProfile alloc] init];
[vpVC setPassedInstructions:#"ViewDetail"];
[[self navigationController] pushViewController:vpVC animated:YES];
});
}
My question: is a new thread started automatically every time a block is used? Are there any other pitfalls I should aware of when using blocks?
Blocks do not create threads. They are closures; they just contain runnable code that can be run at some future point.
This is running on a background thread because that's what you asked it to do:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection
sendAsynchronousRequest:urlRequest
queue:queue
...
You created a new queue and then asked NSURLConnection to call you back on that queue. If you want to be called back on the main thread, pass [NSOperationQueue mainQueue]. That's usually waht you want.

Subviews in dispatch_async appears after a long delay

I've used dispatch_async to put in background a xml document's parsing, I've putted information in an array and, with a for cycle, I would assign the content of every element to an UILabel (for now), the problem is that in the output console I can see the right content of every element but the uilabel are added only in the end after a long delay.
Code:
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(kBgQueue, ^{
NSData *xmlData = [[NSMutableData alloc] initWithContentsOfURL:url];
NSError *error;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];
NSArray *areaCortina = [doc nodesForXPath:#"query" error:nil];
int i=0;
for (GDataXMLElement *element in areaCortina) {
NSLog(#"%#",[[element attributeForName:#"LiftName"] stringValue]); //data are shown correctly
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, (30+10)*i, 200, 30)];// don't appear after log
[label setText:[[element attributeForName:#"name"] stringValue]];
[self.view performSelectorOnMainThread:#selector(addSubview:) withObject:label waitUntilDone:YES];
i++;
}
As you can see i've used performSelectorOnMainThread but nothig, the label doesn't appear once at once but are shown correctly only after a 10 or 15 seconds after the end of the block.
Ideas?
Thanks in advance
Ok edit 1
Thanks to the advice of Shimanski Artem I've now the following:
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
...
dispatch_async(kBgQueue, ^{
NSData *xmlData = [[NSMutableData alloc] initWithContentsOfURL:url];
NSError *error;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];
NSArray *areaCortina = [doc nodesForXPath:#"query" error:nil];
self.data = [[NSMutableArray alloc] init];
for (GDataXMLElement *element in areaCortina) {
[self.data addObject:[[element attributeForName:#"name"] stringValue]];
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"name" object:self];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(caricaItem) name:#"name" object:nil];
...
} //end method
-(void) caricaItem
{
int i=0;
for (NSString * string in self.dati) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, (30+10)*i, 200, 30)];
[label setText:string];
[self.view addSubview:label];
i++;
}
}
I've put uilabels creation out of dispatch, in the caricaItem method where i've ready an array full of precious data, but same delay... and the same if in caricaItem i use a UITableView...
What is the right way to proceed?
Thanks

Resources