I am currently building an application that generates images and stores them in an NSMutableArray, which then used in a UINavigation (Cell.imageView.image). I need to be able to handle up to 2000 images without causing lag in my application.
Currently how I set this generating is by calling the generating method when cellForRowAtIndexPath is accessed. Which seems to cause a 4-5 second lag before the next navigation is called.
fortunately after those 4-5 seconds the generating is done and there is no issues.
In the world of iProducts waiting 4-5 seconds isn't really an option. I am wondering what my options are for generating these images in the background. I tried using threads [self performSelectorInBackground:#selector(presetSnapshots) withObject:nil];
but that only gave me issues about vectors for some reason.
Heres the generating code:
-(void)presetSnapshots{
//NSAutoreleasePool* autoReleasePool = [[NSAutoreleasePool alloc] init];
for (int i = 0; i < [selectedPresets count]; ++i){
GraphInfo* graphInfo = [selectedPresets objectAtIndex:i];
graphInfo.snapshot = [avc takePictureOfGraphInfo:graphInfo PreserveCurrentGraph:false];
[graphInfo.snapshot retain];
}
//[autoReleasePool drain];
presetSnapshotFinished = YES;
}
inside - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { is
if (presetSnapshotFinished == NO){
[self presetSnapshots];
//[self performSelectorInBackground:#selector(presetSnapshots) withObject:nil];
}
cell.imageView.image = [[selectedPresets objectAtIndex:indexPath.row] snapshot];
Edit:
I also rather not use coreData for this. The images are 23x23 and coming out to about 7kb. So its about 6MB being use at any giving time in memory.
You can use Grand Central Dispatch (GCD) to fire up [self presetSnapshots]
dispatch_queue_t working_queue = dispatch_queue_create("com.yourcompany.image_processing", NULL);
dispatch_queue_t high = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,NULL);
dispatch_set_target_queue(working_queue,high);
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(working_queue,^{
if (presetSnapshotFinished == NO){
[self presetSnapshots];
}
dispatch_async(main_queue,^{
cell.imageView.image = [[selectedPresets objectAtIndex:indexPath.row] snapshot];
});
});
Related
I am implementing a Cocoa Application which is just a simple progress bar that starts when I press a button.
The situation is: I can see Animation is Start and Stop when I press the button, but the progress bar will not update the value.
I had also tried the solution mentioned here but it doesn't work:
How do I update a progress bar in Cocoa during a long running loop?
Can someone help to see where is the problem in my source code?
Here is my source.
SimpleProgressBar.m
#import "SimpleProgressBar.h"
#implementation SimpleProgressBar
#synthesize progressBar;
int flag=0;
-(IBAction)startProgressBar:(id)sender{
if(flag ==0){
[self.progressBar startAnimation:sender];
flag=1;
}else{
[self.progressBar stopAnimation:sender];
flag=0;
}
[self.progressBar displayIfNeeded];
[self.progressBar setDoubleValue:10.0];
int i=0;
for(i=0;i<100;i++){
NSLog(#"progr: %f",(double)i);
[self.progressBar setDoubleValue:(double)i];
[self.progressBar setNeedsDisplay:YES];
}
}
#end
SimpleProgressBar.h
#import < Foundation/Foundation.h >
#interface SimpleProgressBar : NSObject{
__weak NSProgressIndicator *progressBar;
}
#property (weak) IBOutlet NSProgressIndicator *progressBar;
-(IBAction)startProgressBar:(id)sender;
#end
Thank you very much for any helpful answer.
Update:
Here is my porting from the solution and it doesn't work:
SimpleProgressBar.m
#import "SimpleProgressBar.h"
#implementation SimpleProgressBar
#synthesize progressBar;
int flag=0;
-(IBAction)startProgressBar:(id)sender{
if(flag ==0){
[self.progressBar startAnimation:sender];
flag=1;
}else{
[self.progressBar stopAnimation:sender];
flag=0;
}
[self.progressBar displayIfNeeded];
[self.progressBar setDoubleValue:0.0];
void(^progressBlock)(void);
progressBlock = ^{
[self.progressBar setDoubleValue:0.0];
int i=0;
for(i=0;i<100;i++){
//double progr = (double) i / (double)100.0;
double progr = (double) i;
NSLog(#"progr: %f",progr);
dispatch_async(dispatch_get_main_queue(),^{
[self.progressBar setDoubleValue:progr];
[self.progressBar setNeedsDisplay:YES];
});
}
};
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue,progressBlock);
}
Update:
A couple of observations:
It strikes me that if you want to watch the NSProgressIndicator advance, you need to add a sleepForTimeInterval or else the for loop iterates so quickly that you won't see the progress indicator advance, but rather you'll just see it quickly end up in its final state. If you insert sleepForTimeInterval, you should see it progress:
self.progressIndicator.minValue = 0.0;
self.progressIndicator.maxValue = 5.0;
[self.progressIndicator setIndeterminate:NO];
self.progressIndicator.doubleValue = 0.001; // if you want to see it animate the first iteration, you need to start it at some small, non-zero value
for (NSInteger i = 1; i <= self.progressIndicator.maxValue; i++)
{
[NSThread sleepForTimeInterval:1.0];
[self.progressIndicator setDoubleValue:(double)i];
[self.progressIndicator displayIfNeeded];
}
Or, if you wanted to do the for loop on a background thread, and dispatch the updates back to the main queue:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSInteger i = 1; i <= self.progressIndicator.maxValue; i++)
{
[NSThread sleepForTimeInterval:1.0];
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressIndicator setDoubleValue:(double)i];
[self.progressIndicator displayIfNeeded];
});
}
});
You are using startAnimation and stopAnimation, but according to the documentation each of these "does nothing for a determinate progress indicator," so these calls seem inappropriate for this situation.
My original answer, below, was predicated on the comment in the Threads and Your User Interface in the Threading Programming Guide, which says:
If your application has a graphical user interface, it is recommended that you receive user-related events and initiate interface updates from your application’s main thread. This approach helps avoid synchronization issues associated with handling user events and drawing window content. Some frameworks, such as Cocoa, generally require this behavior, but even for those that do not, keeping this behavior on the main thread has the advantage of simplifying the logic for managing your user interface.
But the answer below is (incorrectly) an iOS answer, so is not applicable.
Original answer:
Your for loop is running on the main thread, and thus UI updates won't appear until you yield back to the runloop. You're also going through that loop so quickly that even if you properly dispatched that to a background queue, you wouldn't experience the progress view changing as you iterate through your loop.
So, perform the loop on a secondary thread (e.g. via GCD or operation queue) and then dispatch UI updates back to the main thread, which is now free to do UI updates. So, using your theoretical example, you could do something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 100; i++)
{
[NSThread sleepForTimeInterval:0.1];
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressView setProgress: (CGFloat) (i + 1.0) / 100.0 animated:YES];
});
}
});
Note, having a loop that updates the progress view only makes sense if you're doing something slow enough for you to see the progress view change. In your original example, you're just looping from 0 to 99, updating the progress view. But that happens so quickly, that there's no point in a progress view in that case. That's why my above example not only employs a background queue for the loop, but also added a slight delay (via sleepForTimeInterval).
Let's consider a more realistic application of the progress view. For example, let's say I had an array, urls, of NSURL objects that represent items to be downloaded from the server. Then I might do something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < [urls count]; i++)
{
// perform synchronous network request (on main queue, you should only do asynchronous network requests, but on background queue, synchronous is fine, and in this case, needed)
NSError *error = nil;
NSURLResponse *response = nil;
NSURLRequest *request = [NSURLRequest requestWithURL:urls[i]];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:response error:error];
// got it; now update my model and UI on the basis of what I just downloaded
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressView setProgress: (CGFloat) (i + 1.0) / [array count] animated:YES];
// do additional UI/model updates here
});
}
});
In an iOS app, I'm running a fairly large script on a UIWebView using stringByEvaluatingJavaScriptFromString (large in terms of the length of the javascript string). There is a brief pause after calling the javascript causing other elements on the screen to hiccup for a moment.
Placing the javascript call in a function called in the background with self performSelectorInBackground breaks the application. Is there a safe way to call run this on a background thread or otherwise prevent the interface from pausing?
No, Webviews and the Webkit JavaScript engine are both single-threaded and cannot be used on a background thread.
A better option is to split up your JavaScript into discrete execution blocks and pipeline them using a JavaScript timer, like this (JS code, not Obj-C):
var i = 0;
var operation = function() {
switch (i) {
case 0:
//do first part of code
break;
case 1:
//do second part of code
break;
case 2:
//do third part of code
break;
etc...
}
//prepare to execute next block
i++;
if (i < TOTAL_PARTS) {
setTimeout(operation, 0);
}
};
operation();
That will prevent your script from blocking user interaction while it executes
Well, I was doing the same thing. I had to run a synchronous ajax request which was freezing my UI. So this is how I fixed it :
__block NSString *message;
dispatch_queue_t q = dispatch_queue_create("sign up Q", NULL);
dispatch_async(q, ^{
NSString *function = [[NSString alloc] initWithFormat: #"signup(\'%#\',\'%#\',\'%#\')",self.email.text,self.password.text,self.name.text];
dispatch_async(dispatch_get_main_queue(), ^{
NSString *result = [self.webView stringByEvaluatingJavaScriptFromString:function];
NSLog(#"%#",result);
if ([result isEqualToString:#"1"]) {
message = [NSString stringWithFormat:#"Welcome %#",self.name.text];
[self.activityIndicator stopAnimating];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
else {
message = [NSString stringWithFormat:#"%# is a registered user",self.name.text];
[self.activityIndicator stopAnimating];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Message" message:message delegate:self cancelButtonTitle:#"Okay" otherButtonTitles: nil];
[alertView show];
});
});
The logic is simple. Go to a new thread, and from within that, dispatch to the main queue and then do the JS work and everything worked like a charm for me...
Anything you do with a UIWebView must be done on the main thread. It's a UI element, so this is why performSelectorInBackground breaks your app.
You could try putting that call into an NSOperation. Since you are working with a UI element, be sure to use the [NSOperationQueue mainQueue].
I have an app, where i use function FSMoveObjectToTrashSync. It works in background thread. I need ability for my app, to click on button to pause it or continue(if it paused) how i can make it?
Example of code:
NSMutableArray *fileArray = [NSMutableArray array withobjects:#"file1url", #"file2", #"file3", nil];
NSMutableArray *threadArray = [[NSMutableArray alloc] init];
-(void)myFunc{
for (NSURL *url in fileArray){
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:#selector(mySelectorWith:) object:url];
[thread start];
[threadArray addObject:thread];
}
}
-(void)mySelectorWith:(NSURL *) url{
FSRef source;
FSPathMakeRef((const UInt8 *)[[url path] fileSystemRepresentation], &source, NULL);
FSMoveObjectToTrashSync(&source, NULL, kFSFileOperationDefaultOptions);
}
PS:sorry for my english, i'm from Belarus... =(
One solution would be to replace the for loop on a single thread with an NSOperation subclass. Each operation should trash exactly one object; you then create one operation for each object you want to trash and put all of the operations on an NSOperationQueue.
The operation queue will run each operation on a thread, and it can even run multiple operations on multiple threads if it sees enough computing power laying around to do it.
An operation queue can be paused and resumed at will; when you suspend the queue, any operations in that queue that are already running will finish, but no more will start until you resume the queue.
You could use an NSConditionLock. An NSConditionLock is similar to a condition variable. It has a couple of basic methods, lockWhenCondition, and unlockWithCondition, and lock. A typical usage is to have your background thread waiting on the condition lock with "lockWhenCondition:", and the in you foreground thread to set the condition, which causes the background thread to wake up. The condition is a simple integer, usually an enumeration.
Here's an example:
enum {
kWorkTodo = 1,
kNoWorkTodo = 0
}
- (id)init {
if ((self = [super init])) {
theConditionLock = [[NSConditionLock alloc] initWithCondition: kNoWorkTodo];
workItems = [[NSMutableArray alloc] init];
}
}
- (void)startDoingWork {
[NSThread detachNewThreadSelector:#selector(doBackgroundWork) toTarget:self withObject:nil];
}
- (void)doBackgroundWork:(id)arg {
while (YES) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *items = nil;
[theConditionLock lockWhenCondition:kWorkTodo]; // Wait until there is work to do
items = [NSArray arrayWithArray:workItems]
[workItems removeAllObjects];
[theConditionLock unlockWithCondition:kNoWorkTodo];
for(id item in items) {
// Do some work on item.
}
[pool drain];
}
}
- (void)notifyBackgroundThreadAboutNewWork {
[theConditionLock lock];
[workItems addObject:/* some unit of work */];
[theConditionLock unlockWithCondition:kWorkTodo];
}
In this example, when startDoingWork is called doBackgroundWork: will start on a background thread, but then stop because there isn't any work to do. Once notifyBackgroundThreadAboutNewWork is called, then doBackgroundWork: will fire up and process the new work, and then go back to sleep waiting for new work to be available, which will happen the next time notifyBackgroundThreadAboutNewWork is called.
I have a question regarding memory management in objective-c. I have read Apple's various documents regarding this but I still don't seem to be grasping it. I have created a small sample app to demonstrate my confusion. When I launch the app Activity Monitor states that it's using about 7MB. When I execute the main loop the memory usage goes up to 44MB which is expected. However, when I release the array I only get about 14MB back in Activity Monitor. The app continues to use about 30MB. Shouldn't I get all my memory back return the "Real Memory" to 7MB in Activity Monitor?
What am I doing wrong?
Thanks in advance.
Here is the AppDelegate:
- (IBAction) buildArray:(id)sender {
values = [[NSMutableArray alloc] init]; // values is an instance variable of the appDelegate
for(int i = 0; i < 500000; ++i) {
NSString *tempString = [NSString stringWithFormat:#"New Object %i", i];
[values addObject:tempString];
}
[valuesTable reloadData]; // valuesTable is a NSTableView to diplay the array.
}
- (IBAction) clearMemory:(id)sender {
[values release];
[valuesTable reloadData];
}
- (int) numberOfRowsInTableView: (NSTableView *) tableView {
if (values) {
return [values count];
} else {
return 0;
}
}
- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) tableColumn row: (int) row {
return [values objectAtIndex:row];
}
This has been answered a multitude of times before; but the act of releasing an object does not immediately return memory to the system. The application holds on to parts of the arena that lie unused, waiting for them to be reallocated.
If they are actually requested by the system, it gives up these resources, but as long as no other process wants the memory, your application will hang on to it, "just in case".
I have a UITableView that, when items are selected, loads a viewController, which inside it performs some operations in the background using performSelectorInBackground.
Everything works fine if you slowly tap items in the tableView (essentially allowing the operations preforming in background to finish). But when you select the items quickly, the app quickly returns some memory warnings until it crashes, usually after about 7 or 8 "taps" or selections.
Any idea why this would be? When I move my code from the background thread to the main thread, everything works fine as well. You just can't make the tableView selections as quickly because it's waiting for the operations to finish.
Code snippets:
//this is called from - (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
-(void) showLeaseView:(NSMutableDictionary *)selLease
{
LeaseDetailController *leaseController = [[LeaseDetailController alloc] initWithNibName:#"LeaseDetail" bundle:nil];
leaseController.lease = selLease;
//[leaseController loadData];
[detailNavController pushViewController:leaseController animated:NO];
[leaseController release];
}
//this is in LeaseDetailController
- (void)viewDidLoad {
[self performSelectorInBackground:#selector(getOptions) withObject:nil];
[super viewDidLoad];
}
-(void) getOptions
{
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
NSArray *arrayOnDisk = [[NSArray alloc] initWithContentsOfFile:[appdel.settingsDir stringByAppendingPathComponent:#"optionData"]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(LEASE_ID contains[cd] %#)", [lease leaseId]];
self.options = [NSMutableArray arrayWithArray:[arrayOnDisk filteredArrayUsingPredicate:predicate]];
[arrayOnDisk release];
[apool release];
}
Every time you perform the getOptions selector in the background, what's really happening is a new thread is being created on your behalf, and the work is being done there. When the user taps your table cells a bunch of times in a row, a new thread is created each time to handle the work. If the work done by getOptions takes some time to complete, you will have multiple threads calling getOptions at the same time. That is to say, the system doesn't cancel previous requests to perform getOptions in the background.
If you assume that it takes N bytes of memory to perform the work done by getOptions, then if the user taps on five table cells in a row and getOptions doesn't finish right away, then you'll find that your app is using 5 * N bytes at that point. In contrast, when you change your app to call getOptions on the main thread, it has to wait for each call to getOptions to complete before it can call getOptions again. Thus when you do your work on the main thread you don't run into the situation where you're using 5 * N bytes of memory to do the work of five instances of getOptions simultaneously.
That's why you run out of memory when you do this work in the background and the user taps multiple table cells: you're doing multiple instances of the work, and each instance requires its own amount of memory, and when they all get added up, it's more than the system can spare.
It looks like you're just calling getOptions once when the user selects a table cell and navigates into a new view controller. Since the user will only be looking at one of these view controllers at a time, you don't really need to have multiple instances of getOptions going on simultaneously in the background. Instead, you want to cancel the previously-running instance before starting the new one. You can do this using an NSOperationQueue, like so:
- (NSOperationQueue *)operationQueue
{
static NSOperationQueue * queue = nil;
if (!queue) {
// Set up a singleton operation queue that only runs one operation at a time.
queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1];
}
return queue;
}
//this is called from - (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
-(void) showLeaseView:(NSMutableDictionary *)selLease
{
LeaseDetailController *leaseController = [[LeaseDetailController alloc] initWithNibName:#"LeaseDetail" bundle:nil];
leaseController.lease = selLease;
// Cancel any pending operations. They'll be discarded from the queue if they haven't begun yet.
// The currently-running operation will have to finish before the next one can start.
NSOperationQueue * queue = [self operationQueue];
[queue cancelAllOperations];
// Note that you'll need to add a property called operationQueue of type NSOperationQueue * to your LeaseDetailController class.
leaseController.operationQueue = queue;
//[leaseController loadData];
[detailNavController pushViewController:leaseController animated:NO];
[leaseController release];
}
//this is in LeaseDetailController
- (void)viewDidLoad {
// Now we use the operation queue given to us in -showLeaseView:, above, to run our operation in the background.
// Using the block version of the API for simplicity.
[queue addOperationWithBlock:^{
[self getOptions];
}];
[super viewDidLoad];
}
-(void) getOptions
{
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
NSArray *arrayOnDisk = [[NSArray alloc] initWithContentsOfFile:[appdel.settingsDir stringByAppendingPathComponent:#"optionData"]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(LEASE_ID contains[cd] %#)", [lease leaseId]];
NSMutableArray * resultsArray = [NSMutableArray arrayWithArray:[arrayOnDisk filteredArrayUsingPredicate:predicate]];
// Now that the work is done, pass the results back to ourselves, but do so on the main queue, which is equivalent to the main thread.
// This ensures that any UI work we may do in the setter for the options property is done on the right thread.
dispatch_async(dispatch_queue_get_main(), ^{
self.options = resultsArray;
});
[arrayOnDisk release];
[apool release];
}