I need your help. I have write my own custom NSOperation class called GetNewsOperation. I call it like this:
GetNewsOperation *getNewsOperation = [[GetNewsOperation alloc] initWithLocalNewsCategories:self];
[loadNewsOperationQueue addOperation:getNewsOperation];
[getNewsOperation release];
In GetNewsOperation class I have implemented init method for initialization and main method for executing operation and returning data back to the main thread.
Main method looks like this:
- (void)main {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
AppSettingsController *sharedAppSettingsController = [AppSettingsController sharedAppSettingsController];
if( [type isEqualToString:#"getCategory"] ) {
NSMutableArray *parsedData = [[NSMutableArray alloc] initWithArray:[sharedAppSettingsController getLocalNewsCategories]];
[newsViewController performSelectorOnMainThread:#selector(loadDataResponse:) withObject:[NSArray arrayWithObjects:parsedData, nil] waitUntilDone:NO];
[parsedData release]; parsedData = nil;
}
[pool release];
}
Everything works fine but I have a minor problem. When this operation is called application does not rotate on device orientation change. It changes after operation is finished.
As far as I know this operation is running for sure in new thread (not in main) cos all other elements in the app are active (not freezed). But I have only problem with orientation. The app looks kind a crappy if application does not rotate only when this operation occurs...
How can I solve this?
Thanks!
Actually it works like predicted. I was doing something else on main thread that blocked application.
Related
In my model, data is downloaded from website in a for loop and in a each turn data is sending to my viewController using protocol method. In model file;
for(NSString* data in DataArray){
[self.delegate passUpdatingCourse:data_name];
//other operations
}
In my viewController data name coming from model is saving to NSArray property in other thread;
ModelClass *modelObject = [[ModelClass alloc] init];
[modelObject setDelegate:self];
dispatch_queue_t otherQ = dispatch_queue_create("Q", NULL);
dispatch_async(otherQ, ^{
//other operations
[self performSelectorOnMainThread:#selector(passUpdatingCourse:) withObject:nil waitUntilDone:YES];
dispatch_async(dispatch_get_main_queue(), ^{
[self.myIndicator stopAnimating];
self.indicatorText.hidden = YES;
[self.changingCourseLabel setNeedsDisplay];
});
});
And also data coming via protocol method is setting viewController label;
-(void)passUpdatingCourse:(NSString *)data_in{
self.myLabel.text = data_in;
}
When each data came, myLabel in a viewController must be update. But it is not happens. In a protocol method when I use this;
NSLog(#"Data:%#",self.myLabel.text);
Yeah it shows data in a console but myLabel in a view is not changing.
I searched questions like that but couldn't find a solution.
Assuming your loop is on a background thread, dispatch your label updates to the main queue (async).
If you're inside a for loop on the main thread nothing is going to get updated in the UI until your method returns and dispatch won't help in that case.
I need to load UIImagePickerController faster. My app will call UIImagePickerController from possibly multiple controllers and within each controller there are two buttons, one for Photo Library, another for Camera. This is my sample app. Some solutions I tried are recommended by Apple in 'Concurrency Programming Guide'. The simplest is to alloc & init an instance when user presses a button. This can take up to 2 seconds before the camera shows up. Solution attempt:- (1) I made a #property (...) *imagePicker and called its alloc & init in viewDidAppear to make it seem smooth, but it interfered with loading of other elements, possibly because it was using the same thread. Worse results when used in initWithNibName or viewDidLoad. (2) Operation queues... not pleasing results. (3) Dispatch Queues... better results but still noticeable choppy performance. (4) performSelectorInBackground:withObject: not great results. I don't think I want to go global, unless otherwise recommended. I have no problem making two #properties of UIImagePickerController. I will continue to possibly 'Threading Programming Guide'. Please include any necessary memory management practices with your solutions, if you can. Thank you.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// (1) TRYING OPERATION QUEUES
// (a) --- NSBlockOperation
NSBlockOperation *NSBO_IP = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"before");
imagePicker = [[UIImagePickerController alloc] init];
// 1.9, 1.6, 1.4 seconds pass between 'before' and 'after'
// it seems this method has a dynamic technique, executes in different order every time
NSLog(#"after");
}];
// (b) --- NSInvocationOperation
NSInvocationOperation *NSIO_IP = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(loadTheImagePicker) object:nil];
// --- Add an operation
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//NSLog(#"time 1");
// (a) [queue addOperation:NSBO_IP];
// (b) [queue addOperation:NSIO_IP];
//NSLog(#"time 2");
// (2)TRYING DISPATCH QUEUES
// (a) --- GCD call (do I need to release 'myQueue' at some point since this is C-level call?)
/*
NSLog(#"time 1");
dispatch_queue_t myQueue = dispatch_queue_create("My Queue", NULL);
dispatch_async(myQueue, ^{
imagePicker = [[UIImagePickerController alloc] init];
// 1.2, 1.2, 1.2, 1.4 seconds significant increase over Operation Queues
// a solid constant executing technique, executes very consistently
});
NSLog(#"time 2");
*/
// (3)TRYING performSelectorInBackground:withObject (not recommended?)
NSLog(#"time 1");
[self performSelectorInBackground:#selector(loadTheImagePicker) withObject:nil];
// 1.3, 1.7, 1.3 seconds pass between 'before' and 'after'
NSLog(#"time 2");
// RESULTS REFLECTED IN LINE BELOW !
[self changeText:self]; // text does not change on time when trying to alloc & init an ImagePickerController
}
- (void)loadTheImagePicker
{
NSLog(#"before");
imagePicker = [[UIImagePickerController alloc] init];
// --- NSInvocationOperation used
// 1.6, 1.2, 1.4 seconds pass between 'before' and 'after'
// this method has a more constant executing technique, as far as order of executions
// --- NSInvocationOperation used
NSLog(#"after");
}
from #Murat's answer in a comment:
I found the solution. This question has already been answered. It
seems when connected to Xcode debugger the [UIImagePickerController
alloc] init] takes much longer to execute than when running the App
without Xcode.
My solution is still to keep #property and do the alloc
& init in the viewDidLoad method.
The other solution is here:
UIViewController - mysteriously slow to load
moved this to an answer as I almost missed it as a comment.
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.
Hopefully it is an appropriate question to ask. My goal is
1.add a controller into an array name 'arrControllers'
2.access and get current controller from 'arrControllers' to do sth with it
and I just wanna make sure the arrControllers should be ready before I access and use it. That is why I am using serial queue and dispatch_sycn like like following.
-(void)viewDidLoad
{
[super viewDidLoad];
firstViewController = [[FirstViewController alloc] initWithNibName:#"FirstViewController" bundle:nil];
[self setUpWithView:firstViewController];
}
and in setUpWithView: is
-(void)setUpWithView:(UIViewController*)viewController {
dispatch_queue_t queue;
queue = dispatch_queue_create("my queue", NULL);
containerController = [ContainerViewController sharedContainerController];
// What I am taking about is from now on
dispatch_sync(queue, ^{
[containerController initWithRootViewController:viewController];
});
dispatch_sync(queue, ^{
[containerController setUpView];
});
}
and initWithRootViewController: is
- (void)initWithRootViewController:(UIViewController*)rootViewController {
arrControllers = [[NSMutableArray alloc] init];
arrControllers = [NSMutableArray arrayWithObject:rootViewController];
}
and setUpView is
-(void)setUpView {
/* Process to ADD currentController TO hierarchy */
[self addChildViewController:[arrControllers lastObject]];
............................................................
}
As far as I know the compiler will execute codes line by line, it means by do following
-(void)setUpWithView:(UIViewController*)viewController {
containerController = [ContainerViewController sharedContainerController];
// Not using serial_queue and dispatch_sync
[containerController initWithRootViewController:viewController];// line1
[containerController setUpView]; //line2
}
compiler will execute line2 until it finished the task at line1.
My question is
1. is it necessary to using `serial_queue` and `dispatch_sycn`for this situation.
PS: if you think it is a bad question to ask. please comment what you think about it.
First of all, you should have a look at UIView and UIViewController life cycles, this will tell you when a UIVie or a UIViewController is "ready to be used". Second, you should check when the serial ques and GCD is used.
For the second point I can give you a short summary because it's kind of faster that the first point.
Use CGD and queues (other then main queue) if you want to do computing or other stuff, that don't involve UI changes, on a background thread without freezing the UI.
Use GCD with main queue to switch between a background queue and the main (UI) queue.
All UI operations must be performed on the main thread.
So in your case, you want to create a view controller and store it into an array, so as a suggestion, all the UI related calls of your view controllers should be performed on the UI thread, so no need for GCD or background threads.
In case you are doing some complicated stuffs on your view controller's init methods, just put those complicated stuffs on a GCD block on a separated thread.
In this situation if you do not use dispatch_sync, you will get exactly what you need:
-(void)setUpWithView:(UIViewController*)viewController {
containerController = [ContainerViewController sharedContainerController];
// Not using serial_queue and dispatch_sync
[containerController initWithRootViewController:viewController];// line1
[containerController setUpView]; //line2
}
Line 1 will execute initWithRootViewController:, going deeper and executing lines
arrControllers = [[NSMutableArray alloc] init];
arrControllers = [NSMutableArray arrayWithObject:rootViewController];
then it will return from this function one level above and proceed, moving to line 2 and going down to line
[self addChildViewController:[arrControllers lastObject]];
ALSO. Never write code like this
arrControllers = [[NSMutableArray alloc] init];
arrControllers = [NSMutableArray arrayWithObject:rootViewController];
First line creates an empty NSMutableArray and assigns a pointer to it into arrControllers.
Second line creates another array (autoreleased) and assigns a pointer to it in arrControllers again. So a link to array created in the first line is lost and it is leaked under manual memory management.
If you use manual memory management, do the following:
arrControllers = [[NSMutableArray alloc] init];
[arrControllers addObject:rootViewController];
OR
arrControllers = [[NSMutableArray alloc] initWithObjects:rootViewController,nil];
Under ARC leave only second line.
Hope I understood you correctly. Feel free to ask questions.
I really need help here. I'm desperate at this point.
I have NSOperation that when added to the NSOperationQueue is not being triggered.
I added some logging to see the NSOperation status and this is the result:
Queue operations count = 1
Queue isSuspended = 0
Operation isCancelled? = 0
Operation isConcurrent? = 0
Operation isFinished? = 0
Operation isExecuted? = 0
Operation isReady? = 1
Operation dependencies? = 0
The code is very simple. Nothing special.
LoadingConflictEvents_iPad *loadingEvents = [[LoadingConflictEvents_iPad alloc] initWithNibName:#"LoadingConflictEvents_iPad" bundle:[NSBundle mainBundle]];
loadingEvents.modalPresentationStyle = UIModalPresentationFormSheet;
loadingEvents.conflictOpDelegate = self;
[self presentModalViewController:loadingEvents animated:NO];
[loadingEvents release];
ConflictEventOperation *operation = [[ConflictEventOperation alloc] initWithParameters:wiLr.formNumber pWI_ID:wiLr.wi_id];
[queue addOperation:operation];
NSLog(#"Queue operations count = %d",[queue operationCount]);
NSLog(#"Queue isSuspended = %d",[queue isSuspended]);
NSLog(#"Operation isCancelled? = %d",[operation isCancelled]);
NSLog(#"Operation isConcurrent? = %d",[operation isConcurrent]);
NSLog(#"Operation isFinished? = %d",[operation isFinished]);
NSLog(#"Operation isExecuted? = %d",[operation isExecuting]);
NSLog(#"Operation isReady? = %d",[operation isReady]);
NSLog(#"Operation dependencies? = %d",[[operation dependencies] count]);
[operation release];
Now my operation do many things on the main method, but the problem is never being called. The main is never executed.
The most weird thing (believe me, I'm not crazy .. yet). If I put a break point in any NSLog line or in the creation of the operation the main method will be called and everything will work perfectly.
This have been working fine for a long time. I have been making some changes recently and apparently something screw things up. One of those changes was to upgrade the device to iOS 5.1 SDK (iPad).
To add something, I have the iPhone (iOS 5.1) version of this application that use the same NSOperation object. The difference is in the UI only, and everything works fine.
Oh, and this only fails on the actual device. In the simulator everything works ok.
Any help will be really appreciated.
Regards,
If the operation is concurrent, you need to implement -start, not -main.
Ok, I finally solve this issue.
The problem I was having was due to an NSOperation running in the background all the time (but in a different queue). This operation was blocking any other thread (NSOperation) to be executed. This was happening only in iPad 1 because is not dual core.
My NSOperation was doing something like this on the main method:
- (void)main
{
while (![self isCancelled]) {
//Do stuff
}
}
And the NSOperation was contantly doing it the whole time
Silly me, I didn't give the OS time to work with other threads, so adding a sleep made the trick
- (void)main
{
while (![self isCancelled]) {
[NSThread sleepForTimeInterval:0.5];
//Do Stuff
}
}
That sleep gives the OS chance to work with other threads.
Why putting a breakpoint was doing the work? ... the debugger stopped all the threads and because the breakpoint was in my other NSOperation, that thread was executed.
I had the same issue, but the resolution to mine was not the same, so I thought I'd throw my answer in here too for future people like me.
My problem was that in my NSOperation subclass, I had:
#property CGPoint start;
which, when synthesized, creates the method:
-(CGPoint)start
which overrides NSOperation's -(void)start; method, which is the signifier for a non-concurrent NSOperation, and was preventing all the regular stuff that goes on in -(void)start from happening, which thus prevents the -(void)main method from being called at all.
Once I renamed my property to something other than start, it worked fine.
Ok, this issue is only happening with iPad 1 devices with application compiled with the SDK 5.1.
I tried with an iPad 1 with iOS 4.2.1 and iPad 1 with iOS 5.1. Both gives the same problems.
For sure this was working in both iPads with application compiled with SDK 4.3