I am in the process of writing an iPhone app, and am having a few problems with the memory. Here is the code below:
NSURL *url = [curItem valueForProperty: MPMediaItemPropertyAssetURL];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL: url options:nil];
NSError *error = nil;
AVAssetReader* reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];
AVAssetTrack* track = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
NSMutableDictionary* audioReadSettings = [NSMutableDictionary dictionary];
[audioReadSettings setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM]
forKey:AVFormatIDKey];
AVAssetReaderTrackOutput* readerOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:track outputSettings:audioReadSettings];
[reader addOutput:readerOutput];
[reader startReading];
CMSampleBufferRef sample = [readerOutput copyNextSampleBuffer];
while( sample != NULL)
{
sample = [readerOutput copyNextSampleBuffer];
}
CFRelease(sample);
I am reading songs from the user's iTunes library (curItem is the current song), and if I leave the last line: CFRelease(sample) in the code, the program will stop - no error is shown - it just crashes. If I comment out the line, I of course run into memory problems, and the code crashes on about the fourth song after getting "Received memory warning."
What am I doing wrong?
The naming convention copyNextSampleBuffer implies that you own the object returned so you are correct to release it, but you are calling the copyNextSampleBuffer method multiple times in a loop and overwriting the previous copy without releasing it.
When you do finally call CFRelease, you are calling it on a variable that you have just checked to be NULL. According to this StackOverflow answer, calling CFRelease on NULL is not safe, so that's why you're crashing:
What you need to do instead is call release inside your while loop before you overwrite the variable, like this:
CMSampleBufferRef sample = [readerOutput copyNextSampleBuffer];
while( sample != NULL)
{
CFRelease(sample);
sample = [readerOutput copyNextSampleBuffer];
}
If that doesn't fix your crash (and even if it does), try running the static analyser over your code (select Analyze in the product menu in Xcode) and see if it reports any potential leaks or over-releases. Remember, every yellow and blue warning you get is a potential crash so try to fix them all.
EDIT: It just occurs to me that your loop doesn't make much sense - why are you reading samples over and over and then just throwing them away? Did you perhaps get the NULL check wrong in your while loop and you actually meant to write this instead?
CMSampleBufferRef sample = [readerOutput copyNextSampleBuffer];
while( sample == NULL)
{
sample = [readerOutput copyNextSampleBuffer];
}
CFRelease(sample);
That should also be fine as in that case you are explicitly checking that the sample is not NULL before releasing it. Although you're still throwing away the sample before doing anything with it, and you also risk an infinite loop if the readerOutput contains no samples.
Use either autorelease or ARC to get rid off the "too early release syndrom". In both cases the release-task is managed by someone else. For a new project I'd suggest ARC.
Related
I read the image from the photo library and I get the metadata using the assets library. I then try to read the user comment exif tag and display it in my text view. Code is here:
[assetLibrary assetForURL:assestURL resultBlock:^(ALAsset *asset) {
ALAssetRepresentation *representation = [asset defaultRepresentation];
NSMutableDictionary *metadataDictPhoto = (NSMutableDictionary*)[representation metadata];
NSLog(#"This is the read metadata I believe: %#",[metadataDictPhoto description]);
metadataDictPhoto = metadataGlobal;
} failureBlock:^(NSError *error) {
NSLog(#"%#",[error description]);
}];
NSMutableDictionary *exifDictionary = (NSMutableDictionary*)[metadataGlobal objectForKey:(NSString*) kCGImagePropertyExifDictionary];
NSString *comment = (NSString*)[exifDictionary valueForKey:(NSString*)kCGImagePropertyExifUserComment];
textView.text = comment;
When I run it, there is no crashes but nothing is displayed in the textview. I have verified using NSLogs that the metadata received from my code is correct, as in I can see my custom exif user comment tag. If I place my mouse over comment it gives me the error . I can't get rid of this.
How can I read the string from the metadata dictionary and get it to display in the textView?
EDIT: DeePak Noticed that I mixed up an assignment statement and I changed it, but that did not fix the issue. While I was looking into this though I found that my NSLogs show that the code is that reads the metadata is passed over and then it runs the dictionary and string code which at this point the metadata isn't create. It then completes the imagepicker delegate function and then it eventually goes to the complete block and then runs the code and ouputs the dictionary and then I can see that everything is correct.
How could I get the code to run immediantly or have the program wait ntil the assetforurl completes?
You have this assignment flipped.
metadataDictPhoto = metadataGlobal;
You need to change this to
metadataGlobal = metadataDictPhoto;
metadataGlobal is probably an instance variable which is why it is not crashing as it continues to be nil here –
NSMutableDictionary *exifDictionary = (NSMutableDictionary*)[metadataGlobal objectForKey:(NSString*) kCGImagePropertyExifDictionary];
I fixed it by adding in the code outside of the completion block to inside the completion block and made sure all the variables were saved to be used then. This worked perfectly.
Now only if I could figure out why it is not writing IPTC correctly I would be set.
I would like to asynchronously load the duration, time (timestamp the video was created) and locale of an Asset.
All of the sample code shown by Apple for the usage of 'loadValuesAsynchronouslyForKeys:keys' is always shows with only one key. ie:
NSURL *url = aUrl;
AVAsset asset = [[AVURLAsset alloc] initWithURL:url options:nil];
NSArray *keys = [NSArray arrayWithObject:#"duration"];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() {
NSError *error = nil;
AVKeyValueStatus durationStatus = [asset statusOfValueForKey:#"duration" error:&error];
switch (durationSatus) {
case AVKeyValueStatusLoaded:
// Read duration from asset
CMTime assetDurationInCMTime = [asset duration];
break;
case AVKeyValueStatusFailed:
// Report error
break;
case AVKeyValueStatusCancelled:
// Do whatever is appropriate for cancelation
}
}];
Can I assume that if one item's status is 'AVKeyValueStatusLoaded', the other values can be read at the same time in the completion block? ie:
[asset tracks]
[asset commonMetadata];
[asset duration]
No, you can't assume that. One of my methods looks at two keys, playable and duration, and I have found that playable is often available while duration isn't yet. I therefor have moved the loadValuesAsynchronouslyForKeys: code into a separate method shouldSave:. The shouldSave: method I call from a timer in a method called saveWithDuration:. Once saveWithDuration: receives a non-zero duration, it goes ahead and saves stuff. To avoid waiting too long, I use an attempt counter for now -- in the future, I'll refine this (you'll notice that the error instance isn't really used at the moment)
- (void)shouldSave:(NSTimer*)theTimer {
NSString * const TBDuration = #"duration";
NSString * const TBPlayable = #"playable";
__block float theDuration = TBZeroDuration;
__block NSError *error = nil;
NSArray *assetKeys = [NSArray arrayWithObjects:TBDuration, TBPlayable, nil];
[_audioAsset loadValuesAsynchronouslyForKeys:assetKeys completionHandler:^() {
AVKeyValueStatus playableStatus = [_audioAsset statusOfValueForKey:TBPlayable error:&error];
switch (playableStatus) {
case AVKeyValueStatusLoaded:
//go on
break;
default:
return;
}
AVKeyValueStatus durationStatus = [_audioAsset statusOfValueForKey:TBDuration error:&error];
switch (durationStatus) {
case AVKeyValueStatusLoaded:
theDuration = CMTimeGetSeconds(_audioAsset.duration);
break;
default:
return;
}
}];
NSUInteger attempt = [[[theTimer userInfo] objectForKey:TBAttemptKey] integerValue];
attempt++;
[self saveWithDuration:theDuration attempt:attempt error:&error];
}
Technically you can't. The docs for loadValuesAsynchronouslyForKeys:completionHandler: says that
The completion states of the keys you
specify in keys are not necessarily
the same—some may be loaded, and
others may have failed. You must check
the status of each key individually.
In practice, I think this is often a safe assumption -- as you've noted, Apple's StitchedStreamPlayer sample project just looks at the status of the first key.
No you cannot assume so. I usually rely on #"duration" key to create an AVPlayerItem and start playback since loading of #"playable" generally doesn't guarantee that the asset is ready yet. Then I spawn a timer to check periodically whether others keys such as #"tracks" are loaded or not similar to what Elise van Looij has mentioned above.
Also, side note - do remember that the completionHandler in loadValuesAsynchronouslyForKeys is called on an arbitrary background thread. You will have to dispatch it to main thread if you are dealing with UI or AVPlayerLayer.
The following code for some reason poradically works. I have checked the URL so many times it's not funny (It returns plain text that I would like to parse). The code was 100% functional then it just stopped working and started giving me a EXC_BAD_ACCESS error.
There is nothing in the debugging output to post other than a line saying the output is switching to the process twice. (Except sometimes something about a double release.)
So far (as much as I can remember) I have tried:
Reinstalling the app - it only has problems on the 'Default' run (not the first Run/initiate Run.)
Running the URL in the browser (chrome, firefox, IE...)
Putting the call in a #try / #catch block
Using retain
Using a temp NSAutoreleasePool
Splitting up / separating the elements of the call (along with loggin Everything - once it hits the error, nothing gets logged)
Using the dataWithContentsOfURL functions with the above
NSAutoreleasePool *tmpPool = [[NSAutoreleasePool alloc] init];
NSString *url_string = [self getNormalVersionDownloadURL];
NSLog(#"urlString: -%#-", url_string);
NSError *er;
NSURL *the_URL = [[NSURL URLWithString:url_string] retain];
NSString *version_String = [NSString stringWithContentsOfURL:the_URL encoding:NSASCIIStringEncoding error:&er];
NSLog(#"verions_string: -%#-", version_String);
if ([version_String length] < 16)
return;
[tmpPool release];
(NSAutoreleasePool and autorelease added due to http://discussions.apple.com/thread.jspa?threadID=1667544)
(Cashed page - http://webcache.googleusercontent.com/search?q=cache:8D7zlQdG9PMJ:discussions.apple.com/thread.jspa%3FthreadID%3D1667544+http://discussions.apple.com/thread.jspa%3FthreadID%3D1667544&cd=1&hl=en&ct=clnk&gl=us&source=www.google.com)
discussions.apple.com is currently down so I cannot read the discussion thread. At any rate:
NSString *url_string = [[self getNormalVersionDownloadURL] autorelease];
Does -getNormalVersionDownloadURL return an owned or a non-owned object? You only send -autorelease if the method returns an owned object.
NSError **er;
This should be NSError *er instead, or it should be initialised with the address of a variable of type NSError *. Since the latter is uncommon and unnecessary, the following assumes NSError *er.
NSURL *the_URL = [[NSURL URLWithString:url_string] autorelease];
+URLWithString: returns an NSURL object that you don’t own, hence you don’t (auto)release it.
version_String = [[NSString stringWithContentsOfURL:the_URL
encoding:NSASCIIStringEncoding error:er] autorelease]; //ERROR occurs here
Two problems:: +stringWithContentsOfURL: returns an NSString object that you don’t own, hence you don’t (auto)release it. Furthermore, the third parameter should be &er instead of er.
URLWithString and stringWithContentsOfURL are convenience methods and then already put the variable in autorelease I don't think you need to add autorelease while creating the_URL and version_String
try to remove autorelease ...
I have a library of 16 short sound clips I need to be able to play in quick succession. I realized that creating and preparing AVAudioPlayer objects in real time was too much to ask of the iPhone.
So instead, during my app's initialization, I am pre-creating a series of AVAudioPlayers so that each one is basically pre-loaded with one of my 16 sounds, so they're ready to be played at any time.
The problem is, to keep this clean, I would like to store the references for these 16 AVAudioPlayers in an NSMutableArray, so I can easily get at them just by knowing their array location. However, the way I'm doing it is crashing the simulator w/no error messages in the log:
Here is how I'm currently setting up the array of AVAudioPlayer references:
// (soundPlayers is an NSMutableArray instance var)
soundPlayers = [NSMutableArray arrayWithCapacity:(NSUInteger)16];
for ( int i = 0; i < 16; i++ ) {
NSString *soundName = [NSString stringWithFormat:#"sound-%d", i];
NSString *soundPath = [[NSBundle mainBundle] pathForResource:soundName ofType:#"mp3"];
NSURL *soundFile = [[NSURL alloc] initFileURLWithPath:soundPath];
AVAudioPlayer *p = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFile error:nil];
[soundFile release];
[p prepareToPlay];
[soundPlayers addObject:(id)p];
[p release];
}
Then later I try to load, say, sound #8 and play it back:
// (soundPlayer is an AVAudioPlayer instance var)
self.soundPlayer = [soundPlayers objectAtIndex:(NSUInteger)8];
[soundPlayer play];
Any ideas? Also does anyone know if any of the slick debugging tools that come with XCode would be useful for this type of problem?
I'm just guessing here, but there might be a problem with the capacity of the array.
Try: soundPlayers = [[NSMutableArray alloc] init]; instead.
This isn't likely to be a cause of the crash, but try NSLogging self.soundPlayer before calling -play on it to make sure that its not nil. And another minor little thing: you don't need to typecast p when adding it to the array. Just call [soundPlayers addObject:p] without (id).
I'm not sure of the exact cause of your crash, just going through the code and pointing out what doesn't look right. Hope this helps!
It is most likely the case that your soundPlayers mutable array is being released at some point since you aren't retaining it. I would ensure your property for the array is set to retain and do what Macatomy previously stated:
self.soundPlayers = [[NSMutableArray alloc] init];
Or even initWithCapacity:
It looks OK to me except perhaps you shouldn't call prepareToPlay before you put them into the array.
It's probably better to do this when you actually are going to play them. The description of that method says it preloads buffers and acquires the audio hardware for playback. Doing it 16 times at once may be too much.
EDIT - you should also add error checking, your missing results that might tell you something is wrong, initWithContentsOfURL:outError: returns an error message. Take advantage of that:
NSError* error = nil;
AVAudioPlayer *p = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFile error:&error];
if (error) {
NSLog(#"ERROR: %#, URL=%#", error.localizedDescription, soundFile);
}
When I build and analyze my project on XCode, I obtain a 'warning' on the following line:
NSString *contactEmail = (NSString *)ABMultiValueCopyValueAtIndex(emailInfo, 0);
The message is: Potential leak on object allocated on line ... and stored into contactEmail.
Is there any error on that line?
UPDATE
I get the same 'warning' with this line of code:
ABMultiValueRef emailInfo = ABRecordCopyValue(person, kABPersonEmailProperty);
But here, I can't do this:
[emailInfo release];
I'm developing for iPhone.
ABMultiValueCopyValueAtIndex is a "Copy" function, which follows the "Create Rule". You need to call CFRelease to release it after finish using it.
NSString *contactEmail = (NSString *)ABMultiValueCopyValueAtIndex(emailInfo, 0);
...
if (contactEmail != nil)
CFRelease((CFTypeRef) contactEmail);
The cast is somewhat pointless.
The line might leak, unless you release or autorelease it somewhere.
Edit: For brevity:
NSString *contactEmail = [(NSString *)ABMultiValueCopyValueAtIndex(emailInfo, 0) autorelease];
(The cast might still be pointless, I'm unsure as to how the compiler would handle trying sending a message directly to a CFTypeRef.)