I have a pickerView, it appears in the Simulator but as soon as I try and scroll it, it crashes EXC_BAD_ACCESS in the main.m file.
I know it is to do with the arrays that I load in from a pList because when I try this with arrays that are initialised in the program it works (see commented out sections), is this something to do with 'nil' at the end of the array? If so how can I see this and how can I add it.
I appreciate any help on this please I'm pulling what's left of my hair out, and I'm fairly new to this...
// Method to define the numberOfComponent in a picker view.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 2;
}
// Method to define the numberOfRows in a component using the array.
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent :(NSInteger)component
{
if (component==0)
{
return [maximumSpeed count];
}
else
{
return[warningTime count];
}
}
//PickerViewController.m
- (NSString *)pickerView:(UIPickerView *)thePickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
NSString *stringToReturn = nil;
switch (component)
{
case 0:
stringToReturn = [maximumSpeed objectAtIndex:row];
break;
case 1:
stringToReturn = [warningTime objectAtIndex:row];
break;
}
return stringToReturn;
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
// finding the path and loading the pList as a dictionary into 'data'
NSString *pListFile = [[NSBundle mainBundle] pathForResource:#"PropertyList" ofType:#"plist"];
data =[NSDictionary dictionaryWithContentsOfFile:pListFile];
// get and set up the 2 arrays
maximumSpeed = [data valueForKey:#"MaximumSpeed"];
warningTime = [data valueForKey:#"WarningTime"];
//maximumSpeed =[[NSArray alloc] initWithObjects:#"Running",#"Crying",#"Boring",#"Working",nil];
//warningTime = [[NSArray alloc] initWithObjects: #"Happy", #"Sad" , #"Good", #"joyce",nil];
NSLog(#"%#", maximumSpeed);
NSLog(#"%#", warningTime);
}
(void)viewDidUnload
{
[self setTxt1:nil];
[pickerView release];
pickerView = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[txt1 release];
[pickerView release];
[warningTime release];
[maximumSpeed release];
[super dealloc];
}
You do not own the object(s) returned by valueForKey, in this case, your arrays. These objects are getting released and you end up with a dangling pointer. Hence, the crash. The problem is here:
maximumSpeed = [data valueForKey:#"MaximumSpeed"];
warningTime = [data valueForKey:#"WarningTime"];
You need to retain them.
maximumSpeed = [[data valueForKey:#"MaximumSpeed"] retain];
warningTime = [[data valueForKey:#"WarningTime"] retain];
The following works because you own the objects you create.
//maximumSpeed =[[NSArray alloc] initWithObjects:#"Running",#"Crying",#"Boring",#"Working",nil];
//warningTime = [[NSArray alloc] initWithObjects: #"Happy", #"Sad" , #"Good", #"joyce",nil];
This also applies to dictionaryWithContentsOfFile:
data =[NSDictionary dictionaryWithContentsOfFile:pListFile];
You can retain (like before) or switch to the alloc-init version:
data = [[NSDictionary dictionaryWithContentsOfFile:pListFile] retain];
// or
data = [[NSDictionary alloc] initWithContentsOfFile:pListFile];
You do not own the objects returned by methods unless the method name begins with "alloc", "new", "copy" or "mutableCopy".
As it has been said already, you could copy the objects (arrays) maximumSpeed and warningTime or, alternatively, you retain and release them.
Talking about releasing ... You are releasing pickerView twice. First in viewDidUnload and second in dealloc. It may actually work without a crash because you assign nil to it after the first release and therefore the second release should be processed without a crash anyway.
However, that indicates that you should be more careful about your memory management, retain-release-pairs in particular.
Why don't you track the zombies in the instrumemts application? You start it in xcode4 with cmd-i.
In most cases that gives some useful hint on where to have a detailed look on missing retains (or copies).
Related
I'm trying to create an NSDictionary full of arrays in the implementation file of my model but my code hasn't worked yet. I want to create arrays that are lists of types of dogs and cats and then add those arrays to a dictionary with keys called DOG and CAT. Here is my code:
#implementation wordDictionary
#synthesize catList = _catList;
#synthesize dogList = _dogList;
#synthesize standardDictionary =_standardDictionary;
- (void)setCatList:(NSMutableArray *)catList
{
self.catList = [NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil];
}
- (void)setDogList:(NSMutableArray *)dogList
{
self.dogList = [NSMutableArray arrayWithObjects:#"pit bull", #"pug", #"chihuahua", nil];
}
-(void)setStandardDictionary:(NSMutableDictionary *)standardDictionary
{
[self.standardDictionary setObject: _catList forKey:#"CAT"];
[self.standardDictionary setObject: _dogList forKey:#"DOG"];
}
- (NSString*)selectKey
{
NSInteger keyCount = [[self.standardDictionary allKeys] count];
NSInteger randomKeyIndex = arc4random() % keyCount;
NSString *randomKey = [[self.standardDictionary allKeys] objectAtIndex:randomKeyIndex];
return randomKey;
}
#end
This code is the model. The model is hooked up to my view controller such that when a user taps a button, the NSString returned from randomKey is displayed in a label on the screen. So the text will read either CAT or DOG. Here's the code for that:
- (IBAction)changeGreeting:(UIButton*)sender {
NSString *chosenKey = [self.dictionary selectKey];
NSString *labelText = [[NSString alloc] initWithFormat:#"%#", chosenKey];
self.label.text = labelText;
}
Unfortunately when I tap the button on the simulator I get an error message saying: Thread 1:EXC_ARITHMETIC (code=EXC_1386_DIV, subcode=0x0) at NSInteger randomKeyIndex = arc4random() % keyCount; and it appears that I'm getting it because neither my NSArray nor my NSDictionary have any objects inside of them.
Does anyone have any idea why my NSArray and NSDictionary haven't been populated?
Thanks very much.
The simple answer is that there isn't any code here that calls the methods to set the arrays or dictionary.
But the real underlying issue is that there are a couple of bad 'patterns' going on here that you should fix:
In your setter methods (setCatList:, setDogList:, setStandardDictionary:) you're not setting the properties in question to the values that are passed in. For example, you should be setting catList to the passed in "catList" variable.
- (void)setCatList:(NSMutableArray *)catList
{
if (_catList != catList) {
[_catList release];
_catList = [catList retain];
}
}
Then you should have some kind of "setup" happening, usually in a method in the view controller like viewDidLoad:
[wordDictionary setCatList:[NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil]];
// and more for the other two setters
Alternately, you can set these default values in the init for the wordDictionary class:
- (id)init {
self = [super init];
if (self) {
[self setCatList:[NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil]];
}
return self;
}
The former is better in most cases, but you may have a good reason to pre-populate your model for all instances of the class.
Assuming you called setCatList:, setDogList: and setStandardDictionary: before. Probably that causing is this :
NSString *chosenKey = [self.dictionary selectKey];
change into this :
NSString *chosenKey = [self selectKey];
UPDATE
I'm trying to make your life easier. no need to create your object if you don't need the most.
- (NSMutableArray*)getCatList
{
return [NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil];
}
- (NSMutableArray*)getDogList
{
return [NSMutableArray arrayWithObjects:#"pit bull", #"pug", #"chihuahua", nil];
}
-(NSMutableDictionary*)getStandardDictionary
{
NSMutableDictionary *standardDictionary = [NSMutableDictionary new];
[standardDictionary setObject:[self getCatList] forKey:#"CAT"];
[standardDictionary setObject:[self getDogList] forKey:#"DOG"];
return [standardDictionary autorelease];
}
- (NSString*)selectKey
{
NSMutableDictionary *standardDictionary = [self getStandardDictionary];
NSInteger keyCount = [[standardDictionary allKeys] count];
NSInteger randomKeyIndex = arc4random() % keyCount;
NSString *randomKey = [[standardDictionary allKeys] objectAtIndex:randomKeyIndex];
return randomKey;
}
- (IBAction)changeGreeting:(UIButton*)sender {
// NSString *chosenKey = [self selectKey];
//NSString *labelText = [[NSString alloc] initWithFormat:#"%#", chosenKey];
self.label.text = [self selectKey]; //no need to convert it to NSString again
}
Two things to consider:
I don't see you calling these:
setCatList:(NSMutableArray*)catList;
setDogList:(NSMutableArray*)dogList;
You use self.catList and self.dogList, but neither of those are synthesized, instead you have beatList and meList synthesized
Change the synthesizes to the catList and dogList, and make sure you call the set list methods, and then you should make some progress.
I have a SOAP service and I generated classes and functions on SudzC.com.
So I'm using the soap functions they generated, it returns an NSMutableArray with objects that are inherited by my custom class(which is generated by them, too).
So far everything's good. My values are getting into the array and I could see any property of any object with one condition: Only inside of the function that's handling the service.
Just to make it clear, here is the code:
- (void)viewDidLoad
{
SDZGeneratedWebService* service = [SDZGeneratedWebService service];
service.logging = YES;
[service callMyData:self action:#selector(callMyDataHandler:) dataId: 1];
[super viewDidLoad];
}
- (void) callMyDataHandler: (id) value {
// Handle errors
if([value isKindOfClass:[NSError class]]) {
NSLog(#"%#", value);
return;
}
// Handle faults
if([value isKindOfClass:[SoapFault class]]) {
NSLog(#"%#", value);
return;
}
// Do something with the NSMutableArray* result
NSMutableArray *result = (NSMutableArray *)value;
MyCustomClass *myObject = [result objectAtIndex:0];
NSLog(#"%#", myObject.myProperty); //Works Great
}
Like I said, so far everything's perfect. But I need to use the data outside of this function.
So in my .h file, I created an array like NSMutableArray *myDataArray;
When I intend to copy the result array to myDataArray, it copies the objects(I can see that the myDataArray.count value is equal to result array's) but all the objects are "out of scope". So I cannot use them.
I also tried to copy all objects by indexes in a for loop, nope, the objects are getting their values, but when I "addObject" to myDataArray, same, out of scope.
What is wrong here? Can't I generate an array of a custom class this way?
Edit: The code I'm generating myDataArray:
myDataArray = [[NSMutableArray alloc] init];
[myDataArray removeAllObjects];
for (int i=0; i<((NSMutableArray *)result).count; i++) {
MyCustomClass *myObject = [result objectAtIndex:i];
[myDataArray addObject:myObject];
[myObject release];
}
[self.tableView reloadData];
} //(End of callMyDataHandler function)
I before tried this way, too:
[myDataArray removeAllObjects];
duyurular = [result mutableCopy];
} //(End of callMyDataHandler function)
You can copy objects from one array to another using this method:
NSArray *source;
NSArray *dst = [[NSArray alloc] initWithArray:source];
In your code you should remove line: [myObject release]; and I would better call [((NSMutableArray *)result) count] rather then using dot notation.
I am parsing some JSON from the internet and then adding them to an array which is the datasource for my UITableView. I am not sure when I should be releasing my array?
.h: items
#property(nonatomic,retain)NSMutableArray* items;
.m: connectionDidFinishLoading
// fetch succeeded
NSString* json_string = [[NSString alloc] initWithData:retrievedData encoding:NSUTF8StringEncoding];
//Check ST status
int status = [[[[json_string objectFromJSONString] valueForKey:#"response"] valueForKey:#"status"]intValue];
//NSLog(#"Status: %d", status);
items = [[NSMutableArray alloc] init];
NSDictionary* messages = [[NSDictionary alloc] init];
switch (status) {
case 200:
messages = [[[json_string objectFromJSONString] valueForKey:#"messages"] valueForKey:#"message"];
for (NSDictionary *message in messages)
{
[items addObject:message];
}
[self.tableView reloadData];
break;
default:
break;
}
One, you might want to declare items as an instance of NSMutableArray if you intend to call addObject: on it.
Two, declare it as a property so that if you end up getting it multiple times the older value will be released when you do.
self.items = [NSMutableArray array];
And the correct point of releasing it would be dealloc.
Probably you don't want to release it immediately if you:
use didSelectRowAtIndexPath: method for detail views and pass this data to them
define custom UITableViewCell styles in cellForRowAtIndexPath: method
use this data elsewhere
Best practice is declare an instance variable and synthesize it in .m, use in appropriate operations and release in dealloc method.
One possible release point that you could use is where you refresh your data that shown on table.
Example:
I get dictionaries in an array from an API in my app and use something like that.
MyTableViewController.h
#interface MyTableViewController {
NSMutableArray *items;
}
#property (nonatomic, retain) NSMutableArray *items;
#end
MyTableViewController.m
#implementation MyTableViewController
#synthesize items;
- (void)dealloc
{
[items release];
[super dealloc];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [items count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"FilesCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}
cell.textLabel.text = [[items objectAtIndex:indexPath.row] valueForKey:#"name"];
cell.imageView.image = [UIImage imageNamed:[[NSString alloc] initWithFormat:#"filetype_%#.png", [[items objectAtIndex:indexPath.row] valueForKey:#"type"]]];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MyDetailViewController *detailViewController = [[MyDetailViewController alloc] initWithNibName:#"MyDetailViewController" bundle:[NSBundle mainBundle]];
detailViewController.item = [items objectAtIndex:indexPath.row];
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
detailViewController = nil;
}
}
- (void)getItems
{
[items release];
items = [[NSMutableArray alloc] init];
//Do some requests here
for (NSDictionary *dict in results)
{
[items insertObject:dict atIndex:0];
}
[self.tableView reloadData];
[self stopLoading];
}
#end
Releasing at wrong places some time lead to memory leaks, before allocation itself u can have a condition like if() { [...release]}.Not tested but this kind of release avoid leaks.
The most common is to have the items variable as an attribute of your class, once you will probably need it to use in your tableView:cellForRowAtIndexPath: method.
So, having it as an attribute variable you can release it on the dealloc method.
It's clear that your array item will be used by UITableView to show data.
First declare it as instance variable in your .h class.
.h class
#interface MyClass
{
MSMutableArray* items;
}
#property(nonatomic,retain) MSMutableArray* items;
#end
In your .m class.
#synthesis iMyArray;
And you code for filling the array should be
NSMutabelArray* itemsTemp = [[NSMutabelArray alloc] initWithCapacity:1];
messages = [[[json_string objectFromJSONString] valueForKey:#"messages"] valueForKey:#"message"];
[json_string release];
for (NSDictionary *message in messages) {
NSLog(#"%#",[message valueForKey:#"body"]);
[itemsTemp addObject:message];
}
self.items= itemsTemp;
[itemsTemp release];
itemsTemp = nil;
[self.tableView reloadData];
Now in dealloc release your array instance.
-(void) dealloc
{
if(items )
{
[items release];
items = nil ;
}
[super dealloc];
}
Proper way is make it property in .h class, since you have declared it as property: remember one thing always alloc a property by using self.
your statement items=[[NSMutableArray alloc] init];
is wrong.(use self) also since your property is retain type the using alloc on it increase retain count.that gives you a leak.
so use in this way in viewDidLoad
NSMutableArray *tempArray=[[NSMutableArray alloc] init];
self.items=tempArray;
[tempArray release];
then release your items array in dealloc and set it nil in viewDidUnload
- (void)viewDidUnload {
[super viewDidUnload];
self.items=nil;
}
- (void)dealloc {
[self.items release];
[super dealloc];
}
Hope now you can understand how you should use this.
According to Apple's documentation of UITableView reloadData method:
"[...] For efficiency, the table view redisplays only those rows that are visible"
That means yo should not release the items array as long as the table is being used, i.e. you have to declare the array as a property.
First because if you scroll the view, you will still need the items information to display the rows below or above.
And second, because by being a property you ensure that a previous value is going to be released if you happen to assign a new value to items.
Finally, the common place to release a property is in the dealloc method and depending on your implementation in viewDidUnload method.
I'm having issues with a singleton I've created. It contains two NSMutableDictionary's, which are read and used in three views (and some modal views) throughout the app.
I've added an MKMapView t plot some of the venues inside the dictionaries on a map. When I use the exact same method/function used in every other view to access the data, I receive an EXC_BAD_ACCESS error pertaining to a deallocated dictionary. This comes from NSZombieEnabled:
CFDictionary retain: message sent to deallocated instance
In a dsym'ed trace, it is the replacement of one dictionary with another that is causing grief. The code I'm using to call the function comes from a MKAnnotationView click:
UIControl *tempButton = sender;
NSString *selectedEventsString = [self.eventsArray objectAtIndex:tempButton.tag];
NSLog(#"eventString: %#", selectedEventsString);
[[EventsManager eventsManager] changeSelectedEventsDictionaryTo:selectedEventsString];
[tempButton release];
[selectedEventsString release];
"selectedEventsString" is coming out to a perfectly corresponding event.
The corresponding code in EventsManager:
-(void)changeSelectedEventsDictionaryTo:(NSString *)eventName {
NSLog(#"singleton: %#", eventName);
self.eventString = eventName;
self.selectedEventsDictionary = [self.eventsDictionary objectForKey:eventName];
}
Both selectedEventsDictionary and eventsDictionary are set as #property (nonatomic, retain) in the .H file, and this is the init function:
+ (EventsManager*)eventsManager {
if (eventsManager == nil) {
eventsManager = [[super allocWithZone:NULL] init];
eventsManager.eventsDictionary = [[NSMutableDictionary alloc] init];
eventsManager.selectedEventsDictionary = [[NSMutableDictionary alloc] init];
eventsManager.eventString = [[NSString alloc] init];
eventsManager.mode = [[NSString alloc] init];
}
return eventsManager;
}
This is an example of code used in other views that works fine:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
NSString *eventString = [self.eventsArray objectAtIndex:row];
[[EventsManager eventsManager] changeSelectedEventsDictionaryTo:eventString];
//Modal display code here
}
Any help would be greatly appreciated! I think I've provided all relevant code but let me know if more is needed.
Cheers!
Where to start! I will point out some things that I do see wrong.
First Example. Do not release tempButton and selectedEventString as you never explicitly called retain/copy or alloc and init on them.
UIControl *tempButton = sender;
NSString *selectedEventsString = [self.eventsArray objectAtIndex:tempButton.tag];
NSLog(#"eventString: %#", selectedEventsString);
[[EventsManager eventsManager] changeSelectedEventsDictionaryTo:selectedEventsString];
//DO NOT RELEASE THESE YOU NEVER RETAINED THEM!
[tempButton release];
[selectedEventsString release];
Your static eventsManager is not thread safe which may not be a issue for you but should definitely be looked into.
Read the comments for the following code example
+ (EventsManager*)eventsManager {
if (eventsManager == nil) { //<-- Not thread safe
//DO NOT CALL SUPER USE self
//eventsManager = [[self alloc] init];
eventsManager = [[super allocWithZone:NULL] init];
//You need to autorelease these values or use an autoreleased static method
//eventsManager.eventsDictionary = [NSMutableDictionary dictionary];
//eventsManager.selectedEventsDictionary = [NSMutableDictionary dictionary];
eventsManager.eventsDictionary = [[NSMutableDictionary alloc] init];
eventsManager.selectedEventsDictionary = [[NSMutableDictionary alloc] init];
//Do not bother setting these at all or just set them to nil
eventsManager.eventString = [[NSString alloc] init];
eventsManager.mode = [[NSString alloc] init];
}
return eventsManager;
}
Make sure all of those properties are set to retain or copy and that may fix your problem. If you still have an issue after these fixes you can update your question and I will update my answer.
Testing my app on the device it returns a leak whe i call the copy of a custom object ande i can't understand why.
this is the call:
NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:5];
for (SinglePart *sp in [copyFrom partList]) {
[arr addObject:[sp copy]];
}
self.partList = arr;
[arr release];
this is the method:
- (id)copyWithZone:(NSZone *)zone {
SinglePart *copy = [[[self class] allocWithZone:zone] initWithSinglePart:self];
[copy loadImage];
return copy;
}
this is the method that is called by copyWithZone:
- (id)initWithSinglePart:(SinglePart *)copyFrom {
if (self = [super init]) {
self.imagePath = [copyFrom.imagePath copy];
self.color = [UIColor colorWithCGColor:copyFrom.color.CGColor];
self.hasOwnColor = copyFrom.hasOwnColor;
self.blendingMode = copyFrom.blendingMode;
}
return self;
}
copy returns a new object with retain count 1. Meaning you need to release the new object, which you are not doing.
NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:5];
for (SinglePart *sp in [copyFrom partList]) {
SingPart *theCopy = [sp copy];
[arr addObject:theCopy];
[theCopy release];
}
self.partList = arr;
[arr release];
Even your custom copyWithZone: method inits an object, but does not autorelease it, which is the expected behavior of a copy method. Copy must be balanced just like a retain or init, meaning you must balance it with release at some point.
Lastly, your initWithSinglePart: method leaks the imagePath as well. In this case if you declare the imagePath property as copy instead of retain then you don't need to do this manually at all. Then you simply assign the value and let the property setter do it for you.
// Header
#property (copy) NSString *imagePath;
// Now this will do the copy for you
self.imagePath = copyFrom.imagePath;
Also, is the property imagePath defined with retain or copy semantics?
If so you need to add an autorelease here:
self.imagePath = [[copyFrom.imagePath copy] autorelease];
because the default setter will retain/copy it too.
So, you either need to autorelease, or omit the "self." to bypass the default setter.
You are making a copy of sp and then adding it to the array. The array then retains the object so your retain count is now 2.
In the end you release arr, thus making the retain count of it's items 1.
You should either add another release to the sp objects, or not use copy.
Try this:
self.partList = [NSMutableArray arrayWithCapacity:5];
for (SinglePart *sp in [copyFrom partList]) {
[arr addObject:sp];
}