To start let me tell you I am a total Objective-C beginner. This is my problem:
I have a NSMutableArray that stores objects, (Player) that has the name of the player and his/her score.
I am able to add objects to the array using addObject, but I am having trouble traversing this array. This
is how I do it:
// Get the reference to the array
NSMutableArray *myarray = [delegate getArray];
// Create a numerator
NSEnumerator *e = [myarray objectEnumerator];
id object;
while (object = [e nextObject])
{
[object printPlayer];
}
The method printPlayer belongs to the Player class and it just prints the name and the score.
The problem is when I have three players in the array and I am trying to print the content, it reaches this error inside the printPlayer method:
Thread 1: EXC_BAD_ACCESS(code=1, address=0x0000008)
Strangely if I use NSLog(#"%#", object); instead of [object printPlayer]; it prints a reference to the object and does not reach any error.
Anyone could point me what could be the problem when I try to use [object printPlayer]
Cheers
Update 1:
This is my printPlayer method:
-(void) printPlayer
{
NSLog(#"\n\nName: %#\nScore: %d", playerName, playerScore);
}
Update 2:
Player.h:
#interface PROGPlayer : NSObject
#property (nonatomic, assign) NSString *playerName;
#property (nonatomic, assign) int playerScore;
-(id) init: (NSString *) n;
-(void) printPlayer;
#end
Player.m:
#import "PROGPlayer.h"
#implementation PROGPlayer
#synthesize playerName;
#synthesize playerScore;
/**
* Player's class constructor
* #param n Player's name
* #param s Player's score
*/
-init: (NSString *) n
{
if (!(self = [super init])) return nil;
else
{
playerName = n;
playerScore = 0;
}
return self;
}
-(void) printPlayer
{
NSLog(#"\n\nName: %#\nScore: %d", playerName, playerScore);
}
#end
It seems like your problem is in the way you're defining your properties.
You're using assign rather than strong, or copy.
In a nutshell, it's because strong implies that you want your object to be retained.
Using copy implies that you want to create a new copy of an object or a value and set that as value of your property... As Mario and Jarsen explain, using copy is better practice when working with arrays to prevent the array being mutated (i.e. values changed) while it is being enumerated / traversed. Using copy also retains the new object.
If you're using ARC and your objects are not retained, then they will be released automatically by the compiler.
Using assign means that you assume the new object has been retained elsewhere and that you don't want to retain it again.
I suppose what was happening is that you were assigning your variable to your property, but the variable was being released (and hence resulting in nil) and causing the crash.
Here are a few links:
New to Objective C: Need help understanding strong reference vs assign
Objective-C ARC: strong vs retain and weak vs assign
Clarification on assign, retain, copy, strong?
Your playerName property should best be copied instead of assigned
#property (nonatomic, copy) NSString *playerName;
When trying to access the assigned value, the object most likely is gone causing the bad access.
Also remember to release playerName in dealloc when you set the property to copy.
Cheers
You just want to enumerate the array?
for (CustomClass *object in myArray){
[object printPlayer];
}
Either what Mike Z said or the "crude":
for (int i = 0; i < myArray.count; i++) {
CustomClass* object = [myArray objectAtIndex:i];
[object printPlayer];
}
While there are more elegant schemes, you can clearly understand what this one is doing, and how an NS(Mutable)Array is just a simple analog to a standard C array.
Related
I create a NSMutableArray that I need as long as my app lives, lets call it suseranArray, just after the #implementation of my main class. This Array will hold several objects of a class called Vassal. A Vassal is simply:
1) A NSMutableString
2) Another NSMutableString
3) A NSMutableArray
4) Another NSMutable Array
Each Vassal created is also needed for the life of the app, and they never change.
These objects are made as (retain) properties in an .h file, synthesized in the .m file, and each given an alloc+init whenever the object Vassal is created during the init function. Each vassal has data filled in and stored in the suzerain Array. the 3rd item always has several elements, and after a bug appeared, I put a line to check if it is ever empty, but it never is, and life is good.
Now, later on when a certain Vassal object is needed, we try to access its 3rd property to fetch the data in there, and sometimes that array empty... I checked to see if it disappeared somehow, but it is always there on the debug, carrying a nice address like 0x2319f8a0 which makes sense since the NSMutableString just above it is at address 0x2319fb40 - (I was expecting 00000000 after a lot of headache). What is happening? I my head, I am creating an RETAINed objects, which retains data put in by default, and that object is put inside another, but somehow the data inside the array vanishes. What possible scenario could lead to this? Thank you for your time :)
Note: the last array currently just holds one item at this stage of development, and curiously enough, that one item is never missing, despite the two arrays being 'brothers'
Vassal.h
#interface Vassal : NSObject
#property (retain) NSMutableString *wordBody;
#property (retain) NSMutableString *wordCode;
#property (retain) NSMutableArray *wordRelations;
#property (retain) NSMutableArray *wordLinks;
#end
Vassal.m
#implementation Vassal:NSObject
#synthesize wordBody;
#synthesize wordCode;
#synthesize wordRelations;
#synthesize wordLinks;
-(NSObject*) init
{
if(self=[super init])
{
wordBody=[[NSMutableString alloc] init];
wordCode=[[NSMutableString alloc] init];
wordRelations=[[NSMutableArray alloc] init];
wordLinks=[[NSMutableArray alloc] init];
}
return self;
}
//Somewhere in Suseran:
-(void)fillStuff
{
...
Vassal *vassal=[Vassal new];
for (int i=0;i<[originalDataString length];i++)
{
...
[vassal.wordRelations addObject:anItem];
...
}
int errorTest=[vassal.wordRelations count];
if (errorTest==0)
{
//breakpoint here. Program NEVER comes here
}
[bigArrayOfVassals addObject:vassal];
}
//these arrays are never touched again but here:
-(void) getVassalstuff:(NSMutableString*)codeOfDesiredVassal
{
Vassal *aVassal;
for (int i=0;i<[bigArrayOfVassals count];i++)
{
aVassal=bigArrayOfVassals[i];
if ([codeOfDesiredVassal isEqualToString:aVassal.wordCode)
{
int errorTest=[aVassal.wordRelations count];
if (errorTest==0)
{
//yay! this breakpoint sometimes is hit, sometimes not,
//depending on code's mood. Why is this happening to me? :,(
}
}
}
}
I see that that you have properties that are mutable (which is itself a bad idea except for specific cases) and that you are retaining them.
Mutability means that if you have set the array as a property based on some other array, and if that original array is changed, the array in your property is also changed. It may be, and I don't know because you haven't shown any code, that you are emptying the original array, and thus changing the array you have as a property
Solutions:
My preferred solution is to use the immutable versions of these classes for your properties; NSString, NSArray and instead of retain use copy
A second solution is to leave the properties as mutable, but write a custom setter for each of them that stores a mutableCopy of the object that you pass in.
In both of these cases, your property will be a copy of the object used to set the property, so that if the object is changed outside of your class it will not affect your class's properties.
edited to add, after a comment
If you declare your property as
#property (copy) NSArray wordRelations;
Then simply writing
vassal wordArray = tempArray;
will do the same thing and is cleaner and more readable..
There is a need to run some code when one object get dealloc. E.g, I set up one observer which updates the label A's text when object B's name changed. When label A's retain count reach 0, I want to remove the observer from B.
Possible solutions:
1 Subclass and call the clean code in dealloc.
2 Create a wrapper class which able to run arbitrary code in dealloc and associate this object to label A. When A get dealloc, the associated object get dealloc too (suppose only A holds the strong ref to it), then the code get called.
I don't like the 1st one since it is so intrusive that makes it barely useless, need to subclass just for some easy stuff. So I am using No.2.
Do you have any comments? How do you do it?
I put my solution here in case someone needed.
#interface ExecuteWrapper : NSObject
#property (nonatomic, copy) void(^block)();
-(void)dealloc;
#end
#implementation ExecuteWrapper
-(void)dealloc{
if(self.block){
self.block();
}
}
#end
#implementation NSObject (SLUtil)
+(void)executeWhenDealloc:(NSObject *)object block:(void(^)())block{
static char key;
NSMutableArray *executeWrapperArray = [object associatedValueForKey:&key];
if ( executeWrapperArray == nil){
executeWrapperArray = [NSMutableArray array];
[object associateValue:executeWrapperArray withKey:&key];
}
ExecuteWrapper *executeWrapper = [[ExecuteWrapper alloc] init];
executeWrapper.block = block;
[executeWrapperArray addObject:executeWrapper];
}
#end
In client code
[NSObject executeWhenDealloc:labelA block:^{
// clean up code
}];
Note: Keep in mind that don't hold a strong ref to label A in the label
I'm getting Property 'aVariable' not found on object of type id when trying to read or write aVariable to the array. Shouldn't it be known what class the object is that I added? Also noticed that it works to read the value with NSLog(#" %#",[[anArray objectAtIndex:0] aVariable]);
I'm a beginner at Objective C so it might be some simple thing I'm not getting.
AnObject
#interface AnObject : NSObject
#property (nonatomic,readwrite) int aVariable;
#end
AnotherObject
#interface AnotherObject : NSObject
#end
test.h
#import "test.h"
#implementation AnObject
#synthesize aVariable;
- (id)init
{
self = [super init];
if (self) {
aVariable=0;
}
return self;
}
#end
test.m
#implementation AnotherObject
- (id)init
{
self = [super init];
if (self) { }
return self;
}
- (NSMutableArray*) addToArray
{
NSMutableArray* anArray = [[NSMutableArray alloc] initWithCapacity:0];
AnObject* tempObject = [[AnObject alloc] init];
tempObject.aVariable=10;
[anArray addObject:tempObject];
// Property 'aVariable' not found on object of type 'id'
[anArray objectAtIndex:0].aVariable=[anArray objectAtIndex:0].aVariable + 1;
// Property 'aVariable' not found on object of type 'id'
NSLog(#" %i",[anArray objectAtIndex:0].aVariable);
// This works
NSLog(#" %i",[[anArray objectAtIndex:0] aVariable]);
return anArray;
}
#end
This code:
[anArray objectAtIndex:0].aVariable
Can be broken down into 2 sections:
[anArray objectAtIndex:0]
This returns an id- because you can put any type of object into an array. The compiler doesn't know what type is going to be returned by this method.
.aVariable
This is asking for the property aVariable on the object returned from the array - as stated above, the compiler has no idea what this object is - it certainly won't assume that it is an AnObject, just because that is what you added a line or two earlier. It has to evaluate each statement on its own. The compiler therefore gives you the error.
It is a little more forgiving when using accessor methods:
[[anArray objectAtIndex:0] aVariable];
This will give you a warning (that the object may not respond to the selector) but it will still let you run the code, and luckily enough your object does respond to that selector, so you don't get a crash. However this is not a safe thing to rely on. Compiler warnings are your friends.
If you want to use the dot notation, you need to tell the compiler what type of object is being returned from the array. This is called casting. You can either do this in two steps:
AnObject *returnedObject = [anArray objectAtIndex:0];
int value = returnedObject.aVariable;
Or with a mess of brackets:
int value = ((AnObject*)[anArray objectAtIndex:0]).aVariable;
The extra brackets are required to allow you to use dot notation when casting. If you want to use the accessor methods, you need fewer round brackets but more square brackets:
int value = [(AnObject*)[anArray objectAtIndex:0] aVariable];
-[NSArray objectAtIndex:] returns an id pointer. Since id does not contain information about your protocol the compiler cannot know the object has this property you declared; that is why it complains.
You can solve this by either cast the return value of objectAtIndex: or by using the getter/setter notation, i.e. [anArray objectAtIndex:0] setAVariable:...]. Also make sure you import your protocol definition, otherwise the compiler might also not know about the declared method and issue a warning.
I have implemented the following code to assign NSMutableArray to a property -
NSMutableArray * anArray = [responseDictionary valueForKeyPath:#"tags"];
NSLog(#"The array length is=%d",[anArray count]);
for (NSString *s in anArray) {
NSLog(#"you are %#", s);
}
[self setActiveTagArray:anArray];
It prints out the string values fine. But in the setter function, if I place a breakpoint I see that it shows there are two objects but they are "Out of Scope". What does this mean? What am I doing wrong? My getter also does not fetch any values. The property functions -
-(void)setActiveTagArray:(NSMutableArray *)tags
{
activeTagArray = [[NSMutableArray alloc] init];
activeTagArray = tags;
//NSLog(#"%#",[activeTagArray count]);
}
-(NSMutableArray *)getActiveTagArray
{
return activeTagArray;
}
Is activeTagArray a class variable as well as a property. Consider using _activeTagArray as the class variable name. And then in the .m file just use #synthesize activeTagArray = _activeTagArray;, and for get the second two methods completely.
Response to comment:
You said "I have implemented the following code to assign NSMutableArray to a property". I took this to mean you have "#property (nonatomic, retain) NSMutableArray *activeTagArray;" in your .h file. If this is the case then you would access it thru otherObject'sNameForYourClassHere.activeTagArray.
#synthesize create accessors & mutators for you.
If I have a variable in my view controler
viewcontroller.m
#interface MemoryTestViewController : UIViewController
{
NSMutableArray *array;
}
#end
in my implementation
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *aux = [[NSMutableArray alloc] initWithCapacity:1];
array = aux;
[aux release];
// Do i have to do array release?
}
Do i have to release my variable array somewhere? Theoricaly i havent allocated that variable...
I testes the memory leaks and even if i dont release anything the instruments doesn't detect any leak.
No, you don't need to release. All you do is assign the pointer of aux to your array variable.
array is invalid at the moment where you release aux.
This is probably not as intended. If you want to work with array, you'll have to retain it.
You've already released the array with [aux release]; -- you in fact have the opposite problem to a leak: an over-release.
Assignments in Objective-C are just assignments of pointers; there's no copying or automatic memory management. When you say array = aux;, array now points to the exact same object as aux. If you then get rid of aux by releasing it (and therefore letting it be deallocated), array doesn't point to anything anymore.*
You have a couple of options for fixing this:
(Simplest) Assign the newly-created array directly to array:
array = [[NSMutableArray alloc] initWithCapacity:1];
This gives you ownership of the new array, under the name array. Don't release it until you are done with it (possibly in dealloc; certainly not in this method).
(Best) Create a declared property for array and let that mechanism handle the memory management for you:
#interface MemoryTestViewController : UIViewController
{
NSMutableArray *array;
}
#property (copy, nonatomic, setter=setArrayByMutableCopy) NSMutableArray * array;
#end
#implementation MemoryTestViewController
#synthesize array;
// Properties can't automatically make mutable copies, so you need to create
// your own setter method.
- (void) setArrayByMutableCopy: (NSMutableArray *)newArray {
NSMutableArray * tmp = [newArray mutableCopy];
[array release];
array = tmp;
}
...
*Or, rather, it points to a place where there used to be a valid object, which is a great way to make your program crash.
No. Assigning an object to a variable does not retain it. However if you plan to use that variable for a while, you should retain it and release it when you are done with it.
alloc raised the retain counter to 1 and [aux release] set it to 0
You should add a property:
#property (nonatomic, retain) NSMutableArray *array;
and later in your viewDidLoad:
// wrong, leaks: self.array = [[NSMutableArray alloc] initWithCapacity:1];
array = [[NSMutableArray alloc] initWithCapacity:1];
and yes, somewhat later release it, probably in dealloc..