Objective-C Memory management: ViewController in a ViewController that's inside a UITabBarController
I have a UITabBarController with three ViewControllers (A, B, and C). In ViewControllerB I have a UIScrollView. The UIScrollView consists of several instances of my PhotoViewController. These PhotoViewController objects are called from ViewControllerA, not ViewController B, where they're located.
The PhotoViewController instances have a UIImage and a two buttons. And at first, when I clicked on a button in one of my PhotoViewController instances I received a 'Thread 1: Program received signal: "EXC_BAD_ACCESS"' error. Looking around on stackoverflow that error seems to appear whenever there are memory management issues.
Since I was creating the PhotoViewController objects in a loop from a method called in ViewControllerA, and releasing those objects, I figured that by the time I switched over to ViewControllerB they were already released - and hence the memory issue.
But that's just my guess. Could you tell me if I should just stop releasing the PhotoViewController objects inside of the loop code? Because that's what I did (just commented that line out) and the program "works" fine. However, I'm still not sure if this is the proper way to handle it and if it is causing unknown memory management issues.
Here is some of my code:
ViewControllerA.m
//Creating an album in ViewControllerB, the photos in the album are PhotoViewController objects
-(IBAction)showAlbum:(UIButton *)sender
{
//Go goes here to get an album and display it in the UIScrollView
albumID = @"ALBUM_ID";
NSString* graphUrl = [NSString stringWithFormat:@"%@/photos?limit=10", albumID];
[_facebook requestWithGraphPath:graphUrl andDelegate:self];
}
...
- (void)request:(FBRequest *)request didLoad:(id)result {
//Code for array of photos
NSLog(@"%@",result);
NSString *requestType = [request.url stringByReplacingOccurrencesOfString:@"https://graph.facebook.com/" withString:@""];
if ([requestType isEqualToString:[NSString stringWithFormat:@"%@/photos?limit=10", albumID]]){
NSArray *photoAlbumArray=(NSArray*)[result valueForKey:@"data"];
[self.label setText:[NSString stringWithFormat:@"%i", [photoAlbumArray count]]];
for(UIViewController *controller in self.tabBarController.viewControllers)
开发者_JAVA技巧 {
if([controller isKindOfClass:[ViewControllerB class]])
{
ViewControllerB *mtbvc = (ViewControllerB *)controller;
[mtbvc setArray:photoAlbumArray];
self.tabBarController.selectedIndex = 1;//switch over to the second view to see if it worked
}
}
}
...
@end
ViewControllerB.m
//loop where I create PhotoViewController objects
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
arrayCount = [array count];
scroller.delegate=self;
scroller.pagingEnabled=YES;
scroller.directionalLockEnabled=YES;
scroller.showsHorizontalScrollIndicator=NO;
scroller.showsVerticalScrollIndicator=NO;
//should have an array of photo objects and the number of objects, correct?
scrollWidth = 0;
scroller.contentSize=CGSizeMake(arrayCount*scroller.frame.size.width, scroller.frame.size.height);
for (int i = 0; i < arrayCount;i++) {
PhotoViewController *pvc = [[PhotoViewController alloc] initWithNibName:@"PhotoViewController" bundle:nil];
UIImageView *scrollImageView = [[UIImageView alloc] initWithFrame:CGRectOffset(scroller.bounds, scrollWidth, 0)];
CGRect rect = scrollImageView.frame;
pvc.view.frame = rect;
[pvc view];
pvc.label.textColor = [UIColor whiteColor];
id individualPhoto = [array objectAtIndex:i];
NSLog(@"%@",individualPhoto);
NSArray *keys=[individualPhoto allKeys];
NSLog(@"%@",keys);
NSString *imageURL=[individualPhoto objectForKey:@"source"];
//here you can use this imageURL to get image-data and display it in imageView
NSURL *url = [NSURL URLWithString:imageURL];
NSData *data = [NSData dataWithContentsOfURL:url];
pvc.imageView.image = [[UIImage alloc] initWithData:data];
pvc.label.text = [individualPhoto objectForKey:@"id"];
//check to make sure the proper URL was passed
//I have an imageView next to the UIScrollView to test whether that works - it does.
[scroller addSubview:pvc.view];
[scrollImageView release];
//[pvc release];
scrollWidth += scroller.frame.size.width;
}
if (arrayCount > 3) {
pageControl.numberOfPages=3;
} else {
pageControl.numberOfPages=arrayCount;
}
pageControl.currentPage=0;
//[self.view addSubview:scroller];
}
PhotoViewController.m
#import "PhotoViewController.h"
@implementation PhotoViewController
@synthesize label, imageView;
-(IBAction)likeButton:(UIButton *)sender
{
//code goes here
for(UIViewController *controller in self.tabBarController.viewControllers)
{
if([controller isKindOfClass:[DemoAppViewController class]])
{
DemoAppViewController *davc = (DemoAppViewController *)controller;
[davc likePicture:self.label.text];
}
}
self.tabBarController.selectedIndex = 0;//switch over to the third view to see if it worked
}
-(IBAction)skipButton:(UIButton *)sender
{
//code goes here
}
-(IBAction)likeCommentButton:(UIButton *)sender
{
//code goes here
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
@end
PhotoViewController.h
#import <UIKit/UIKit.h>
#import "DemoAppViewController.h"
#import "MyTabBarViewController.h"
@interface PhotoViewController : UIViewController {
IBOutlet UILabel *label;
IBOutlet UIImageView *imageView;
UIButton *likeButton;
UIButton *skipButton;
UIButton *likeCommentButton;
}
@property (nonatomic, retain) UILabel *label;
@property (nonatomic, retain) UIImageView *imageView;
-(IBAction)likeButton:(UIButton *)sender;
-(IBAction)skipButton:(UIButton *)sender;
-(IBAction)likeCommentButton:(UIButton *)sender;
@end
To write iOS apps, it is critical that you understand the memory management rules.
In ViewControllerB, viewDidLoad, you alloc the pvc. Further down, you add the pvc's view as a subview of the scroller. This retains the pvc's view, but not the pvc itself. Then when you release the pvc, it's retain count is zero, and when you reference it later, it's gone. Crash. It seems like you need to pass in and retain a reference to the pvc in the controller that's using it.
I am not sure why you are using PhotoViewController
(Subclassing UIViewController
) instead of PhotoView
(subclassing UIView
). As you are not using any of the facility of the viewcontroller(no life cycle method and other).
If you subclass PhotoViewController
with UIView
and remove the viewcontroller's methods it will work and will not cause any memory issues as you have aleready discussed in your discussion with Rayfleck. ( it will be retained by parent view controller.)
If you are thinking about the events, then these will also be handled by view itself. But if you want to handle it in your controller then you can easily delegate, or pass your controller reference and invoke the event on it.
Thanks,
精彩评论