I have a two views, 1st is a simple view with some introduction about usage and by click of a button it opens the main view. The main view has many images and two customized tables with rows consists of text and image, thus the creation of the main view is quite slow. The profiler shows most of the time is consumed by imageIO (decode_mcu, png_read_filter_row, pmap_copy_page and decompress_onepass)
I want to optimize by creating the main view immediately after the 1st view is loaded, and when I click the button, it simply set that view to visible or bring that view to front.
I tried to alloc & init the main view in the first view's viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
rootVC = [[RootViewController alloc] initWithNibName:#"ViewController" bundle:nil];
rootVC.delegate = self;
}
and do this in the button's action method
- (IBAction)buttonUp:(id)sender {
[self.view addSubview: rootVC.view];
}
But this is not working, the loading still takes very long. How could I sacrifice some memory to make better user experience?
Thanks
Leo
You should:
Use Instruments to see where your code is spending the most time, and optimize there. It may not be where you think.
If you are using a UITableView, learn how to create UITableViewCells on demand (rather than preloading a large number) and recycle instances (rather than recreating them).
Only if Instruments points to this as the bottleneck: try loading/decompressing images in on a background queue, then updating the UIImageView (or whatever) on the main queue after loading.
In iOS 4.x or later, you can do this:
NSString *path = ...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
// This is happening in the background thread.
UIImage *image = [UIImage imageWithContentsOfFile:path];
dispatch_async(dispatch_get_main_queue(), ^{
// This is happening on the main thread; all UI updates must happen here.
imageView.image = image;
});
});
P.S. By the way, UIViewController doesn't load the view on creation, but on demand. So based on your code above, the expression rootVC.view is probably triggering the view to load. If you want to force it to load earlier, just put rootVC.view somewhere else.
UIViewController tries to load its view lazily, i.e. the view isn't loaded until it's actually needed. If you really want to force your view controller to load its view, you just need to access its view property, e.g. rootVC.view.
However, you really shouldn't need to do that. Table views, for example, only create the cells that are visible on screen, so it's unlikely that the number of rows is significantly impacting the time to load the view. One caveat here is that there are some things that can really slow down a table view. For example, if you're using variable-height cells in a table with a lot of rows, that can really slow down table creation. Try not to do that.
#benzado gives some good pointers for improving performance. Trying to work against Cocoa-Touch is always a recipe for pain, so try instead to figure out why your view is taking so long to load. Where is your app spending all that time? What can you do to reduce that load?
Related
I have seen a few posts on this subject here on SO, but no definitive answers. Here is my problem.
I have a UINavigationController which I use as a gallery. On the first controller I load up a bunch of remote images. This increases my memory size, but not by that much. When clicking an image, it will push on another viewController which has images for the gallery just clicked. This might load in another 1MB or more of data from those images.
The problem here is that a user might browse any number of these galleries. Since when I pop the viewController, that memory is not released I start to get too much memory usage in my app when the users continues to browse the galleries.
Is there any way that I can release this memory when I pop my viewController? Perhaps in my viewDidDisappear: method? If so, what would I release? And how can I create it again? I tried this to a point, such as releasing my view, but I get crashes.
Any insight into this issue?
PhotosGalleryiPad *gallery = [[PhotosGalleryiPad alloc] init];
gallery.items = self.items;
gallery.asset = self.currentAsset;
[self.navigationController pushViewController:gallery animated:YES];
[gallery release];
You normally release any memory in the dealloc method of the view controller that gets popped off the stack.
However, if you're talking about images and you loaded them with [UIImage imageNamed:] then UIKit may be caching them. Try faking a memory warning in the simulator (from the Hardware menu) and see if that unloads these cached images.
You can also do a heapshot analysis in Instruments. Mark the heap before loading the view controller, mark it again after closing the view controller, and see which objects stick around.
Where, exactly, are you retaining these gallery images?
Let me take a guess that gallery.items is a mutable collection (possibly an array) holding the images. As the user visits new galleries, more images are added to this array. From view controller to view controller, you are passing a pointer to this array:
gallery.items = self.items;
So, when you pop the view controller, you are still left with the same, enlarged, array. The issue, then, is how to cull this array of the newly added images when you pop a view controller.
Rather than passing a pointer, you could make a shallow copy of the collection. If it's a mutable array, you could do as follows:
gallery.items = [self.items mutableCopyWithZone:nil];
Then, when a view controller is popped, its items array is released. The objects added by the popped view controller are released, but the old objects are still retained in the previous view controller's items array.
(I'm just taking a guess. If I'm wrong, it would be helpful if you explained where you're retaining these images.)
If you are "popping" views onto the foreground like this:
infoScreen = [[[infoScreen alloc] initWithNibName:#"InfoScreen" bundle:nil] autorelease];
infoScreen.view.center = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
[self.view sendSubviewToBack:self.view];
Then you would do [infoScreen release];
to release/free the view from memory.
Remember anything you alloc, or retain (there are other situations too but i forget them off hand) you need to release.
I implemented the LazyTableImages project (link) by Apple, but in my version I used RestKit to obtain the data and my UItableviewcontroller was push onto navigation stack.
So I eschew whatever apple does in the app delegate to get the xml. I dont think that's the problem. My problem is that when you back out of the UITableviewcontroller using either the nav back button or accessing another tabbar item and coming back, the images that were loaded there previously show up, but immediately it loads the placeholder image. Basically, the opposite happens.
It's like the UITableview cached data, so when you come back it interferes with the Lazy Table Images. I need to know has anyone implemented this code where they had to back out?
EDIT:
Looks like imageDownloader is not nil the second time, which prevents the image from loading. I'm still figuring out how to bypass it. Of course, I can just take out the condition, but I dont know if that is "bad" for performance.
imageDownloadsInProgress, a mutable dictionary, still has all of its data even if you back out. It has become a different question now, how do I delete imageDownloadsInProgress if a user hits back or strays from the current view.
imageDownloadsInProgress is retained, but I added [imagesDownloadsInProgress release] in the dealloc method, however I don't think that runs.
-(void)startEventImageDownload:(WhatsonEvent *)eventRecord forIndexPath:(NSIndexPath *)indexPath
{
EventImageDownloader *imageDownloader = [imageDownloadsInProgress objectForKey:indexPath];
if(imageDownloader == nil)
{
NSLog(#"%#",eventRecord.title);
imageDownloader = [[EventImageDownloader alloc] init];
imageDownloader.eventRecord = eventRecord;
imageDownloader.indexPathInTableView = indexPath;
imageDownloader.delegate = self;
[imageDownloadsInProgress setObject:imageDownloader forKey:indexPath];
[imageDownloader startDownload];
[imageDownloader release];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
}
The way I do it is to build my own cache and save the images in the user documents directory. When I call [tableView reloadData] (you are calling that, right?) it first checks for each cell if the image is there locally, otherwise it will lazily load them from the feed. Tell me if you need code for this.
The problem was that the self.imageDownloadsInProgess = [NSMutableDictionary dictionary] was placed in the ViewDidLoad method with the intention of resetting the dictionary every time. However, if you place the code within a view pushed onto a navigation controller, the ViewDidLoad only executes the first time (I'm not positive that is the case). I added the line to ViewWillAppear since it runs every time the view is placed on screen.
I have an iPad App which shows a whole load of information and related images. For instance: I can access to the section “Events”, from which I can choose one. Every single one has different dates or side events. Once I choose the side event, I charge some small views, which contain the title of the news; a small image as a preview and a short text in a ViewController (which has a ScrollView). This previews can be a lot. There are no leaks due to my code (I analized with Instruments and the only ones I can find are due to NSXMLParser bug), but I have noticed that the live bytes don’t slow down, ever, they only grow. Every time I have to update the content of the ScrollView I take care of inserting the following code:
if ( bannerVideo ) {
[banner release];
banner = nil;
}
Do you by any chance know what could be the reason for such a constant growth?
SOLVED
I added the ViewController "bannerVideo" as a subview to the main ViewController. This causes the retainCount to increase by one. So, when I asked for a new view:
if ( something ) {
[[bannerVideo view] removeFromSuperview];
[bannerVideo release];
bannerVideo = nil;
}
Yes, the main access to the heap area for bannerVideo was released, but it was still retained by the main ViewController View.
I am trying to dynamically add subviews to a UiScorllView in real time (to save memory).
Doing so causes the scrollview to hang for a brief second... what is the way around this?
any hints appreciated
Read up on the how the UITableView solves this problem. Your biggest performance hits here are the allocation and initial drawing of the subview, but primarily the allocation. UITableViews use a reuse identifier and an in-memory heap to keep all its cells in memory during scrolling so it can just reuse cells that have already been allocated instead of re-allocating new ones every time a new cell scrolls into the viewable area.
Perhaps you could implement your own subview reuse system to save yourself all that allocation time. Again, read up on specifically how the UITableView does it and model yours off of that. Unfortunately, there really is no easier solution than that that I can think of.
I suggest that any action that could possibly hang the user interface should be placed in a thread. This way, the process of adding the subview will be done in the background, not disturbing the main thread.
You could either do this by NSThread or you could implement the NSOperationQueue.
Declaration of the NSThread is simple;
[NSThread detachNewThreadSelector:#selector(myFunction:) toTarget:myObject withObject:argumentObject];
Where 'myFunction' should be replaced with the name of a function, the 'myObject' with an object (or simply state self if you want the current class to handle the thread) and 'argumentObject' should be replaced with any object you want to pass along as an argument; this may be nil
The function itself should look like this;
-(void)myFunction {
NSAutoReleasePool *pool = [[NSAutoReleasePool alloc] init];
// do your thing here...
[pool release];
}
Every thread should have an autorelease pool.
One hint; if your subview has to animate, try declaring a UIAnimation block
How do I create a loading screen that can be reused at any given time. I'm aware of the Default.png but I need the flexibility to plug in a loading screen at any point during the application life cycle.
This is what I have thus far.
//inside a method that gets called by a UIButton
LoadingViewController* loadController = [[LoadingViewController alloc] initWithNibName:#"Loading" bundle:nil vertical:NO];
[self.view addSubview: loadController.view];
//some method call that takes a few seconds to execute
[self doSomething];
//This loads some other view, my final view
[self.view addSubview: someOtherView]
but it seems that the loading view is never displayed. Instead the previous view stays there until the "someOtherView" gets added. I put trace logs and the code does seem to get executed, I even replaced [self doSomething] with a sleep(2), but the intermediate loading view is never displayed.
If I remove [self.view addSubview:someOtherView]; then after a few seconds...(after doSomething finishes executing) the load view is displayed since there is no view that is pushed on top of it, however this is obviously not the functionality I want.
Can explain this behavior? Is there something about the rendering cycle that I am misunderstanding because it doesn't seem like the view (on the screen at least) is instantly updated, even though I call a [self.view addSubview: loadController.view];
Would I need to create a separate thread?
In general, for changes in the UI to be made visible to the user, control must return to the main runLoop. You are only returning to the runLoop after taking the loading view down and replacing it with the other view. One strategy for dealing with this is to move the code that does the loading onto another thread. NSOperation and NSOperationQueue can be used for this.
An even simpler approach is to use performSelectorInBackground:withObject to do the processing. Once processing is complete the UI can be updated again to show the data. It is important to remember that the UI updates must be carried out on the main thread. Use performSelectorOnMainThread:withObject:waitUntilDone: to accomplish this from the loading thread.
This sounds like a lot of complication but it is really as simple as breaking your single method up into three separate methods as follows:
Display the loading view and start the background process - this is the button action method.
Do the background loading - called from the button action function with performSelectorInBackground:withObject.
Remove the loading view and update the display with the data - called from the background thread with performSelectorOnMainThread:withObject:waitUntilDone.
I created a subclass of UIView where I initialized how my loading-view should work and look like. (My view appeared and slided in from the bottom with an nice animation).
I then added code that handled whether the loading-view should be visible or not in a subclass of UIViewController.
I then let all my viewcontrollers be an subclass of my new viewcontrollerclass which made it possible for me to do:
[self showloadingMessage:#"loading..."];
in all my viewcontrollers...