I have following error msg in console when using NSThread
"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..."
I have submit my sample code here
- (void)viewDidLoad {
appDeleg = (NewAshley_MedisonAppDelegate *)[[UIApplication sharedApplication] delegate];
[[self tblView1] setRowHeight:80.0];
[super viewDidLoad];
self.title = #"Under Ground";
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[NSThread detachNewThreadSelector:#selector(CallParser) toTarget:self withObject:nil];
}
-(void)CallParser {
Parsing *parsing = [[Parsing alloc] init];
[parsing DownloadAndParseUnderground];
[parsing release];
[self Update_View];
//[myIndicator stopAnimating];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
here "DownloadAndParseUnderground" is the method of downloding data from the rss feed and
-(void) Update_View{
[self.tblView1 reloadData];
}
when Update_View method is called the tableView reload Data and in the cellForRowAtIndexPath create error and not display custom cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
CustomTableviewCell *cell = (CustomTableviewCell *) [tblView1 dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"customCell"
owner:self
options:nil];
cell = objCustCell;
objCustCell = nil;
}
if there is a crash, there is a backtrace. Please post it.
Method names start with lowercase letters, are camelCased, and do not contain underscores. Following these conventions will make your code easier to read by other iOS programmers and learning these conventions will make it easier to understand other iOS programmer's code.
You can't directly or indirectly invoke methods in the main thread from background threads. Your crash and your code both indicate that you are freely interacting with the main thread form non-main threads.
The documentation on the use of threads in iOS applications is quite extensive.
Your problem should come because you load your UIViewController from a thread that's not the main thread. Tipically when you try to charge data before loading the view.
To arrange this you can try to do this
1. Add a method to load your viewcontroller with just one param
-(void)pushViewController:(UIViewController*)theViewController{
[self.navigationController pushViewController:theViewController animated:YES];}
2.Change your code (commented below) in your asynchronous loading to "PerformSelectorOnMainThread"
-(void)asyncLoadMyViewController
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
MyViewController *myVC = [[myVC alloc] initWithNibName:#"myVC" bundle:nil ];
[self performSelectorOnMainThread:#selector(pushViewController:) withObject:myVC waitUntilDone:YES];
// [self.navigationController pushViewController:wnVC animated:YES];
[wnVC release];
[pool release];
}
ok please explain proper Why you require thread in parsing method? in your code u use table reload method in properly in thread....
because
u cant put any thing which relavent to your VIEW in thread...
u can put only background process like parsing in it... if u want reload table after parsing u can use some flag value in your code and after parsing u load table
Try change CallParser method to
-(void)CallParser {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Parsing *parsing = [[Parsing alloc] init];
[parsing DownloadAndParseUnderground];
[self performSelectorOnMainThread:#selector(Update_View)
withObject:nil
waitUntilDone:NO];
[parsing release];
[pool release];
}
And move
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
line to Update_View method
You can't access any UI elements from a background thread. You certainly can't create views on a background thread. Use "performSelectorOnMainThread" method instead of "detachNewThreadSelector" method.
All the best.
Related
so I have a table view and whenever user press a row, another class view shows up. So I wanted to have a loading indicator in between the transition. I am using MBProgressHUD, but it showed nothing when I pressed the row. And what should I put inside the #selector()?
[loading showWhileExecuting:#selector() onTarget:self withObject:[NSNumber numberWithInt:i] animated:YES];
Here is my code.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
loading = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:loading];
loading.delegate = self;
loading.labelText = #"Loading Events, Please Wait..";
[loading showWhileExecuting:#selector(//what should I put) onTarget:self withObject:nil animated:YES];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if ([[self.citiesArray objectAtIndex:indexPath.row] isEqual:#"NEW YORK"])
{
self.newYorkViewController = [[NewYorkViewController alloc] initWithNibName:#"NewYorkViewController" bundle:nil];
Twangoo_AppAppDelegate *delegate = (Twangoo_AppAppDelegate*)[[UIApplication sharedApplication] delegate];
[delegate.citiesNavController pushViewController:self.newYorkViewController animated:YES];
}
}
You need to implement a waiting function so that when the control returns from that method the HUD will hide/disappear from the screen. So its basically what you do when the HUD is being displayed on the screen, it may be some processing or wait for response for a http request etc. It can also be a timer.
- (void)waitForResponse
{
while (/*Some condition is not met*/)
{
}
}
Also you need to implement the
- (void) hudWasHidden
{
[HUD removeFromSuperview];
[HUD release];
}
You might have a look to the Cocoa documentation chapter on selectors
Selector can be simply see as a pointer to a function.
Then, I guess you are trying to display a progress hud while a particular process is running .. this particular process logically should be isolated in a dedicated method (let's call it doTheJob ).
So start by creating a dedicated method named whatever ( here doTheJob )
- (void) doTheJob;
That being said the MBProgressHUD allows you to simply specify the working method that should be handled by the progress information using the showWhileExecuting method. And the selector is here to defined the target worker method.
[loading showWhileExecuting:#selector(doTheJob) onTarget:self withObject:nil animated:YES];
The target would be the object reference that defines the selector. To remain simple, if you define the method doTheJob in the current class use self as target.
and the withObject, is any parameter that you want / need to provide to the selector method. Beware that if you need to provide parameter to the target method, you need to extend the selector definition with an trailing colon as #selector(doTheJob:)
Hope this helps.
I am implementing a simple in-app browser. In my home view (UITableViewController), I have something like:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
WebViewController *webViewController = [[WebViewController alloc] init];
switch (indexPath.row) {
case 0:
webViewController.stringURL = #"http://www.google.com";
break;
case 1:
webViewController.stringURL = #"http://www.bing.com";
break;
default:
webViewController.stringURL = #"http://stackoverflow.com";
break;
}
[self.navigationController pushViewController:webViewController animated:YES];
[webViewController release];
}
The app crashed after I repetitively navigated back and forth between my home view and webViewControllera few times.
Inside WebViewController class, I have nothing but a [UIWebView *webView] and a [UIActivityIndicatorView *activityIndicator]. Both are with attributes nonatomic, retain. Here is the implementation.
#import "WebViewController.h"
#implementation WebViewController
#synthesize webView, activityIndicator, stringURL;
- (void)dealloc
{
[self.webView release];
self.webView.delegate = nil;
[self.activityIndicator release];
[super dealloc];
}
-(void)loadView {
UIView *contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
self.view = contentView;
CGRect webFrame = [[UIScreen mainScreen] applicationFrame];
webFrame.origin.y = 0.0f;
self.webView = [[UIWebView alloc] initWithFrame:webFrame];
self.webView.backgroundColor = [UIColor blueColor];
self.webView.scalesPageToFit = YES;
self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
self.webView.delegate = self;
[self.view addSubview: self.webView];
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.stringURL]]];
self.activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
self.activityIndicator.frame = CGRectMake(0.0, 0.0, 30.0, 30.0);
self.activityIndicator.center = self.view.center;
[self.view addSubview: self.activityIndicator];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadView];
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
// starting the load, show the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[activityIndicator startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// finished loading, hide the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[activityIndicator stopAnimating];
}
#end
I just ran my app in Instruments using the Zombies template, which shows -[UIWebView webView:didReceiveTitle:forFrame:] is the Zombie call. But I still can’t figure out what is actually the problem.
(Please download trace if needed)
Any help is greatly appreciated!
[Update]:
As #7KV7 and #David pointed out, there is an obvious bug in my dealloc function. I should call self.webView.delegate=nil; first before I release self.webView. Sorry about that. Unfortunately, after I fix it, the app still crashes in the same way.
If I delete [webViewController release]; from the first code block, the crash actually is gone. But obviously, there will be memory leak.
First of all, remove that call to loadView in viewDidLoad. The framework will the call the method when it doesn't find a view provided in XIB file. Second, your loadView is filled with memory leaks. You are allocating, initializing and retaining an object every time the method is called. So you are taking ownership twice and releasing it only once in the dealloc.
The objects are not being properly deallocated. You should do something like alloc-init-autorelease to solve this. Next thing is the that every time the controller gets loaded, because of your call to loadView, you end up creating two web view objects and two requests. You lose reference to one of them as you reassign. Herein, lies the problem mentioned in the title. You aren't able to reset the delegate of a web view object that has your controller as a delegate. Imagine a request being completed soon after you leave. Here the message will go to a zombie object. This is a pretty good example for why you need to nil out your delegates.
- (void)dealloc
{
self.webView.delegate = nil;
[self.webView release];
[self.activityIndicator release];
[super dealloc];
}
Try this dealloc. You were releasing the webview and then setting the delegate as nil. You should first set the delegate as nil and then release it. Hope this solves the issue.
I think what's happening is that you are going back while the page is still loading so the controller gets deallocated and then the webview finishes loading.
Try calling [webView stopLoading] in your viewDidUnload method to make sure this isn't happening.
Don't know if it's the cause of your problem, but this is definitely wrong:
[self.webView release];
self.webView.delegate = nil;
You cannot (safely) refer to self.webView after you release it!
Instead of pushing webViewController,add its view to self.view .
Dont call [self loadView]; in viewDidLoad.
I want to display the view first and then load the data in a background thread. When I navigate from root controller to the view controller, I want to display the view first. As of now, it stays on the root controller until the view controller is loaded. Here's my code for the root controller.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ProductDetailViewController *tempProductDetail = [[ProductDetailViewController alloc] init];
[self.navigationController pushViewController:tempProductDetail animated:YES];
[tempProductDetail release];
}
ProductDetailViewController, here I want to display the view first and then load the data...
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:YES];
[self performSelectorOnMainThread:#selector(workerThread) withObject:nil waitUntilDone:NO];
}
-(void) workerThread{
NSAutoreleasePool *arPool = [[NSAutoreleasePool alloc] init];
[arPool release];
}
Don't know what I'm doing wrong. Please, help.
Use [self performSelectorInBackground:#selector(workerThread) withObject:nil]; instead of
[self performSelectorOnMainThread:#selector(workerThread) withObject:nil waitUntilDone:NO];
found the answer for this issue,
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:YES];
[self performSelectorInBackground:#selector(workerThread) withObject:nil];
}
- (void) workerThread
{
// Set up a pool for the background task.
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// only do data fetching here, in my case from a webservice.
//...
// Always update the components back on the main UI thread.
[self performSelectorOnMainThread:#selector(displayView) withObject:nil waitUntilDone:YES];
[pool release];
}
// Called once the background thread task has finished.
- (void) displayView
{
//here in this method load all the UI components
}
Consider using the following pattern instead for threading, in my opinion it's much cleaner:
- (void)viewWillAppear:(BOOL)animated
{
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self
selector:#selector(someFunction)
object:nil];
[[NSOperationQueue currentQueue] addObject:operation]; // this will actually start the thread
[operation release];
}
- (void)someFunction
{
// don't need to initialize and release an autorelease pool here,
// you can just write a function as usual ...
[self updateUI];
}
- (void)updateUI
{
if (![NSThread isMainThread]) // if we need a UI update, force it on main thread
{
[self performSelectorOnMainThread:#selector(updateUI) withObject:nil waitUntilDone:YES];
return;
}
// do UI updates here
}
By writing code in this way, you can more dynamically decide which function you want to thread, since there is no autorelease pool requirement. If you need to do UI updates, the updateUI function will make sure for itself that it's running on the main thread, so the caller doesn't need to take this into account.
When my app starts I would like to initialize an UIImagePickerController. Since this can take several seconds, I would like to do it in the background. What is the best way to ensure that the background task finished, before invoking the picker?
Currently I have the following code. It works, but it will crash if one invokes the picker before the background task is done.
- (void) viewDidAppear: (BOOL)animated {
[super viewDidAppear: animated];
[self performSelectorInBackground:#selector(initPicker) withObject:nil];
....
}
and
- (void) initPicker {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"picker start... ");
[self setPicker: [[UIImagePickerController alloc] init]];
NSLog(#"picker done.");
[pool release];
}
Thank you!
Edit: It turns our this question is somewhat theoretical. Computing [[UIImagePickerController alloc] init] only takes time in debug mode on the device. So for production code, there is no need to run anything in the background. Also, [[UIImagePickerController alloc] init] seems to lock the main thread, so even in debug mode there is no advantage of placing it on a background thread.
Maybe a simple flag can do the job ?
#interface MyViewController : UIViewController
{
BOOL _pickerIsLoaded;
}
#end
#implementation MyViewController
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
_pickerIsLoaded = NO;
[self performSelectorInBackground:#selector(initPicker) withObject:nil];
}
- (void)initPicker
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"picker start... ");
[self setPicker: [[UIImagePickerController alloc] init]];
NSLog(#"picker done.");
_pickerIsLoaded = YES;
[pool release];
}
#end
I would use NSConditionLock for signaling that your controller is loaded. In the -initPicker method I would set the condition when the UIImagePickerController is finish initializing. And in your IBAction for the showing the picker, I would check for that condition. For more options, see Threading Programming Guide.
Can you wait until your "picker done" and then add the picker view to whatever view makes it visible?
Something like this, right after your "picker done" statment:
[self presentModalViewController: theImagePickerController]; // assuming self is a viewController.
I have an App using UITableViews and fetching data from a server. I am attempting to put a UIActivityIndicatorView on the Parent UITableView, so it spins while the Child UITableView is loading. I have the UIActivityIndicatorView all hookedup through Interface Builder, etc.
-(void)spinTheSpinner {
NSLog(#"Spin The Spinner");
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[spinner startAnimating];
[NSThread sleepForTimeInterval:9];
[self performSelectorOnMainThread:#selector(doneSpinning) withObject:nil waitUntilDone:NO];
[pool release];
}
-(void)doneSpinning {
NSLog(#"done spinning");
[spinner stopAnimating];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
NSMutableString *tempText = [[NSMutableString alloc] initWithString:[categoryNumber objectAtIndex:indexPath.row]];
Threads *viewController = [[Threads alloc] initWithNibName:#"newTable" bundle:nil tagval:tempText SessionID:PHPSESSID];
[self.navigationController pushViewController:viewController animated:YES];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[viewController release];
}
So, when I push the Child UITableView onto the screen, the UIActivityIndicatorView (spinner) just sits there on the Parent UITableView. If I go to the Child UITableView and then quickly go back to the Parent View, I can catch the UIActivitIndicatorView in the act of spinning. The NSLogs are showing at the correct times - when I first tap the cell in the Parent UITableView, the "Spin The Spinner" appears in the Log. 9 seconds later, I get "done spinning" in the Log, but the spinner never spins unless I pop back to the Parent UITableView in time.
Ideas of why this is not working properly?
Perhaps the sleepForTimeInterval is blocking the animation.
Try removing the sleepForTimeInterval and replace performSelectorOnMainThread with a call to performSelector:withObject:afterDelay:.
let the ui display it outside thread:
[NSObject dispatchUI:^{
[spinner startAnimating];
}];