I have set up a couple of very simple methods that play with NSURLs and NSStrings.
the first method that gets called is getAudio. I want to set the variable audioPath equal to cleanAudio so that it can be used later in the encryptWithAudioFile. The problem is somewhere along the way when it is being passed as a parameter the object becomes null. I have no idea why and I have tried every method I can think of in terms of memory management to try and keep the object. The logs I have used work result as follows
NSLog(#"Clean Audio1: %#", audioPath); Perfect, works as it should NSLog(#"Clean Audio2: %#", audioPath); NSLog(#"Clean Audio3: %#", audioPath); NSLog(#"Clean Audio4: %#", audioPath);
#implementation Stub
#synthesize audioPath,
userText;
-(NSURL *)retrieveAudio:(NSURL *)cleanAudio{
self.audioPath = cleanAudio;
NSLog(#"Clean Audio1: %#", audioPath);
return cleanAudio;
}
-(NSString *)retrieveText:(NSString *)message{
self.userText = message;
NSLog(#"Text: %#", self.userText);
NSLog(#"Clean Audio2: %#", audioPath);
return message;
}
-(void)startEncrption{
NSLog(#"Clean Audio3: %#", self.audioPath);
[self encrypterWithAudioFile:self.audioPath withString:self.userText];
}
-(NSURL *)encrypterWithAudioFile:(NSURL *)audio withString:(NSString *)text{
NSLog(#"DIRTY AUDIO and TEXT: %#, %#", audio, text);
return audio;
}
-(void)dealloc{
[super dealloc];
[audioPath release];
[userText release];
}
#end
Edit: Here's the .h
#interface Stub : NSObject {
NSURL *audioPath;
NSString *userText;
}
-(NSURL *)retrieveAudio:(NSURL *)cleanAudio;
-(NSString *)retrieveText:(NSString *)message;
-(void)startEncrption;
-(NSURL *)encrypterWithAudioFile:(NSURL *)audio withString:(NSString *)text;
#property(nonatomic, retain) NSURL *audioPath;
#property(nonatomic, retain) NSString *userText;
I'm calling the methods in other classes as follows:
stubObj = [[Stub alloc] init];
[stubObj retrieveAudio:recordedTmpFile];
and
stubObj2 = [[Stub alloc] init];
[stubObj2 retrieveText:textView.text];
[stubObj2 startEncrption];
Edit: I should probably have mentioned that I have three views, all with different controllers running on a UIScrollView that are calling the [Stub] interface.
I would look into your memory management and make sure you are not over releasing that URL some where after you've passed it into this methods. What I can see from your current code is that (assuming your audioPath property is marked retain) you are actually over retaining it within this snipped when you are assigning it:
self.audioPath = [[NSURL alloc] initWithString:#"TEST"];
Should be:
self.audioPath = [NSURL urlWithString:#"TEST"];
It looks as if you are allocating the NSURL only in -[Stub getAudio:], right? It also looks like you are creating multiple Stub objects. Are you calling the getAudio: method on each of those objects before trying to use the audioPath property on that object? That could be your problem.
In your latest update of the post, you include 2 different examples of how you use this class. In the first one you have this:
stubObj = [[Stub alloc] init];
[stubObj retrieveAudio:recordedTmpFile];
I assume that this is working, and that your "Clean Audio1:" log shows the expected URL, is that correct?
In your next example you have this:
stubObj2 = [[Stub alloc] init];
[stubObj2 retrieveText:textView.text];
[stubObj2 startEncrption];
Since this is a new instance and you never call retrieveAudio on this instance, the audioPath property for this instance is never set so it is nil. When you call startEncrption it calls encrypterWithAudioFile and passes self.audioPath, which is nil. Isn't this what you are expecting or am I missing something here?
Finally figured out a fix. I ended up moving all my variables to my app delegate and referenced them from other classes by pointing to the delegate::
RonnieD1AppDelegate *appDelegate = (RonnieD1AppDelegate *)[[UIApplication sharedApplication] delegate];
I removed the startEncryption method altogether and called the method encryptWithAudioFile like this:
[stubObj2 encrypterWithAudioFile:appDelegate.audioPath withString:appDelegate.userText];
My final code ended up looking like this:
#implementation Stub
-(NSURL *)retrieveAudio:(NSURL *)cleanAudio{
RonnieD1AppDelegate *appDelegate = (RonnieD1AppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.audioPath = cleanAudio;
NSLog(#"Clean Audio (retrieveAudio): %#", appDelegate.audioPath);
return cleanAudio;
}
-(NSString *)retrieveText:(NSString *)message{
// self.audioPath = [NSURL URLWithString:#"TEST"];
RonnieD1AppDelegate *appDelegate = (RonnieD1AppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.userText = message;
NSLog(#"Text: %#", appDelegate.userText);
NSLog(#"Clean Audio (retireveText): %#", appDelegate.audioPath);
return message;
}
//-(void)startEncrption{
// NSLog(#"Clean Audio (startEncryption): %# TEXT: %#", self.audioPath, self.userText);
// //self.audioPath = [NSURL URLWithString:#"TEST"];
// [self encrypterWithAudioFile:self.audioPath withString:self.userText];
//
//}
-(NSURL *)encrypterWithAudioFile:(NSURL *)audio withString:(NSString *)text{
NSLog(#"DIRTY AUDIO and TEXT: %#, %#", audio, text);
return audio;
}
-(void)dealloc{
[super dealloc];
}
#end
I know there are still some memory management issues with this but its working finally so I can figured that out myself. Thanks for all your help.
Related
In one of my methods, I fetched and parsed a JSON and placed it inside an NSArray called jsonArray in -(void)method1. I then copied the contents of that jsonArray to an NSMutableArray called copiedJsonArray to be used on other methods. Problem is, copiedJsonArray crashes whenever I log its contents in the console from the other methods -(void)method2 but it logs fine in -(void)method1.
How can I fix this?
In my header file:
#interface MainViewController : UIViewController
#property (nonatomic, retain) NSMutableArray *copiedJsonArray;
In my implementation file:
#synthesize copiedJsonArray;
- (void)viewDidLoad
{
[self method1];
}
- (void)method1
{
NSString *urlString = [NSString stringWithFormat:THE_URL];
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
NSString *jsonString = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
NSDictionary *jsonDictonary = [jsonString JSONValue];
NSArray *jsonArray = [jsonDictonary valueForKeyPath:#"QUERY.DATA"];
self.copiedJsonArray = [[NSMutableArray alloc] initWithArray:jsonArray copyItems:YES];
NSLog(#"Copied JSON Array in Method 1: %#", self.copiedJsonArray);
[self method2];
}
- (void)method2
{
NSLog(#"Copied JSON Array in Method 2: %#", self.copiedJsonArray);
}
I also tried doing this too but it does the same error:
copiedJsonArray = [jsonArray mutableCopy];
I also tried implementing NSCopy but fails too:
#interface MainViewController : UIViewController <NSCopying>
{
NSMutableArray *copiedJsonArray;
}
I'm doing this so that I can do a loop in my copiedJsonArray without fetching its contents from JSON again and again when the user taps on my UISegmentedControl.
If you call method2 before method1 it will crash as copiedJasonArray has not been created. You should not create instance variables inside methods (as you cannot know if they have been called). You should do it when you create your viewController, in viewDidLoad for example.
And use properties:
#interface
#property (retain) NSMutableArray* copiedJsonArray;
#end
then either
#synthesize copiedJsonArray = _copiedJsonArray
or leave that line it out (the compiler will put it in automatically in 4.5)
access as self.copiedJsonArray or _copiedJSONArray.
Outside of getters,setters,inits and deallocs, use the self. form, it's safer.
You could also create _copiedJsonArray lazily in the setter:
- (NSMutableArray*) copiedJsonArray
{
if (!_copiedJasonArray)
_copiedJsonArray = [NSMutableArray alloc] init;
return _copiedJasonArray;
}
I'm struggling to find the correct way to release an array after my method has been called. I wonder if there is a better way to achieve what I'm trying to acheive with my method:
- (NSArray *) setupDetailArray : (NSString *) selectedCategory {
// Load .plist file
NSString *path = [[NSBundle mainBundle] pathForResource:#"data" ofType:#"plist"];
// Load .plist into a new dictionary
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
// Drill down to next level
NSArray *faceSelection = [[NSArray alloc] initWithArray:[dict objectForKey:detailTitle]];
[dict release], dict = nil;
// Set up link to App Delegate
UltimateRageAppDelegate *dataCenter = (UltimateRageAppDelegate *) [[UIApplication sharedApplication] delegate];
dataCenter.faces = [[NSMutableArray alloc] init];
// Set app delegate faces to array
dataCenter.faces = faceSelection;
[dataCenter.faces release];
return faceSelection;
// [faceSelection release], faceSelection = nil; ??????
}
And I call my method in viewDidLoad
// If faceArray is empty, create it
if (faceArray == nil)
faceArray = [self setupDetailArray:detailTitle];
...
My application is leaking memory here, and I'm really looking for a way to release everything once I'm done.
Your method should return an autoreleased array which is then retained by the method that calls it if it wants/needs to keep it.
- (NSArray *) setupDetailArray : (NSString *) selectedCategory {
...
// Create the array, but don't own it
NSArray *faceSelection = [[[NSArray alloc] initWithArray:[dict objectForKey:detailTitle]] autorelease];
...
return facesSelected;
}
Now the code that calls this method should retain the object if it needs it. So, in your viewDidLoad
if (faceArray == nil)
faceArray = [[self setupDetailArray:detailTitle] retain];
...
If faceArray is an instance variable in your class, then you can just release it in your dealloc method.
You are also leaking memory here
// Set up link to App Delegate
UltimateRageAppDelegate *dataCenter = (UltimateRageAppDelegate *) [[UIApplication sharedApplication] delegate];
dataCenter.faces = [[NSMutableArray alloc] init];
// Set app delegate faces to array
dataCenter.faces = faceSelection;
[dataCenter.faces release];
This should be
// Set up link to App Delegate
UltimateRageAppDelegate *dataCenter = (UltimateRageAppDelegate *) [[UIApplication sharedApplication] delegate];
dataCenter.faces = faceSelection;
I'd suggest you read (and re-read and re-read) the docs on memory management and read up on properties, setters and the dot notation.
Apple Objective-C Memory Management
dataCenter.faces = [[NSMutableArray alloc] init];
You allocate a non-autoreleased array and assign it to the property faces (I bet it has the retain modifier).
dataCenter.faces = faceSelection;
Now you assign as new array to the faces property, but you haven't properly release the previous NSMutableArray.
[dataCenter.faces release];
You now indirectly release your faceSelection array.
You leak at least one NSMutableArray every time you run that method. You should do it like this instead:
// Drill down to next level
NSArray *faceSelection = [[dict objectForKey:detailTitle] copy];
[dict release], dict = nil;
// Set up link to App Delegate
UltimateRageAppDelegate *dataCenter = (UltimateRageAppDelegate *) [[UIApplication sharedApplication] delegate];
// Set app delegate faces to array
dataCenter.faces = faceSelection;
return [faceSelection autorelease];
Your method should return an autoreleased object. The only methods that should return retained objects are methods whose name:
starts with alloc
starts with new
contains copy
All other methods should return autoreleased objects.
Other way for doing this.
//Declare method as follows.
- (void) setupDetailArray : (NSString *) selectedCategory arrFaceArray:(NSArray *)faceArray
{
}
And I call my method in viewDidLoad
if (!faceArray)
{
faceArray = [[NSArray alloc] init]; //Alloc in ViewDidLoad and release in ViewDidUnload or dealloc.
faceArray = [self setupDetailArray:detailTitle arrFaceArray:faceArray];
}
Also consider the #DarkDust answer for maintaining autoreleased objects. Both are the possible ways.
I have a model class that keeps track record being built by multiple views. It has a NSMutableDictionary that has the fields and values I eventually write to the database. It is saved to a plist and loaded back when needed. I thought that I was keeping track of my memory, but it throws a EXC_BAD_ACCESS when I try to release the Dictionary. Here is my interface:
#import <Foundation/Foundation.h>
#interface CurrentEntryModel : NSObject {
NSMutableDictionary *currentEntry;
}
#property (nonatomic, retain) NSMutableDictionary *currentEntry;
- (void) setValue: (NSString *)value;
- (NSString *) getValue;
#end
My understanding is that currentEntry should be retained and I would have to release it during dealloc.
Here is my implementation (this isn't the entire class just the relevant parts):
#import "CurrentEntryModel.h"
#implementation CurrentEntryModel
#synthesize currentEntry;
-(id) init {
if ( self = [super init] )
{
//check for file
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *file;
file = #"location.plist";
if ([fileManager fileExistsAtPath:file]){
NSLog(#"file exists");
currentEntry = [[NSMutableDictionary alloc] initWithContentsOfFile:file];
}else {
NSLog(#"file doesn't exist");
currentEntry = [[NSMutableDictionary alloc ] initWithCapacity:1];
NSDate *testDate = [NSDate date];
[currentEntry setObject:testDate forKey:#"created"];
[currentEntry writeToFile:file atomically:YES];
}
}
return self;
}
- (void) setValue: (NSString *)value {
[currentEntry setObject:value forKey:#"location"];
}
- (NSString *) getValue {
return [currentEntry objectForKey:#"location"];
}
- (void) dealloc{
[currentEntry release];
[super dealloc];
}
#end
If I init this class it will automatically create the dictionary and if I call one of the set or get methods it seems like the dictionary is retained as it will dealloc correctly. If the class is just initialized and then no methods are called it will throw the EXC_BAD_ACCESS errors. If I am not mistaken when the file doesn't exist I don't initialize the dictionary correctly because the method starts with dictionary and not init. Although every time I run this the file is there so it always uses the the file found logic and I thought that that will retain the variable.
Am I not initializing the dictionary correctly?
Edit - changed the code on the convenience method to reflect the proper way. Everyone take note of what Squeegy has to say.
This is bad bad bad.
else {
NSLog(#"file doesn't exist");
currentEntry = [[NSMutableDictionary alloc ] dictionaryWithCapacity:1];
dictionaryWithCapacity: is a class method on NSMutableDictionary which returns an autoreleased object, and you don't retain it. So the run loop ends, and the dictionary gets autoreleased. Then you run [currentEntry release] in your dealloc and it explodes because that object is deallocated already.
you probably wan't initWithCapacity: instead. Always pair alloc with a method that starts with init.
Also, when using retained properties like this, I usually let the property figure this out for me, and only work with autoreleased objects. You just have to remember less rules, and there are less gotchas.
- (id)init {
// ...
self.currentEntry = [NSMutableDictionary dictionWithContentsOfFile:file];
// ...
}
- (void)dealloc {
//...
self.currentEntry = nil;
//...
}
This way you never have to call retain or release directly on the object. In my experience, this results in less confusing bugs. But it's also point of style among many ObjC programmer that not everyone agrees with.
Joshua -
+ (id)dictionaryWithCapacity:(NSUInteger)numItems
is a class method of NSDictionary. So when you call it, it should be:
[NSMutableDictionary dictionaryWithCapacity:1];
Not:
[[NSMutableDictionary alloc] dictionaryWithCapacity:1];
Further, [NSMutableDictionary dictionaryWithCapacity:] returns an autoreleased object. If you want to keep the dictionary as an ivar and not have it autoreleased on the next cycle of the run loop, you should call:
[currentEntry retain];
So, basically, change it to:
currentEntry = [[NSMutableDictionary alloc] initWithCapacity:1];
or:
currentEntry = [[NSMutableDictionary dictionaryWithCapacity:1] retain];
The first one probably makes more sense, since the connivence class methods were designed to be used when you wanted an autoreleased instance.
I am using following method to get back an NSDictionary object in ViewDidAppear. But when I attempt to access it in CellForRowAtIndexPath() it is always nil. I have tried adding an extra retain and copy to it, but it still gets released. I have been pulling my hair for 3 hours now. Any help would be appreciated.
Excerpt :
#property(nonatomic, retain) NSDictionary* userInfoObj;
- (void) viewDidAppear:(BOOL)animated
{
[super viewWillAppear:animated];
**//The object has data in it at this point**
self.UserInfoObj = [self getUserInfo];
}
- (NSDictionary*)getUserInfo
{
JsonHelper *helper=[[JsonHelper alloc] autorelease];
NSString* apiURL = [self.appDelegate urlGetUserInfo];
apiURL = [apiURL stringByReplacingOccurrencesOfString:#"{user_id}" withString:[UserSettings lastLoginUserId]];
return [helper getJsonDictionaryFromWebMethod:apiURL];
}
- (NSDictionary*)getJsonDictionaryFromWebMethod :(NSString*) url
{
.....
.....
....
// Get JSON as a NSString from NSData response
NSString *json_string = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
// parse the JSON response into an object
// Here we're using NSArray since we're parsing an array of JSON status objects
dict = [[parser objectWithString:json_string error:nil] retain];
return dict;
}
Try putting self.UserInfoObj = [self getUserInfo]; in the viewDidLoad delegate method instead.
Hi all i'm still new to iPhone development, but had strong experience with other programming languages. The thing that makes me pull my hair out is the Obj-C memmory management and releasing / retaining properly. I know the concept, i know "once i understand it will be easy" but i'm not quite still there, and that makes me crazy. Here i have one simple piece of code with class and method, that simply add's one character to existing string that is synthesized so used as class proprety ... the class for example is called myClass ...
myClas.h
#interface myClass : NSObject {
#private
NSString* someCommonString;
}
#propery (retain, nonatomic) NSString* someCommonString;
myClass.m
...
#synthesize someCommonString;
- (id) init
{
self = [super init];
if(self)
{
someCommonString = [[NSString alloc] initWith String:#"one "];
}
}
- (NSString*) appendString:(NSString*) stringToAdd
{
NSString* result = [someCommonString stringByAppendingString: stringToAdd];
return result;
}
- (void) doTheJob
{
NSString* test1 = #"two ";
NSString* test2 = [[NSString alloc] initWithString: #"three "];
NSString* test3 = [NSString stringWithFormat:#"four "];
self.someCommonString = [self appendString:test1];
self.someCommonString = [self appendString:test2];
self.someCommonString = [self appendString:test3];
NSLog(#"%#", someCommonString);
}
- (void) dealloc
{
[someCommonString release];
[super release];
}
...
Ok, after i alloc myClass and execute the doTheJob method, i should have #"one two three four" in the someCommonString class proprety. I know this is working, but is also leaking as hell. test1, test2 and test3 are 3 ways of initialising NSString, and only the test2 should be released, this is quite self-explanatory, but im much more worried what happens when passing them as arguments to the appendString method. Because there i know i hawe a leak, but don't know how to handle 1. stringToAdd argument [should i worry about it in appendString method at all ?] 2. the result -> if i autorelease the result, i don't know at which point the result will be deallocated. 3. sommeCommonString in appendStringMethod, should i retain it, release it or leave it alone ?
Huh :)
At a first glance, it seems to me like you're not releasing test2. After you have appended it to your common string, you do not need to retain it anymore.
self.someCommonString = [self appendString:test1];
self.someCommonString = [self appendString:test2];
self.someCommonString = [self appendString:test3];
[test2 release];
The other two (test1 and test3) are autoreleased, so your thread will reclaim them at some point).
As far as your appendString: method is concerned, result is already autoreleased and in fact you could reduce your implementation to
return [someCommonString stringByAppendingString: stringToAdd];
someCommonString is not affected by the operation at all. stringByAppendingString: returns a new autoreleased string from the concatenation of self and stringToAdd.
Hope that helps
… but im much more worried what happens when passing them as arguments to the appendString method. Because there i know i hawe a leak, but don't know how to handle
1. stringToAdd argument [should i worry about it in appendString method at all ?] …
You don't have a leak in -appendString:. You are passing stringToAdd around without retaining it and that's okay here.
The result is autoreleased and you don't have to take any action on it.
2. the result -> if i autorelease the result, i don't know at which point the result will be deallocated.
The result is already autoreleased and will be released as soon as the current NSAutoreleasePool will be released. Til then you can pass it around without retaining.
3. sommeCommonString in appendStringMethod, should i retain it, release it or leave it alone ?
Leave it alone, it's managed by the accessors. But as fedmest (and you too) said: release test2.
Well there are few problems in your code, but basic problem is you need NSMutableString string, not NSString to make your code work.
in init method, correct code to initialize is,
someCommonString = [[NSMutableString alloc] initWithString:#"one "];
You have to return the object (self) from init, otherwise it will not work, like this.
return self;
If you wanted to append the string, it should be NSMutableString, not NSString.
[[self someCommonString] appendString:test1];
[[self someCommonString] appendString:test2];
[[self someCommonString] appendString:test3];
In dealloc method, you call dealloc method of super, not release the super. So correct it it like this.
[super dealloc];
There is no need to release test1 and test3, because they are autorelease.
I have witten the correct code, try this out.
#interface myClass : NSObject {
#private
NSMutableString* someCommonString;
}
#property (retain, nonatomic) NSMutableString* someCommonString;
- (void) doTheJob;
#end
#implementation myClass
#synthesize someCommonString;
- (id) init
{
self = [super init];
if(self)
{
someCommonString = [[NSMutableString alloc] initWithString:#"one "];
}
return self;
}
- (NSString*) appendString:(NSString*) stringToAdd
{
NSString* result = [someCommonString stringByAppendingString: stringToAdd];
return result;
}
- (void) doTheJob
{
NSString* test1 = #"two ";
NSString* test2 = [[NSString alloc] initWithString: #"three "];
NSString* test3 = [NSString stringWithFormat:#"four "];
[[self someCommonString] appendString:test1];
[[self someCommonString] appendString:test2];
[[self someCommonString] appendString:test3];
NSLog(#"%#", someCommonString);
[test2 release];
}
- (void) dealloc
{
[someCommonString release];
[super dealloc];
}
#end
This is a technique which might help you deal better with what is happening with the #synthesize directive. Change your header as follows
#interface myClass : NSObject {
#private
NSString* _bob;
}
#property (retain, nonatomic) NSString* someCommonString;
and your class file to
#synthesize someCommonString = _bob;
If you recompile your code you will get build errors. Fix these and you will suddenly be able to see exactly what you are accessing via the synthesized property and what you are directly accessing.
Additionally, as already mentioned, you need to release test2 in the doTheJob method.