I am doing my functionality in a secondary thread and once I get the result, I call the function that pops my ViewController in the main thread. But I get the following error:
void WebThreadLockFromAnyThread(), 0x5c6dec0: Obtaining the web lock from a thread other than the main thread or the web thread. UIKit should not be called from a secondary thread..
I am using the code below:
-(IBAction)done{
if([self validateRegistrationDetails]){
[NSThread detachNewThreadSelector:#selector(invokeWebService) toTarget:self withObject:nil];
}
}
-(void) invokeWebService{
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc]init];
NSString *url = [NSString stringWithFormat:#"%#%#action=1&userName=%#&eMail=%#&firstName=%#&lastName=%#&mobileNo=%#",kBaseURL,kRegisterFunction,userName.text,eMail.text,firstName.text,lastName.text,mobileNo.text];
[ADCUtilities performSelectorOnMainThread:#selector(updateText:) withObject:#"Registering... "waitUntilDone:NO];
[ADCUtilities performSelectorOnMainThread:#selector(showIndicator:) withObject:self.view waitUntilDone:NO];
NSDictionary *tempDict = [webService makeAPICall:url];
[NSThread sleepForTimeInterval:3];
if(tempDict!=nil){
NSString *tempLoginSuccess = [tempDict valueForKey:kLoginStatus] ;
if([tempLoginSuccess isEqual:#"LoginSuccess"]){
[ADCUtilities displayAlertView:NSLocalizedString(#"REG_SUCCESS",#"")];
[self performSelectorOnMainThread:#selector(popViewController) withObject:nil waitUntilDone:NO];
} else {
[ADCUtilities performSelectorOnMainThread:#selector( dismissIndicator) withObject:nil waitUntilDone:NO];
[ADCUtilities displayAlertView:NSLocalizedString(#"REG_FAILED",#"")];
}
} else {
[ADCUtilities performSelectorOnMainThread:#selector( dismissIndicator) withObject:nil waitUntilDone:NO];
[ADCUtilities displayAlertView:NSLocalizedString(#"REG_FAILED",#"")];
}
[pool release];
}
-(void)popViewController{
[self.navigationController popViewControllerAnimated:YES];
}
I think your problem is actually [ADCUtilities displayAlertView:NSLocalizedString(#"REG_SUCCESS",#"")], which I assume displays some sort of UIAlertView. You should never access any UIKit classes except from the main thread.
Related
I am working on Instagram integration in IOS.Every thing goes well.I am geting the feeds of the user and displaying them on tableview and also in scroll view.Here the user is allowed to refresh the page.While refreshing the method is not getting called and its getting crashed because of zero objects in array.I had used the following code to call the method.
-(IBAction)loginAction:(id)sender
{
AppDelegate* appDelegate_new = (AppDelegate*)[UIApplication sharedApplication].delegate;
[appDelegate_new.instagram authorize:[NSArray arrayWithObjects:#"comments", #"likes", nil]];
if ([appDelegate.instagram isSessionValid]) {
// NSLog(#"ViewDidLoad Session Valid");
loginView.hidden=YES;
crossButton.hidden=YES;
settingsButton.hidden=NO;
noticeView.hidden=YES;
[self.view addSubview:feedsView];
// [self.logOutView removeFromSuperview];
self.feedsView.frame=CGRectMake(0, 0, 240, 300);
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:#"users/self/feed", #"method", nil];
[appDelegate.instagram requestWithParams:params
delegate:self];
}
}
The called method was like this
- (void)request:(IGRequest *)request didLoad:(id)result
{
[self performSelector:#selector(startspinner) withObject:nil afterDelay:0.1];
self.data = (NSMutableArray *)[result objectForKey:#"data"];
// NSLog(#"Data Count is %#",[self.data description]);
createdArray=[[NSMutableArray alloc]init];
//*****Here I am performing Json Parsing******//
}
I am calling the above request method again while refreshing
- (void)dropViewDidBeginRefreshing:(ODRefreshControl *)refreshControl
{
[createdArray removeAllObjects];
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:#"users/self/feed", #"method", nil];
[appDelegate.instagram requestWithParams:params
delegate:self];
[self performSelector:#selector(refreshData) withObject:nil afterDelay:5.0];
}
Please tell me where I am going wrong.Correction appreciated.Thanks in advance.
Try it....
[NSTimer scheduledTimerWithTimeInterval:0.5f
target:self
selector:#selector(showTime)
userInfo:NULL
repeats:YES];
- (void)showTime
{
NSLog(#"Method called");
}
Use NSTimer for call method frequently.
Hope i helped.
I use a NSThread in order to download videos and images from a server side.It work looks and works great except the fact that when the downloading is done my GUI gets blocked until the download is complete.When the download is finished it takes a few seconds to work again.
this is how the server request is done:
- (void) repeatRequest{
NSLog(#"repeatRequest");
[NSThread detachNewThreadSelector:#selector(backgroundRequest) toTarget:self withObject:nil];
}
- (void) backgroundRequest{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURL *url = [NSURL URLWithString:myURLStr];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
[pool drain];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
//do things
}
IMPORTANTAnd I also tried to start the ASIHTTPRequest from the GUI thread but with the same behaviour.
Any idea about what could be wrong?
EDIT:
- (void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[[UIApplication sharedApplication] setStatusBarHidden:YES];
//internetReachable = [[Reachability reachabilityForInternetConnection] retain];
if(timer1 == nil)
{
timer1 = [NSTimer scheduledTimerWithTimeInterval:60.0 target:self selector: #selector(repeatRequest) userInfo: nil repeats: YES];
}
}
Try to run synchronous ASIHTTPRequest in your background thread, and handle results not in delegate method (requestFinished), but after [request startSynchronous];
I don't know anything about ASIHTTPRequest but i would assume its -startAsynchronous method already handles the background downloading for you. It all likelihood, it is returning immediately and your new thread is exiting. Also, you should just use [pool release] at the end of a thread method instead of [pool drain], it will be drained upon release, and you won't be leaking an NSAutoReleasePool. Does ASIHTTPRequest have a -startSynchronous (or just plain -start) method? Try using that within -backgroundRequest, as it should block the premature exit of that thread.
I can't seem to find any info on this question, so I thought I'd ask the community.
Basically, I have a UITableView and I want to show an activity indicator while its data is loading from my server.
Here is some example code of what I'm trying to do (I'm using ASIHttpRequest).
//self.listData = [[NSArray alloc] initWithObjects:#"Red", #"Green", #"Blue", #"Indigo", #"Violet", nil]; //this works
NSString *urlStr=[[NSString alloc] initWithFormat:#"http://www.google.com"]; //some slow request
NSURL *url=[NSURL URLWithString:urlStr];
__block ASIHTTPRequest *request=[ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setCompletionBlock:^{
self.listData = [[NSArray alloc] initWithObjects:#"Red", #"Green", #"Blue", #"Indigo", #"Violet", nil]; //this doesn't work...
[table reloadData];
}];
[request setFailedBlock:^{
}];
[request startAsynchronous];
The dummy request to google.com does nothing - it just creates a delay and in the response I hope to repopulate the table with some JSON response from my own website.
But when I try to populate the table with the colours, nothing happens! I just get a blank table... If I uncomment the line above, it works fine, it's just on http responses things don't work for me.
Any suggestions greatly appreciated.
Edit:
I did a [self.tableView reloadData]; and now it works...
Stop using ASIHTTPRequest. NSURLConnection is not hard to use and will result in better, more performant code.
Your JSON response should be fed into a data structure not the UI. I recommend Core Data.
The data structure should feed your UITableView. Again, I recommend Core Data.
I would suggest reviewing how MVC works, you are short circuiting the design and that is the core problem.
SPOILER
Here is a more detailed how to. First you want the data retrieval to be async. Easiest and most reusable way to do that is build a simple NSOperation subclass.
#class CIMGFSimpleDownloadOperation;
#protocol CIMGFSimpleDownloadDelegate <NSObject>
- (void)operation:(CIMGFSimpleDownloadOperation*)operation didCompleteWithData:(NSData*)data;
- (void)operation:(CIMGFSimpleDownloadOperation*)operation didFailWithError:(NSError*)error;
#end
#interface CIMGFSimpleDownloadOperation : NSOperation
#property (nonatomic, assign) NSInteger statusCode;
- (id)initWithURLRequest:(NSURLRequest*)request andDelegate:(id<CIMGFSimpleDownloadDelegate>)delegate;
#end
This subclass is the most basic way to download something from a URL. Construct it with a NSURLRequest and a delegate. It will call back on a success or failure. The implementation is only slightly longer.
#import "CIMGFSimpleDownloadOperation.h"
#interface CIMGFSimpleDownloadOperation()
#property (nonatomic, retain) NSURLRequest *request;
#property (nonatomic, retain) NSMutableData *data;
#property (nonatomic, assign) id<CIMGFSimpleDownloadDelegate> delegate;
#end
#implementation CIMGFSimpleDownloadOperation
- (id)initWithURLRequest:(NSURLRequest*)request andDelegate:(id<CIMGFSimpleDownloadDelegate>)delegate
{
if (!(self = [super init])) return nil;
[self setDelegate:delegate];
[self setRequest:request];
return self;
}
- (void)dealloc
{
[self setDelegate:nil];
[self setRequest:nil];
[self setData:nil];
[super dealloc];
}
- (void)main
{
[NSURLConnection connectionWithRequest:[self request] delegate:self];
CFRunLoopRun();
}
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSHTTPURLResponse*)resp
{
[self setStatusCode:[resp statusCode]];
[self setData:[NSMutableData data]];
}
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)newData
{
[[self data] appendData:newData];
}
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
[[self delegate] operation:self didCompleteWithData:[self data]];
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
{
[[self delegate] operation:self didFailWithError:error];
CFRunLoopStop(CFRunLoopGetCurrent());
}
#synthesize delegate;
#synthesize request;
#synthesize data;
#synthesize statusCode;
#end
Now this class is VERY reusable. There are other delegate methods for NSURLConnection that you can add depending on your needs. NSURLConnection can handle redirects, authentication, etc. I strongly suggest you look into its documentation.
From here you can either spin off the CIMGFSimpleDownloadOperation from your UITableViewController or from another part of your application. For this demonstration we will do it in the UITableViewController. Depending on your application needs you can kick off the data download wherever makes sense. For this example we will kick it off when the view appears.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSURLRequest *request = ...;
CIMGFSimpleDownloadOperation *op = [[CIMGFSimpleDownloadOperation alloc] initWithURLRequest:request andDelegate:self];
[[NSOperationQueue mainQueue] addOperation:op];
[self setDownloadOperation:op]; //Hold onto a reference in case we want to cancel it
[op release], op = nil;
}
Now when the view appears an async call will go and download the content of the URL. In this code that will either pass or fail. The failure first:
- (void)operation:(CIMGFSimpleDownloadOperation*)operation didFailWithError:(NSError*)error;
{
[self setDownloadOperation:nil];
NSLog(#"Failure to download: %#\n%#", [error localizedDescription], [error userInfo]);
}
On success we need to parse the data that came back.
- (void)operation:(CIMGFSimpleDownloadOperation*)operation didCompleteWithData:(NSData*)data;
{
[self setDownloadOperation:nil];
NSLog(#"Download complete");
//1. Massage the data into whatever we want, Core Data, an array, whatever
//2. Update the UITableViewDataSource with the new data
//Note: We MIGHT be on a background thread here.
if ([NSThread isMainThread]) {
[[self tableView] reloadData];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[[self tableView] reloadData];
});
}
}
And done. A few more lines of code for you to write but it replaces 13K+ lines of code that gets imported with ASI resulting in a smaller, leaner, faster application. And more importantly it is an app that you understand every single line of code.
This is the problem
request setCompletionBlock:^{
self.listData = [[NSArray alloc] initWithObjects:#"Red", #"Green", #"Blue", #"Indigo", #"Violet", nil]; //this doesn't work...
[table performSelectorOnMainThread:#selector(reloadTable) withObject:nil waitUntilDone:NO];
}];
The reload table needs to be done on the main thread.
I tried NWCoder's solution, and it didn't work because its calling the wrong method. This is what i used.
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
I am developing an iphone app in which I am extracting RSS feeds and then parsing them.My code is as bellow:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"in view did appear");
if ([stories count] == 0) {
NSString * path = #"http://www.shire.com/shireplc/rss.jsp";
//[self parseXMLFileAtURL:path];
//[self performSelectorInBackground:#selector(parseXMLFileAtURL:) withObject:path];
NSLog(#"internet is %d",[self checkInternet]);
if([self checkInternet]==1)
[NSThread detachNewThreadSelector:#selector(parseXMLFileAtURL:)
toTarget:self withObject:path];
}
}
- (void)parseXMLFileAtURL:(NSString *)URL
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
stories = [[NSMutableArray alloc] init];
//you must then convert the path to a proper NSURL or it won't work
NSURL *xmlURL = [NSURL URLWithString:URL];
// here, for some reason you have to use NSClassFromString when trying to alloc NSXMLParser, otherwise you will get an object not found error
// this may be necessary only for the toolchain
rssParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
// Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks.
[rssParser setDelegate:self];
// Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
[rssParser setShouldProcessNamespaces:NO];
[rssParser setShouldReportNamespacePrefixes:NO];
[rssParser setShouldResolveExternalEntities:NO];
[rssParser parse];
[pool release];
}
Can anyone tell me where I am going wrong?
My log is as bellow:
log: [Switching to thread 12803]
[Switching to thread 12035]
2011-05-10 11:31:30.932 Annual Report[454:490b] found file and started parsing
[Switching to thread 14339]
2011-05-10 11:32:04.742 Annual Report[454:640b] found file and started parsing
[Switching to thread 13827]
[Switching to thread 13827]
Program received signal: “EXC_BAD_ACCESS”.
gdb stack trace at 'putpkt: write failed':
0 gdb-arm-apple-darwin 0x0019026b remote_backtrace_self + 54
I am not sure but i guess the parameter for the method gets released at some point. Can you make sure that the URL is present in the method parseXMLFileAtURL
Finally,I used a flag to check if the view is appeared or not(by making flag true in viewdidAppear) and if the view is not appearing,don't run the thread function.That solved the problem!!!
// you can use the method, which is safer if your have not to update the UserInterface
[self performSelectorInBackground:#selector(parseXMLFileAtURL:) withObject:path];
if UserInterface needs also to be updated you can first parse data in background and the update the ui with method.
[self performSelectorOnMainThread:#selector(myUpdateUI) withObject:nil waitUntilDone:YES];
i am working on a map application, in that i like to drop the pins (as in Zillow apps) when ever user change the map view. I am using following code code. i am try to load the xml data from server using NSAutoreleasepool to do the xml parsing in the background thread.
(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated{
NSLog(#"inside region did changed ");
urlString =[NSString stringWithFormat: #"http://asdfasdasdf.com/asdfasdf/mapxml.php];
[stories1 release];
[mapview removeAnnotations:eventPoints1];
eventPoints1 = [[NSMutableArray array] retain];
[self performSelectorInBackground:#selector(callParsing) withObject:nil];
}
-(void)callParsing{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self parseXMLFileAtURL:urlString];
[self performSelectorOnMainThread:#selector(droppingPin) withObject:nil waitUntilDone:YES];
[pool drain];
}
The above code is working fine, but once i changed the mapview, the appllication get crashed. Anyone can help me to fix the issue?
thanks in advance.
urlString is already autoreleased when it is returned from stringWithFormat.
Since you are using urlString in callParsing which is executed on a different thread, you should pass it as an object to that method. Otherwise you risk it getting released before the callParsing method is executed and thus causing the crash:
...
[self performSelectorInBackground:#selector(callParsing:) withObject:urlString];
...
-(void)callParsing:(NSString*)urlString {
...