I'm using the UIRefreshControl for the first time and I get an exception during the loading of my refresh control.
Here is my declaration :
self.refreshControl = [[UIRefreshControl alloc] init];
self.refreshControl.tintColor = [UIColor grayColor];
[self.refreshControl addTarget:self action:#selector(refreshView:) forControlEvents:UIControlEventValueChanged];
[self.actualitesTableView addSubview:self.refreshControl];
Here are my functions :
- (void)refreshView:(UIRefreshControl *)sender {
[self performSelectorInBackground:#selector(threadAction) withObject:nil];
}
- (void)threadAction {
[self choixMAJ];
NSLog(#"OK1");
[self.refreshControl endRefreshing];
NSLog(#"OK2");
}
When I use the choixMAJ() method, it works perfectly.
Everything's going right and the OK2 is logged but after that, when the refresh control disapeared, the app crashed with this error :
*** -[__NSArrayM removeObject:]: message sent to deallocated instance 0x655a1a0
I don't understand why.. Any ideas ?
Well, you shouldn't call -endRefreshing on a background thread, for starters. UIKit methods (including that one) should be performed on the main thread. I'm not sure that's actually causing your problem, though.
Thanks shusta, it helps me a lot !
The answer here is correct, and to work around this i did the following.
I setup a timer in the main thread which monitored for a boolean. When you are ready to stop the refresher... set that boolean to true in your sub-thread. The timer will see that and call the endRefreshing function from the main thread.
Related
I have a UITableViewController in which I am implementing a UIRefreshControl for pull to refresh. Everything is working fine, the table is getting populated from my web service. But when I pull down to refresh I get the error:
[MyViewController refreshView]: unrecognized selector sent to instance ...
Which is complaining about the addTarget action here:
UIRefreshControl * refresh = [[UIRefreshControl alloc] init];
[refresh addTarget:self action:#selector(refreshView) forControlEvents:UIControlEventValueChanged];
The error flag on that line in the editor is Undeclared selector 'refreshView'
My refreshView method is simply:
- (void) refreshView: (UIRefreshControl *)refresh {
NSLog(#"test");
}
Any ideas as to why this would be causing the application to crash? (I am running iOS 7.1)
If you declared your method as "refreshView:" (i.e. with a parameter), you need to add a colon to the "#selector" bit.
In other words, one line changes with one character:
[refresh addTarget:self action:#selector(refreshView:) forControlEvents:UIControlEventValueChanged];
I'm using AFNetworking 2.x and this UIRefreshControl+AFNetworking category, have a weird problem using a UIViewController with a UITableView as a property, I add a UIRefreshControl with this particular code
CGRect frame = self.mytableView.bounds;
frame.origin.y = -frame.size.height;
UIView *refreshBackgroundView = [[UIView alloc]initWithFrame:frame];
refreshBackgroundView.backgroundColor = [UIColor colorWithRed:239/255.0f green:239/255.0f blue:244/255.0f alpha:1.0f];
self.refreshControl = [[UIRefreshControl alloc]init];
[self.mytableView insertSubview:refreshBackgroundView atIndex:0];
[self.mytableView addSubview:self.refreshControl];
self.refreshControl.tintColor = [UIColor lightGrayColor];
[self.refreshControl addTarget:self action:#selector(getAlertList:) forControlEvents:UIControlEventValueChanged];
[self.mytableView.tableHeaderView addSubview:self.refreshControl];
UIRefreshControl+AFNetworking.h causing a EXC_BAD_ACCESS, when I have a call for my webservice and the user do a segue (the task its not finish), the task continue calling the UIRefreshControl from my dead VC, i have a strong reference for the property self.refreshControl.
The trace on the error leave to this part of the code in AFURLSessionManager.h on the Method:
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
exactly on this lines:
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
I open a issue to AFNetworking an #matt (AFNetworking creator and maintainer) response was: "Your UIRefreshControl is being deallocated before the task finishes, causing a selector to be sent to an incorrect target. Be sure to keep a strong reference to the control to ensure that it's not released prematurely. " Something that i already know.I already use Instrument to detect the zombie object and all appears to be the UIRefreshControl, any ideas to solve this problem ???
Other advice or comment i had form #matt (AFNetworking creator and maintainer) was "It's the responsibility of the developer to either keep a reference to the control or cancel the associated task before performing the segue. " . I already try to cancel the task before the segue but it's the same problem.
If someone have a idea to do that or have a solution for this problem i would be very happy to listen.
Thx
I am having some trouble updating my UI using performSelectorOnMainThread. Here is my situation. In my viewDidLoad I set up an activity indicator and a label. Then I call a selector to retrieve some data from a server. Then I call a selector to update the UI after a delay. Here's the code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.reloadSchools = [[UIAlertView alloc] init];
self.reloadSchools.message = #"There was an error loading the schools. Please try again.";
self.reloadSchools.title = #"We're Sorry";
self.schoolPickerLabel = [[UILabel alloc]init];
self.schoolPicker = [[UIPickerView alloc] init];
self.schoolPicker.delegate = self;
self.schoolPicker.dataSource = self;
self.server = [[Server alloc]init];
schoolList = NO;
_activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self.view addSubview:_activityIndicator];
[self.view bringSubviewToFront:_activityIndicator];
[_activityIndicator startAnimating];
[NSThread detachNewThreadSelector: #selector(getSchoolList) toTarget: self withObject: nil];
[self performSelector:#selector(updateUI) withObject:nil afterDelay:20.0];
}
The selector updateUI checks to see if the data was retrieved, and calls a selector on the main thread to update the UI accordingly. Here is the code for these parts:
-(void)updateUI
{
self.schools = [_server returnData];
if(!(self.schools == nil)) {
[self performSelectorOnMainThread:#selector(fillPickerView) withObject:nil waitUntilDone:YES];
}
else {
[self performSelectorOnMainThread:#selector(showError) withObject:nil waitUntilDone:YES];
}
}
-(void)showError {
NSLog(#"show error");
[_activityIndicator stopAnimating];
[self.reloadSchools show];
}
-(void)fillPickerView {
NSLog(#"fill picker view");
schoolList = YES;
NSString *schoolString = [[NSString alloc] initWithData:self.schools encoding:NSUTF8StringEncoding];
self.schoolPickerLabel.text = #"Please select your school:";
self.shoolArray = [[schoolString componentsSeparatedByString:#"#"] mutableCopy];
[self.schoolPicker reloadAllComponents];
[_activityIndicator stopAnimating];
}
When the selector fillPickerView is called the activity indicator keeps spinning, the label text doesn't change, and the picker view doesn't reload its content. Can someone explain to me why the method I am using isn't working to update my ui on the main thread?
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//load your data here.
dispatch_async(dispatch_get_main_queue(), ^{
//update UI in main thread.
});
});
First of all you should not be using detachNewThreadSelector. You should use GCD and submit your background task to an async queue. Threads are costly to create. GCD does a much better job of managing system resources.
Ignoring that, your code doesn't make a lot of sense to me. You submit a method, getSchoolList, to run on a background thread. You don't show the code that you are running in the background.
Then use performSelector:withObject:afterDelay to run the method updateUI on the main thread after a fixed delay of 20 seconds.
updateUI checks for self.schools, which presumably was set up by your background thread, and may or may not be done. If self.schools IS nil, you call fillPickerView using performSelectorOnMainThread. That doesn't make sense because if self.schools is nil, there is no data to fill the picker.
If self.schools is not nil, you display an error, again using performSelectorOnMainThread.
It seems to me that the logic on your check of self.schools is backwards. If it is nil you should display an error and if it is NOT nil you should fill the picker.
Next problem: In both cases you're calling performSelectorOnMainThread:withObject:waitUntilDone: from the main thread. Calling that method from the main thread doesn't make sense.
Third problem: It doesn't make sense to wait an arbitrary amount of time for a background task to run to completion, and then either succeed or fail. You won't have any idea what's going on for the full 20 seconds. If the background task finishes sooner, you'll never know.
Instead, you should have your background task notify the main thread once the task is done. That would be a valid use of performSelectorOnMainThread:withObject:waitUntilDone:, while calling it from the main thread is not. (Again, though, you should refactor this code to use GCD, not using threads directly.
It seems pretty clear that you are in over your head. The code you posted needs to be rewritten completely.
I have an app that makes web service calls to obtain data. I want to add an activity indicator that is visible when the app is fetching web service data. I have looked into other posts, and though I believe I am doing as the posts recommend, my indicator does not render on the screen. The object that makes the web service call is stateGauges. Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
UIActivityIndicatorView *activityStatus = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(120, 230, 50, 50)];
activityStatus.center = self.view.center;
[self.view addSubview:activityStatus];
[activityStatus bringSubviewToFront:self.view];
[UIApplication sharedApplication].networkActivityIndicatorVisible = TRUE;
[activityStatus startAnimating];
stateGauges = [[GaugeList alloc] initWithStateIdentifier:stateIdentifier andType:nil];
[activityStatus stopAnimating];
}
Any suggestions? Thanks! V
Your problem is that your animation start is blocked by whatever you're doing in your GuagesList initializer.
When you tell the activity indicator to start animating, it doesn't immediately render to the screen but rather flags the view as needing an update on the next turn of the run loop. Your initializer then blocks the thread until its done, you call stopAnimating, and then the thread has a chance to update the indicator. By which point its already set to not animate.
The best solution is to perform your initializer on another thread using GCD. And be sure to switch back to the foreground thread before calling stopAnimating.
The usual pattern is do something like:
[activityStatus startAnimating];
// enqueue it
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
stateGauges = [[GaugeList alloc] initWithStateIdentifier:stateIdentifier andType:nil];
// now switch back to main thread
dispatch_async(dispatch_get_main_queue(), ^{
[activityStatus stopAnimating];
});
});
You'll want to verify the code as I had to type this from memory on a Windows machine.
take out
[activityStatus bringSubviewToFront:self.view];
because according to the docs bringSubviewToFront:
Moves the specified subview so that it appears on top of its siblings.
which isn't what you want. (another answer suggested you do [self.view bringSubviewToFront:activityStatus] instead.. that's fine, but generally this call is redundant, b/c
[self.view addSubview:activityStatus] adds the activityStatus to the end of the views in the self.view subviews array anyways)
if that still don't work.. basically put a break point right after you start animating, then type this into the console:
[[activityStatus superview] recursiveDescription]
recursiveDescription will give you a UI tree graph and basically tell you exactly where the activityIndicator view is.. you may have made an incorrect assumption about something.
Change
[activityStatus bringSubviewToFront:self.view];
To
[self.view bringSubviewToFront:activityStatus];
I'm nearing the end of a school project with programming in Xcode, but right now I'm having a small yet extremely annoying issue: a memory leak. The leak has been traced down to the following line of code:
#autoreleasepool {
[NSThread detachNewThreadSelector:#selector(updateThread) toTarget:self withObject:nil];
}
When I comment this out, the leak is gone. Apparently something goes wrong in the autoreleasepool: I'm still a bit new on these (especially when using ARC), but threads like this one made it clear to me that using #autoreleasepool should be sufficient.
For some reason, this is not the case for my code. I guess I'm missing something here: if someone could give some ideas on what the issue could be, then that would be highly appreciated. Just tell me if I have to post more code, that won't be a problem: it's just for the readability of the question that I try to limit it to the main issue.
Thanks in advance!
EDIT:
Thank you for the first responses! The issue still persists however... I will post a bit more code to clear things up a bit. The thread is started in viewDidLoad:
/*
Everything mentioned here will be done after loading.
*/
- (void)viewDidLoad
{
// Do standard setup
[super viewDidLoad];
// Do any additional setup before loading the view from its nib.
self.title = #"Blog Manager";
// Activate edit mode
[tbvBlogList setEditing:YES animated:YES];
tbvBlogList.allowsSelectionDuringEditing = YES;
[NSThread detachNewThreadSelector:#selector(updateThread) toTarget:self withObject:nil];
UIImage *btnImage = [UIImage imageNamed:#"iPhone_General_Button_Add_Blog.png"];
UIButton *viewBtnAddBlog = [UIButton buttonWithType:UIButtonTypeCustom];
[viewBtnAddBlog setImage:btnImage forState:UIControlStateNormal];
viewBtnAddBlog.frame = CGRectMake(0, 0, 80, 36);
[viewBtnAddBlog addTarget:self action:#selector(addBlogByButton:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *btnAddBlog = [[UIBarButtonItem alloc] initWithCustomView:viewBtnAddBlog];
btnAddBlog.tintColor = [UIColor clearColor];
self.navigationItem.rightBarButtonItem = btnAddBlog;
}
Then, the other functions that are used for the threading:
/*
Thread to update the progress bar with.
*/
- (void)updateThread
{
#autoreleasepool {
while(YES){
[self performSelectorOnMainThread:#selector(updateProgressBar) withObject:nil waitUntilDone:false];
[NSThread sleepForTimeInterval:0.1f];
}
}
}
/*
Updates the progress bar.
*/
- (void)updateProgressBar
{
pvProgress.progress = dProgress;
}
If it is anything worth mentioning: I'm using Xcode 4.2.1. Thanks again for the support!
Right now I just want to hit myself with a rock.
I just realized the "while"-loop never stops. Of course this means the thread will keep running, therefore the memory won't ever be released until the app finishes.
By simply adding a boolean that is set to "NO" when the thread should quit, the issue was solved. Everyone: thanks you very much for looking at this problem for me. Sometimes the biggest problems have the smallest solutions...
The #autoreleasepool block goes in your thread code (updateThread in this case), not around the creation of the thread.
You're not creating an autorelease pool inside the detached selector's method. Every thread selector needs its own pool. Do like this:
- (void) updateThread
{
#autoreleasepool {
// former code here
}
}