开发者

Xcode's "build and analyze" reports odd memory leaks for this code

Does anyone know why xCode's "build and analyze" would report this line as a "possible memory leak"?

goodSound = [[SoundClass alloc] initializeSound:@"Good.wav"];

///// Here are the 4 files in question:

//  Sounds.h 

#import <Foundation/Foundation.h> 
#import <AudioToolbox/AudioToolbox.h> 

@interface SoundClass : NSObject  
{ 
    SystemSoundID soundHandle; 
} 

-(id) initializeSound:(NSString *)soundFileName; 
-(void) play;    

@end 

/////////////

//  Sounds.m 

#import "Sounds.h" 

@implementation SoundClass 

-(id) initializeSound:(NSString *)soundFileName 
{ 
    self = [super init]; 

    NSString *const resourceDir = [[NSBundle mainBundle] resourcePath]; 
    NSString *const fullPath    = [resourceDir stringByAppendingPathComponent:soundFileName]; 
    NSURL *const url            = [NSURL fileURLWithPath:fullPath]; 

    OSStatus errCode = AudioServicesCreateSystemSoundID((CFURLRef) url, &soundHandle); 

    if(errCode == 0) 
        NSLog(@"Loaded sound: %@", soundFileName); 
    else 
        NSLog(@"Failed to load sound: %@", soundFileName); 

    return self;             
} 

////////////////////////////// 

-(void) play     
{ 
    AudioServicesPlaySystemSound(sound开发者_高级运维Handle); 
} 

///////////////////////////// 

-(void) dealloc 
{ 
    AudioServicesDisposeSystemSoundID(soundHandle); 
    [super dealloc]; 
} 

///////////////////////////// 

@end 

//////////////

//  MemTestViewController.h 

#import <UIKit/UIKit.h> 

@class SoundClass; 

@interface MemTestViewController : UIViewController  
{ 
    SoundClass *goodSound; 
} 

-(IBAction) beepButtonClicked:(id)sender; 

@end 

///////////

//  MemTestViewController.m 

#import "MemTestViewController.h" 
#import "Sounds.h" 

@implementation MemTestViewController 

- (void)viewDidLoad  
{ 
    NSLog(@"view did load: alloc'ing mem for sound class"); 

    // "build and analyze" says this is possibly a memory leak: 
    goodSound = [[SoundClass alloc] initializeSound:@"Good.wav"]; 

    [super viewDidLoad]; 
} 


-(IBAction) beepButtonClicked:(id)sender 
{ 
    NSLog(@"beep button clicked"); 

    [goodSound play]; 
} 


- (void)didReceiveMemoryWarning  
{ 
    [super didReceiveMemoryWarning]; 
} 


- (void)dealloc  
{ 
    [goodSound release]; 
    [super dealloc]; 
} 

@end 


It is a possible memory leak. viewDidLoad may be called any number of times if the view is unloaded, in which case you will leak memory each time. To guard, you can either release the memory in viewDidUnload and set the ivar to nil, or you can simply initialize the sound in viewDidLoad only if the ivar actually is nil.

So:

- (void)viewDidLoad
{
  if( !goodSound )
    goodSound = [[SoundClass alloc] initializeSound:@"Good.wav"];
  [super viewDidLoad];
}

And/or:

- (void)viewDidUnload
{
  [goodSound release];
  goodSound = nil;
  [super viewDidUnload];
}


The static analyzer is right; your code is a possible memory leak. Because static analysis can't prove that MemTestViewController's dealloc method will always be called (doing so would solve the halting problem, afterall), there is no way for the static analyzer to be sure that goodSound will be correctly released.

Modern Objective-C rarely uses direct ivar access accept in init and dealloc methods. You should declare a @property (retain) for your goodSound, even if it's private, and use that elsewhere in your class, including in the viewDidLoad method. The static analyzer should then correctly recognize this pattern and not flag the line as a warning.

On a side note—just in case you're interested—your -[SoundClass initializeSound:] should be named something like -[SoundClass initWithSoundFileName:] to match convention, and should be written with a guard to test for a nil return from [super init]:

-(id)initWithSoundFileName:(NSString*)soundFileName
{ 
    self = [super init]; 

    if(self != nil) {
      NSString *const resourceDir = [[NSBundle mainBundle] resourcePath]; 
      NSString *const fullPath    = [resourceDir stringByAppendingPathComponent:soundFileName]; 
      NSURL *const url            = [NSURL fileURLWithPath:fullPath]; 

      OSStatus errCode = AudioServicesCreateSystemSoundID((CFURLRef) url, &soundHandle); 

      if(errCode == 0) 
        NSLog(@"Loaded sound: %@", soundFileName); 
      else 
        NSLog(@"Failed to load sound: %@", soundFileName); 
    }

    return self;             
} 

It's always best to add this guard, especially because you are calling into C code that might not handle nil/NULL as nicely as Objective-C.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜