我在UIScrollView实现中泄漏了内存吗? [英] Am I leaking memory in my UIScrollView implementation?
问题描述
我正在开发一个使用UIScrollView滚动浏览一堆幻灯片的应用。当应用程序打开时,它会创建幻灯片并将它们传递给滚动视图。我的应用程序还有一个计时器,使UIScrollView滚动幻灯片。
I am working on an app which uses a UIScrollView to scroll through a bunch of slides. When the app opens, it creates the slides and passes them to the scroll view. My app also has a timer which causes the UIScrollView to scroll through the slides.
我还有一个设置按钮。当我按下该按钮时,它会打开设置面板。计时器无效并设置为 nil
。当设置面板打开时,用户可以更改应用程序主题,幻灯片和动画方向等内容。
I also have a settings button. When I press on that button, it opens the settings panel. The timer is invalidated and set to nil
. When the settings panel opens, the user can change things like the application theme, the slides and the animation direction.
当关闭设置面板时,将应用设置:从UIScrollView中删除幻灯片,并为UIScrollView提供新的NSArray幻灯片以供使用。然后,UIScrollView以正确的顺序和方向布置新幻灯片。计时器被重置。
When the settings panel is closed, the settings are applied: The slides are removed from the UIScrollView and the UIScrollView is given a new NSArray of slides to work with. The UIScrollView then lays out the new slides in the proper order and direction. The timer is reset.
一旦大约每14-16次,应用程序在运行 dismissAndRestart
时崩溃( 关闭代码)。我不知道为什么。我以为我是在泄漏记忆,显然我是,但我看不到。
Once, approximately every 14-16 times, the app crashes while running dismissAndRestart
(the "close" code). I'm not sure why. I thought I was leaking memory, and apparently I am, but I don't see where.
这是我的代码:
这在启动时运行:
- (void)viewDidLoad {
[scrollView setPagingEnabled:YES];
}
- (void) viewWillAppear:(BOOL)animated{
[self prepareViews];
[self applyTheme];
}
- (void) viewDidAppear:(BOOL)animated{
[self createTimerWithInterval:kScrollInterval];
}
将视图加载到滚动条的主力方法。 (它也删除了旧的):
- (void)loadViews:(NSArray *)views IntoScroller:(UIScrollView *)scroller withDirection:(NSString *)direction{
[scrollView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
[scrollView setShowsHorizontalScrollIndicator: NO];
[scrollView setShowsVerticalScrollIndicator:NO];
scrollView.scrollsToTop = NO;
if([direction isEqualToString:@"horizontal"]){
scrollView.frame = CGRectMake(0, 0, 1024, 768);
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * [[NSNumber numberWithUnsignedInt:[views count]] floatValue], scrollView.frame.size.height);
}else if([direction isEqualToString:@"vertical"]){
scrollView.frame = CGRectMake(0, 0, 1024, 768);
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width, scrollView.frame.size.height * [[NSNumber numberWithUnsignedInt:[views count]] floatValue]);
}
for (int i=0; i<[[NSNumber numberWithUnsignedInt:[views count]] intValue]; i++) {
[[[views objectAtIndex:[[NSNumber numberWithInt:i] unsignedIntValue]] view] setFrame:scrollView.frame];
if([direction isEqualToString:@"horizontal"]){
[[[views objectAtIndex:[[NSNumber numberWithInt:i] unsignedIntValue]] view] setFrame:CGRectMake(i * [[views objectAtIndex:[[NSNumber numberWithInt:i] unsignedIntValue]] view].frame.size.width, 0, scrollView.frame.size.width, scrollView.frame.size.height)];//CGRectMake(i * announcementView.view.frame.size.width, -scrollView.frame.origin.x, scrollView.frame.size.width, scrollView.frame.size.height)];
}else if([direction isEqualToString:@"vertical"]){
[[[views objectAtIndex:[[NSNumber numberWithInt:i] unsignedIntValue]] view] setFrame:CGRectMake(0, i * [[views objectAtIndex:[[NSNumber numberWithInt:i] unsignedIntValue]] view].frame.size.height, scrollView.frame.size.width, scrollView.frame.size.height)];
}
[scrollView addSubview:[[views objectAtIndex:[[NSNumber numberWithInt:i] unsignedIntValue]] view]];
}
}
创建自动滚动的计时器:
#pragma mark -
#pragma mark Create the Timer to automate scrolling
- (void) createTimerWithInterval:(float)interval{
self.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(scrollWrapperForTimer) userInfo:nil repeats:YES];
}
计时器功能的包装函数。 (在NSUserDefault方向之前这是必要的,现在我不需要这个。)
#pragma mark -
#pragma mark Scroll Automatically Every N seconds
- (void) scrollWrapperForTimer{
[self scrollToNewViewInDirection:kDirection];
}
- (void)scrollToNewViewInDirection:(NSString *)direction{
if([[NSUserDefaults standardUserDefaults] boolForKey:@"animated_scrolling"] == YES){
if([direction isEqualToString:@"horizontal"]){
if([[NSNumber numberWithFloat:scrollView.contentOffset.x] compare:[NSNumber numberWithFloat:scrollView.contentSize.width - scrollView.frame.size.width]] == (NSOrderedAscending)){
[scrollView scrollRectToVisible:CGRectMake(scrollView.contentOffset.x + scrollView.frame.size.width, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:YES];
}else if([[NSNumber numberWithFloat:scrollView.contentOffset.x] compare:[NSNumber numberWithFloat:scrollView.contentSize.width - scrollView.frame.size.width]] == (NSOrderedSame)){
[scrollView scrollRectToVisible:CGRectMake(0, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:YES];
}
}else if([direction isEqualToString:@"vertical"]){
if([[NSNumber numberWithFloat:scrollView.contentOffset.y] compare:[NSNumber numberWithFloat:scrollView.contentSize.height - scrollView.frame.size.height]] == (NSOrderedAscending)){
[scrollView scrollRectToVisible:CGRectMake(0, scrollView.contentOffset.y + scrollView.frame.size.height, scrollView.frame.size.width, scrollView.frame.size.height) animated:YES];
}else if([[NSNumber numberWithFloat:scrollView.contentOffset.y] compare:[NSNumber numberWithFloat:scrollView.contentSize.height - scrollView.frame.size.height]] == (NSOrderedSame)){
[scrollView scrollRectToVisible:CGRectMake(0, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:YES];
}
}
}else {
if([direction isEqualToString:@"horizontal"]){
if([[NSNumber numberWithFloat:scrollView.contentOffset.x] compare:[NSNumber numberWithFloat:scrollView.contentSize.width - scrollView.frame.size.width]] == (NSOrderedAscending)){
[scrollView scrollRectToVisible:CGRectMake(scrollView.contentOffset.x + scrollView.frame.size.width, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:NO];
}else if([[NSNumber numberWithFloat:scrollView.contentOffset.x] compare:[NSNumber numberWithFloat:scrollView.contentSize.width - scrollView.frame.size.width]] == (NSOrderedSame)){
[scrollView scrollRectToVisible:CGRectMake(0, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:NO];
}
}else if([direction isEqualToString:@"vertical"]){
if([[NSNumber numberWithFloat:scrollView.contentOffset.y] compare:[NSNumber numberWithFloat:scrollView.contentSize.height - scrollView.frame.size.height]] == (NSOrderedAscending)){
[scrollView scrollRectToVisible:CGRectMake(0, scrollView.contentOffset.y + scrollView.frame.size.height, scrollView.frame.size.width, scrollView.frame.size.height) animated:NO];
}else if([[NSNumber numberWithFloat:scrollView.contentOffset.y] compare:[NSNumber numberWithFloat:scrollView.contentSize.height - scrollView.frame.size.height]] == (NSOrderedSame)){
[scrollView scrollRectToVisible:CGRectMake(0, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:NO];
}
}
}
}
在设置面板中按下完成按钮时会调用此方法。
#pragma mark -
#pragma mark Restart the program
- (void) dismissAndRestart{
[self prepareViews];
[self applyTheme];
[self dismissModalViewControllerAnimated:YES];
[self createTimerWithInterval:kScrollInterval];
}
这会创建正确的图像文件并将其加载到位:
#pragma mark -
#pragma mark Apply the theme to the main view
- (void) applyTheme{
//Front panel
UIImage *frontImage = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[[NSString stringWithFormat:@"%@_front", kTheme]description] ofType:@"png"]];
[self.overlayImage setImage:frontImage];
[frontImage release];
//Back Panel
if([kTheme isEqualToString:@"walnut"]){
[self.backgroundImage setHidden:NO];
UIImage *backImage = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[[NSString stringWithFormat:@"%@_back", kTheme]description] ofType:@"png"]];
[self.backgroundImage setImage:backImage];
[backImage release];
}else if([kTheme isEqualToString:@"metal"]){
[self.backgroundImage setHidden:YES];
[self.view setBackgroundColor: [UIColor scrollViewTexturedBackgroundColor]];
}
//Gabbai Button
UIImage *gabbaiImage = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[[NSString stringWithFormat:@"%@_settings_button", kTheme]description] ofType:@"png"]];
[self.gabbaiButton setImage:gabbaiImage forState:UIControlStateNormal];
[gabbaiImage release];
}
需要的另一个包装函数待重构:
#pragma mark -
#pragma mark Slide View related
- (void) prepareViews{
[self loadViews:[self createandReturnViews] IntoScroller:scrollView withDirection:kDirection];
}
创建要传递给滚动条的幻灯片数组:
//recreation of the views causes a delay each time the admin panel is closed.
- (NSArray*) createandReturnViews{
NSMutableArray *announcements = [NSMutableArray array];
NSArray *announcementsArray = [NSArray arrayWithObjects: @"Welcome to\nGabbai HD!",
@"First slide",
@"Second Slide",
@"Third Slide",
@"etc.",
nil];
for (int i = 0; i<[[NSNumber numberWithUnsignedInteger:[announcementsArray count]] intValue]; i++) {
MBAnnouncementViewController *announcement = [[MBAnnouncementViewController alloc] initWithNibName:@"MBAnnouncementViewController" bundle:nil];
[announcement setAnnouncementText:[announcementsArray objectAtIndex:[[NSNumber numberWithInt:i] unsignedIntegerValue]]];
[announcements addObject:announcement];
[announcement release];
}
return announcements;
}
为什么我的设置面板会在关闭时崩溃应用程序?
Why would my settings panel be crashing the app on close?
推荐答案
您展示的代码有几个alloc调用。在每个之后,您设置一个属性,然后释放。我假设该属性声明为 retain
。
The code you show has a few alloc calls. After each one, you set a property, and release. I assume that the property is declared as retain
.
如果是这样,那么最终需要释放这些属性。通常在dealloc消息实现中。
If so, then eventually, you need to release these properties. Usually in the dealloc message implementation.
具有保留属性的类需要像这样的dealloc
Your classes with retained properties need a dealloc like this
-(void) dealloc() {
self.prop = nil; // calls release
self.prop2 = nil;
[super dealloc];
}
如果您的房产声明如此
@property (nonatomic, retain) Type* prop;
然后当您使用设置$ c $重新分配属性时c> message或
self.prop
语法,前一个值已经调用了它。如果你只是使用 prop = newVal;
,那么你是直接访问该字段而不是调用 set
消息,所以不会调用release。
Then when you reassign the property with either the set
message or self.prop
syntax, the previous value has release called on it. If you just use prop = newVal;
, then you are accessing the field directly and not calling the set
message, so release will not be called.
通常,总是使用 set
消息或点语法,这样你就不会需要担心它 - 这是你将属性声明为保留的主要原因,所以要利用它。
Generally, always use the set
message or dot syntax, so that you don't need to worry about it -- it's the main reason you declare the property as retain, so take advantage of it.
这篇关于我在UIScrollView实现中泄漏了内存吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!