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.
Related
Consider this code:
[self otherStuff];
// "wait here..." until something finishes
while(!self.someFlag){}
[self moreStuff];
Note that this all happens ON THE SAME THREAD - we do not want to go to another thread.
otherStuff could do things like connect to the cloud, get input from the user, etc. so it would take a lot of time and could follow many possible paths.
otherStuff would set self.someFlag to true, when otherStuff is finally finished.
This works perfectly and there's no problem with it at all -- except that it's lame to burn up the processor like that with the empty loop!!
Quite simply, is there a way to say something like ..
halt here, until (some message, interrupt, flag, boolean, whatever?)
Rather than just while(!self.someFlag){}
(Note the alternative is to "chain" the procedures ... so at the end of "otherStuff", you and all the other programmers have to "just know" that you have to next call "moreStuff", regardless of how otherStuff plays out, etc. Of course, that is very messy when you have to add new procedures or change the order of things.) Cheers!!
BTW there are already two excellent answers below regarding the situation when you want DIFFERENT THREADS.
This is a solution using a semaphore, be careful not to introduce a deadlock - you need some way of telling your application something has finished, you can either do that using the NSNotificationCentre like you suggested but using a block is much easier.
[self someOtherStuffWithCompletion:nil];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self someOtherStuffWithCompletion:^{
dispatch_semaphore_signal(semaphore);
}];
NSLog(#"waiting");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(#"finished");
[self someOtherStuffWithCompletion:nil];
I would suggest to use a NSOperationQueue and to wait for all tasks until they are finished at specific points. Something like that:
self.queue = [[NSOperationQueue alloc] init];
// Ensure a single thread
self.queue.maxConcurrentOperationCount = 1;
// Add the first bunch of methods
[self.queue addOperation:[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(method1) object:nil]];
[self.queue addOperation:[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(method2) object:nil]];
[self.queue addOperation:[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(method3) object:nil]];
// Wait here
[self.queue waitUntilAllOperationsAreFinished];
// Add next methods
[self.queue addOperation:[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(method4) object:nil]];
[self.queue addOperation:[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(method5) object:nil]];
// Wait here
[self.queue waitUntilAllOperationsAreFinished];
HTH
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.
Sorry, it's a bit wordy, but I wanted to make sure I was clear! ;-)
I have an iOS app that uses FFMPEG for streaming RTSP. I've multi-threaded FFMPEG using NSOperationQueue such that most its work, other than painting the image to the screen, of course, happens in background threads.
Works great! ...except for the fact that threads the NSOperationQueue creates never die!
I init the Queue in the class' init method with:
self->opQ = [[NSOperationQueue alloc] init];
[self->opQ setMaxConcurrentOperationCount:1];
I add methods to the Queue using blocks:
[self->opQ addOperationWithBlock:^{
[self haveConnectedSuccessfullyOperation];
}];
Or
[self->opQ addOperationWithBlock:^{
if (SOME_CONDITION) {
[self performSelectorOnMainThread:#selector(DO_SOME_CRAP) withObject:nil waitUntilDone:NO];
}
}];
Later, when I need to tear down the RTSP stream, in addition to telling FFMPEG to shut down, I call:
[self->opQ cancelAllOperations];
Which does indeed stop the threads from doing any work , but never actually destroys them. Below, you'll see a screen shot of threads that are doing nothing at all. This is what my threads look like after starting/stoping FFMPEG several times.
I seem to remember reading in Apple's documentation that NSOperations and the threads they are run on are destroyed once they are done executing, unless otherwise referenced. This doesn't appear to be the case.
Do I just need to destroy the NSOperationQueue, then re-init it when I need to start up FFMPEG again (I just realized I haven't tried this)? Anyone know how I need to kill these extra threads?
THANKS!
I solved it by creating NSBlockOperations so that I could monitor the isCancelled state, while also making the new NSBlockOperations' content more intelligent, such that I simplified the routine that would add the operations to the queue.
... Plus, I made an NSOperationQueue n00b mistake: I was adding operations to the queue on a looping basis, which fired up to 30 times per second (matching the video's frame rate). Now, however, the operation is added to the queue only once and the looping behavior is contained within the operation instead of having the loop add the operation to the queue.
Previously, I had something like this (pseudo code, since I don't have the project with me):
NSTimer *frameRateTimeout = [NSTimer scheduledTimerWithTimeInterval:1/DESIRED_FRAMES_PER_SECOND target:self selector:#selector(ADD_OPERATION_TO_QUEUE_METHOD:) userInfo:nil repeats:YES];
-(void)ADD_OPERATION_TO_QUEUE_METHOD:(NSTimer *)timer {
[opQ addOperation:displayFrame];
}
Which worked well, as the OS would correctly manage the queue, but it was not very efficient, and kept those threads alive forever.
Now, it's more like:
-(id)init {
self = [super init];
if (self) {
// alloc/init operation queue
...
// alloc/init 'displayFrame'
displayFrame = [NSBlockOperation blockOperationWithBlock:^{
while (SOME_CONDITION && ![displayFrame isCancelled]) {
if (playVideo) {
// DO STUFF
[NSThread sleepForTimeInterval:FRAME_RATE];
}
else { // teardown stream
// DO STUFF
break;
}
}
}];
}
return self;
}
- (void)Some_method_called_after_getting_video_ready_to_play {
[opQ addOperation:displayFrame];
}
Thanks, Jacob Relkin, for responding to my post.
If anyone needs further clarification, let me know, and I'll post better code once I have the project in my hands again.
The problem is that I manage scrollView with lots of tiles in it.
Each visible tile display image loaded from URL or (after first URL load) cached file in background. Invisible tiles recycles (set new frame and redraw).
Image load depends on tile position.
With long range scroll there is multiple redraw called for each tile: each tile loads (and display) different image several times before display the correct one.
So problem is to cancel all previously added operations for tile before add new.
I subclass NSInvocationOperation just to contain context object to detect operation attached to and before add new operation I canceling all operation for same tile:
-(void)loadWithColler:(TileView *)coller {
if (queue == nil) {
queue = [NSOperationQueue new];
}
NSInvocationOperationWithContext *loadImageOp = [NSInvocationOperationWithContext alloc];
[loadImageOp initWithTarget:self selector:#selector(loadImage:) object:loadImageOp];
[loadImageOp setContext:coller];
[queue setSuspended:YES];
NSArray *opers = [queue operations];
for (NSInvocationOperationWithContext *nextOperation in opers) {
if ([nextOperation context] == coller) {
[nextOperation cancel];
}
}
[queue addOperation:loadImageOp];
[queue setSuspended:NO];
[loadImageOp release];
}
And in operation itself I check isCancelled:
-(void)loadImage:(NSInvocationOperationWithContext *)operation {
if (operation.isCancelled) return;
TileView *coller = [operation context];
/* TRY TO GET FILE FROM CACHE */
if (operation.isCancelled) return;
if (data) {
/* INIT WITH DATA IF LOADED */
} else {
/* LOAD FILE FROM URL AND CACHE IT */
}
if (operation.isCancelled) return;
NSInvocationOperation *setImageOp = [[NSInvocationOperation alloc] initWithTarget:coller selector:#selector(setImage:) object:cachedImage];
[[NSOperationQueue mainQueue] addOperation:setImageOp];
[setImageOp release];
}
But it is do nothing. Some times early returns works but tiles still load many images before the correct one.
So how could I success? And could this lots of unneeded operations cause delays on main thread when scrolling? (Because delays are exists and I do not know why...all load in background..)
Update:
With NSLog:
isCancelled while executing:
>
cancel loadImage method for:
>
So canceling work.
Now I save reference to last operation in TileView object and perform setImage operation only if invoked operation is equal to TileView operation.
Doesn't make any difference...
Looks like there IS number of operations to load different images to one tile invoked one after another.
Any another suggestions?
For clearance:
There is singleton DataLoader (all code from it). And all tiles has call to it in drowRect:
[[DataLoader sharedDataLoader] loadWithColler:self];
Update:
NSInvocationOperation subclass:
#interface NSInvocationOperationWithContext : NSInvocationOperation {
id context;
}
#property (nonatomic,retain,readwrite) id context;
#end
#implementation NSInvocationOperationWithContext
#synthesize context;
- (void)dealloc
{
[context release];
[super dealloc];
}
#end
Thanks a lot for any help!
SOLUTION:
From answer below: need to subclass from NSOperation
As I subclass NSOperation and put all loadImage: code into it "main" method (just move all code here and nothing else) and all work just perfect!
As about scroll delaying: it occurs cause loading images to UIImageView (it takes long time because of decompress and rasterize (as I understood).
So better way is to use CATiledLayer. It loads data in background and do it much faster.
The delays on main thread is due to a the mode of the runloop while you scroll. I suggest you to watch the WWDC2011 networking app sessions. I don't know if it is fine to subclass an NSInvocationOperation that is a concrete subclass of NSOperation. I will subclass NSOperation instead. For my experience if you like to avoid sluggish scrolling, you should create NSOperation subclasses that load their main on a specific thread for networking operation (you must create it). There is a wonderful sample code from apple https://developer.apple.com/library/ios/#samplecode/MVCNetworking/Introduction/Intro.html
The way that NSOperationQueue works with respect to "setSuspended" is that it won't start to run newly added NSOperations added to it after that point, and won't start to run any that are currently in it that haven't started running yet. Are you sure your operations you're trying to cancel haven't already started yet?
Also - does your NSOperation subclass correctly deal with Key Value Observing? Concurrent Queue subclassed NSOperations have to call willChangeValueForKey and didChangeValueForKey for some properties here - but doesn't look like that's the issue as your queue doesn't set isConcurrent. Just FYI if you go that route.
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.