I am implementing the AQRecorder class from Apple's SpeakHere example into my project using ARC. To get it to compile, I had to create a class (AQRecorderController) that controls the AQRecorder instance (equivalent to the SpeakHereController in the example). AQRecorderController is connected through the nib of my main view controller and implemented as a property. The problem occurs whether or not the property is strong or weak.
My problem is that shortly after loading the view controller, the AQRecorderController is released, but only when tested on device. In the simulator, this does not occur. It occurs for iPad and iPhone, iOS 5 and iOS 6. I need to maintain this reference throughout the lifetime of my view controller for recording purposes (you can't delete the recorder while recording and expect to have a finished file).
Has anyone run into this or anything similar? If the AQRecorderController property is strong, I get a bad access error when trying to use it, if its weak, I just get a nil, and its unusable.
Any help would be greatly appreciated.
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
@class AQRecorderController;
@interface formViewController : UIViewController <UIActionSheetDelegate, UITableViewDelegate, UIGestureRecognizerDelegate> {
IBOutlet AQRecorderController *aqRecorderController;
}
@property (nonatomic, weak) IBOutlet AQRecorderController *aqRecorderController;
@end
#import <Foundation/Foundation.h>
#import "AQRecorder.h"
@interface AQRecorderController : NSObject
{
AQRecorder *aqRecorder;
}
@property (readonly) AQRecorder* aqRecorder;
@property (nonatomic, assign) bool isRecording;
@property (nonatomic, strong) NSString* fileName;
-(bool)startRecording;
-(bool)pauseRecording;
-(bool)stopRecording;
-(bool)initializeRecordSettingsWithCompression:(bool)compressionEnabled;
@end
Here is the stack trace after the AQRecorderController has been released:
2012-10-23 10:34:09.600 TestApp[510:907] (
0 TestApp 0x000f32ab
-[AQRecorderController dealloc] + 138
1 CoreFoundation 0x32247311 CFRelease + 100
2 CoreFoundation 0x3225195d <redacted> + 140
3 libobjc.A.dylib 0x31ad5489 <redacted> + 168
4 CoreFoundation 0x32249441 _CFAutoreleasePoolPop + 16
5 Foundation 0x37303a7f <redacted> + 466
6 CoreFoundation 0x322db5df <redacted> + 14
7 CoreFoundation 0x322db291 <redacted> + 272
8 CoreFoundation 0x322d9f01 <redacted> + 1232
9 CoreFoundation 0x3224cebd CFRunLoopRunSpecific + 356
10 CoreFoundation 0x3224cd49 CFRunLoopRunInMode + 104
11 GraphicsServices 0x32fb52eb GSEventRunModal + 74
12 UIKit 0x34e92301 UIApplicationMain + 1120
13 TestApp 0x00081a9d main + 48
14 TestApp 0x0005aa68 start + 40
)
This is where the recorder is instantiated.
AQRecorderController.mm:
- (void)awakeFromNib
{
aqRecorder = new AQRecorder();
}
This is where the recorder is used. By this point, the AQRecorderController has been released and this code never executes (it causes a crash, because the AQRecorderController has been deallocated).
-(bool)startRecording
{
if (aqRecorder->IsRunning())
{
[self stopRecording];
}
else // If we're not recording, start.
{
@try
{
// Start the recorder
CFStringRef filenameString = (CFStringRef)CFBridgingRetain(self.fileName);
aqRecorder->StartRecord(filenameString);
}
@catch(NSException *ex)
{
NSLog(@"Error: %@", [ex description]);
return NO;
}
[self setFileDescriptionForFormat:aqRecorder->DataFormat() withName:@"Recorded File"];
}
[self checkIfRecording];
return YES;
}
Here is where the AQRecorderController is instantiated.
formViewController.mm:
//this is called in viewDidAppear
-(void)initializeAQRecorder: (NSString*)soundFileName
{
aqRecorderController = [[AQRecorderController alloc] init];
NSLog(@"AQRecorderController is being initialized for file %@",soundFileName);
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
NSString *soundFilePath =[[NSString alloc] initWithFormat:@"%@",[documentsDir stringByAppendingPathComponent:soundFileName]];
[aqRecorderController setFileName:soundFilePath];
[aqRecorderController initializeRecordSettingsWithCompression:NO];
}
My problem is that shortly after loading the view controller, the AQRecorderController is released...I need to maintain this reference throughout the lifetime of my view controller
Mark your property strong
instead of weak
. weak
means that the object pointed to by aqRecorderController
won't be retained by the setter; strong
will cause it to be retained.
If the AQRecorderController property is strong, I get a bad access error when trying to use it, if its weak, I just get a nil, and its unusable.
That sounds like the property is being set to some invalid value somewhere in your program. Since you can't manually retain the object under ARC and you've marked the property weak
, it may be released very early on. I'm not sure why you'd have a problem if you mark it strong
... it'd help to see the code where you set the variable or property.
You're never setting the AQRecorderController
to your formViewController
from what I see. You need to do self.aqRecorderController = aqRecorderController
, I believe it's just disappearing as soon as you leave the scope where you create the controller.
I got it working for now. I haven't completely fixed it, but I can record without it crashing. I commented out every line having to do with AQRecorderController until it stopped being released, then slowly added them back until I found out where it happens. It looks like the audio session setup code somehow provokes it to release the controller. This is the code that causes it (but no errors are thrown here):
From AQRecorderController.mm:
-(void)initializeRecordSettingsWithCompression:(bool)compressionEnabled
{
OSStatus error = AudioSessionInitialize(NULL, NULL, interruptionListener, (__bridge void*)self);
if (error) printf("ERROR INITIALIZING AUDIO SESSION! %d\n", (int)error);
else
{
UInt32 category = kAudioSessionCategory_PlayAndRecord;
error = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category);
if (error) printf("couldn't set audio category!");
error = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, propListener, (__bridge void*)self);
if (error) printf("ERROR ADDING AUDIO SESSION PROP LISTENER! %d\n", (int)error);
UInt32 inputAvailable = 0;
UInt32 size = sizeof(inputAvailable);
// we do not want to allow recording if input is not available
error = AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable, &size, &inputAvailable);
if (error) printf("ERROR GETTING INPUT AVAILABILITY! %d\n", (int)error);
// we also need to listen to see if input availability changes
error = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioInputAvailable, propListener, (__bridge void*)self);
if (error) printf("ERROR ADDING AUDIO SESSION PROP LISTENER! %d\n", (int)error);
error = AudioSessionSetActive(true);
if (error) printf("AudioSessionSetActive (true) failed");
}
}
So far, this isn't necessary for the functioning of my app, but I am curious as to why it would cause the AQRecorderController instance to release.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.