Please, correct me, where i'm wrong (i'm a begginer)
I want to store a class objects into NSArray. For example:
MySimpleClass *mscObj = [[MySimpleClass alloc] initWithSomething:#"something"];
NSMutableArray *myarr = [[NSMutableArray alloc] init];
[myarr addObject:mscObj];
mscObj = #"somethingelse";
And then my myarr index 0 change from #"something" to #"somethingelse". Why? Can i store a copy only to array?
EDIT:
In MySimpleClass:
#import <Foundation/Foundation.h>
#interface MySimpleClass : NSObject {
}
#property (strong,nonatomic) NSString *objectName;
#property (strong,nonatomic) NSString *objectTarget;
-(void)addName:(NSString*)name;
-(void)addTarget:(NSString*)target;
#end
.m file
#import "MySimpleClass.h"
#implementation MySimpleClass
#synthesize objectName;
#synthesize objectTarget;
-(void)addName:(NSString*)name{
self.objectName = name;
}
-(void)addTarget:(NSString*)target{
self.objectTarget = target;
}
-(void)flushAll {
self.objectTarget = nil;
self.objectName = nil;
}
#end
Then In other class i have:
MySimpleClass *mscObj = [[MySimpleClass alloc] initWithSomething:#"something"];
[myarr addObject:[mscObj copy]];
int testunit = [myarr count];
for(int i=0;i<testunit;i++) {
MySimpleClass *myelement = [myarr objectAtIndex:i];
NSLog(#"%# : %#",myelement.objectName,myelement.objectTarget);
}
Given the code you've posted, the first object (index 0) in the array won't change due to the fourth line, mscObj = #"something else";. That line changes the value of the pointer mscObj itself, so that it'll point to a completely different object. If the object in the array is changing, I believe that the real code you're using won't quite match what you've posted -- please check that.
However, if you use the mscObj pointer to change an attribute of the object that it points to, then you'll have changed the object in the array:
mscObj.something = #"somethingelse";
Here you're changing the something property of the object that mscObj refers to, but you're not changing the value of mscObj. This will change the contents of the object in the array, since that's the same object that mscObj points to.
Try:
[myarr addObject:[[mscObj copy] autorelease]];
When you do this
[myarr addObject:mscObj];
myarr is retaining a reference to mscObj. So any time mscObj is changed myarr will reflect that change.
When you do this
[myarr addObject:[mscObj copy]]; // I've left the autorelease out for simplicity
myarr retains a reference to a new copy of mscObj. When mscObj is then updated myarr will not reflect the change because it's reference points to a completely different object.
EDIT
For copy to work your class ** MySimpleClass** needs to implement NSCopying. See this SO answer for help on that.
It is because of the pointers. Your object at index:0 remains at the same location in the memory.It will always change.
try releagind the mscObj after adding the array and create a new MySimpleClass object with #"somethingelse"
mscObj is a reference and what the array stores are references. So if you manipulate a object from outside the container the reference reflects the changes. Think about the by value by reference difference.
Related
My purpose: making an API call to a server, and getting back from them is an array of data named dataArr and I want to store these data to another array for later need.
What I am doing so far is
myClass.h:
#propery ( nonatomic, retain ) NSArray *dataList;
myClass.m:
#implementation myClass
-(void)receivedData:(NSArray*) dataArr {
// ???
}
To fill in at line 3, I have two options, option A:
dataList = dataArr;
or option B:
[dataList release];
[dataArr retain];
dataList = dataArr;
I think option A is the right way to do it because dataList is declared as retain in the header file. Therefore, the setter will make sure to release a current array (dataList) and reain a received array (dataArr) as well
I just want to double check that I am on the right path.
Please correct me if I have just made a mistake in the middle. Thanks
Any comments are welcomed.
dataList = [dataArr];
this is not valid Objecitve-C. If you wanted to write
dataList = dataArr;
that's still a no-go, as you're acessing the instance variable directly, not through the property setter, that is, your array won't be retained and it will badly crash.
[dataList release];
[dataArr retain];
dataList = dataArr;
is wrong again. If dataList was the same as dataArr, and the reference of the object (self) was the last reference to it, then it would get deallocated, breaking the following retain message, and most likely crashing again.
If you have a property setter (which you have), simply write
self.dataList = dataArr;
this will retain the array correctly. By the way, the implementation of the setter is something like your last method, but it checks either for inequality:
- (void)setDataList:(NSArray *)dl
{
if (dataList != dl)
{
[dataList release];
dataList = [dl retain];
}
}
or pre-retains the object to be set:
- (void)setDataList:(NSArray *)dl
{
[dl retain];
[dataList release];
dataList = dl;
}
add #synthesize dataList; so the compiler can generate the default setter
then in line 4 add:
self.dataList = dataArr;
The default setter will take charge of releasing and retaining in a correct manner
Hopefully I can make this clear, but I am new to Objective-C and to be honest not great with Arrays in the first place.
So, I have a Singleton class (called SingletonState) that I am using to pass variables around my app (please can we leave the whether I should use Singleton classes out of this - I will fix that later). In this class, I have an NSMutableArray (called arrayMyEvents). I have also created a class that I am going to store a list of events (called EventClass). When the user logs in, I call a web service and get back 3 strings. The 3rd string is a comma separated list of value. I parse the data and populate the custom class EventClass. I then add this class to the SingletonState.arrayMyEvents.
I have all of this working. I can go to another ViewController and access the "count" of items in arrayMyEvents.
PROBLEM: I now want to edit one of the ScheduledEventsClass"es" in my array. How do I get access to it and edit some of the properties and update the one in my SingletonState class?
Here is some of the code, that I've tried:
NSString *sWebServiceEvents = [[NSString alloc] initWithFormat:#"%#", [result objectAtIndex:2]];
if ( [ sWebServiceEvents isEqualToString:#"NULL" ] != true ) {
NSArray *arrayEvents = [sWebServiceEvents componentsSeparatedByString:#","];
// If the array has not been initialized they initialize it.
if (sharedState.arrayMyEvents == nil) {
sharedState.arrayMyEvents = [[NSMutableArray alloc ] init ];
}
for (NSString * sEvent in arrayEvents) {
// Set equal to the value of the array (the Event Number) at the same
// position as the row that we are being asked to return a cell/row for.
EventClass *eventClass = [[EventClass alloc] retain];
eventClass.sEvent = sEvent;
[ sharedState.arrayEvents addObject:eventClass ];
}
NSLog(#"LoginView - sharedState.arrayMyEvents Count: %d", [sharedState.arrayMyEvents count]);
}
Here is me trying to access it in another ViewController:
EventClass *eventClass =
[sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"eventClass.sEventNumber: ", eventClass.sEventNumber);
eventClass.sLocation = #"Jason's Big Location";
You're going to have some memory leaks from the sEvent loop. [[EventClass alloc]retain] leaves you an uninitialized EventClass object with a reference count of 2. You'll need to change that to [[[EventClass alloc] init] autorelease] to keep it from leaking. The arrayEvents NSMutableArray will retain it during the addObject: call. (Shouldn't that be [sharedState.arrayMyEvents addObject: eventClass] in the loop?)
After that, all you have to do to edit the EventClass object in the second block of code is edit it. The eventClass variable is a pointer to an object in the array. Anything done to that object doesn't affect the pointer referencing it, it affects data referenced by it. The code you have in the second block should change the sLocation of the selected object as you intend.
You have a few more memory leaks in there, too. Use Cmd-Shift-A to build with the static analyzer and it'll tell you where.
Maybe the problem is that you put them in sharedState.arrayEvents but try to take them out of sharedState.arrayMyEvents. Different variables.
Also, lots of memory leaks.
Thanks John and St3fan, your answers and time are appreciated!!!
I think that I figured out my issue:
Basically, the class that I created (EventClass) had the properties setup like this:
#property (nonatomic, assign) NSString *sStudyNumber;
#property (nonatomic, assign) NSString *sTheater;
but, they should be (or at least I got it to work like this):
#property (nonatomic, retain) NSString *sStudyNumber;
#property (nonatomic, retain) NSString *sTheater;
Then, in my second view I was able to do this:
EventClass *eventClass = [sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"MyEvents: %#", eventClass.sEventNumber);
eventClass.sLocation = #"Jason's Big Location";
I then checked it in another method of the view using this and it was still there:
EventClass *eventClass = [sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"MyEvents: %#", eventClass.sEventNumber);
NSLog(#"MyEvents: %#", eventClass.sLocation);
I also, checked it in yet another view and the value was maintained in the SharedState.arrayMyEvents without issue. :)
In the end, I believe that I boiled down to the difference between "assign" and "retain".
Now, on to the memory leaks :(
Please, let me know if you see any other issues with this.
Thanks,
Jason
Here is a simple one for you guys . I'm defining a class to store variables in so I can reuse those variables in different ViewControllers .
Here is how I do it, it`s obviously not working ( that's why I'm asking a question ... ):
I declare a class :
VariableStore.h
#interface VariableStore : NSObject {
int evTe;
}
#property (nonatomic) int evTe;
+ (VariableStore *)shareInstance;
#end
VariableStore.m
#implementation VariableStore
#synthesize evTe;
+ (VariableStore *)sharedInstance {
static VariableStore *myInstance = nil;
return myInstance;
}
#end
Now in my FirstViewController I want to set the value for evTe :
[[VariableStore sharedInstance] setEvte:2];
NSLog(#"value testing, %i", evTe);
And this keeps on returning 0 unfortunately, Im obviously missing something important here but I can't figure out what it is .
Later on Id like to set the value for evTe here in the FirstViewController and then reuse it back in the SecondViewController ..
You are setting your shared instance to nil and then returning it:
static VariableStore *myInstance = nil;
return myInstance;
A nil instance won't hold your variable. It's nil.
First off you shouldn't be using a singleton to pass around variables. If you're going to do that then you might as well just use global variables instead (don't do that either by, the way). Second, if you insist on using a singleton, you need to read up on how to use them.
Finally, if you want to pass variables between view controllers, you either need another view controller that is a parent to the two to facilitate passing data between them, or one needs to call the other and take the first one or its data as a parameter.
Well, you're asking for the value of evTe without calling the object to which it belongs. Try this:
NSLog(#"value testing, %i", [[VariableStore sharedInstance] evTe]);
If you keep using the singleton for a number of times, you might want to do:
VariableStore *vStore = [VariableStore sharedInstance];
so you can do:
[vStore setEvTe:2];
NSLog(#"value testing, %i", [vStore evTe]);
And look out for what Matt said about nilling your singleton ;)
I think in nslog you should output not just evTe, but [[VariableStore sharedInstance] evTe].
First, you have to declare the static variable outside the function, in a way both controllers can access.
static VariableStore* myInstance = nil;
The singleton sharedInstance should be:
if(myInstance == nil)
{
myInstance = [[VariableStore] alloc] init];
}
return myInstance;
Check it:
- (IBAction)toggleFavorite {
DataManager *data = [DataManager sharedDataManager];
NSMutableSet *favorites = data.favorites;
if (thisEvent.isFavorite == YES) {
NSLog(#"Toggling off");
thisEvent.isFavorite = NO;
[favorites removeObject:thisEvent.guid];
[favoriteIcon setImage:[UIImage imageNamed:#"notFavorite.png"] forState:UIControlStateNormal];
}
else {
NSLog(#"Toggling on, adding %#", thisEvent.guid);
thisEvent.isFavorite = YES;
[favorites addObject:thisEvent.guid];
[favoriteIcon setImage:[UIImage imageNamed:#"isFavorite.png"] forState:UIControlStateNormal];
}
NSLog(#"favorites array now contains %d members", [favorites count]);
}
This is fired from a custom UIButton. The UI part works great--toggles the image used for the button, and I can see from other stuff that the thisEvent.isFavorite BOOL is toggling happily. I can also see in the debugger that I'm getting my DataManager singleton.
But here's my NSLog:
2010-05-13 08:24:32.946 MyApp[924:207] Toggling on, adding 05db685f65e2
2010-05-13 08:24:32.947 MyApp[924:207] favorites array now contains 0 members
2010-05-13 08:24:33.666 MyApp[924:207] Toggling off
2010-05-13 08:24:33.666 MyApp[924:207] favorites array now contains 0 members
2010-05-13 08:24:34.060 MyApp[924:207] Toggling on, adding 05db685f65e2
2010-05-13 08:24:34.061 MyApp[924:207] favorites array now contains 0 members
2010-05-13 08:24:34.296 MyApp[924:207] Toggling off
2010-05-13 08:24:34.297 MyApp[924:207] favorites array now contains 0 members
Worst part is, this USED to work, and I don't know what I did to break it.
--EDIT: By request, my shared data singleton code:
.h
#import <Foundation/Foundation.h>
#interface DataManager : NSObject {
NSMutableArray *eventList;
NSMutableSet *favorites;
}
#property (nonatomic, retain) NSMutableArray *eventList;
#property (nonatomic, retain) NSMutableSet *favorites;
+(DataManager*)sharedDataManager;
#end
.m:
#import "DataManager.h"
static DataManager *singletonDataManager = nil;
#implementation DataManager
#synthesize eventList;
#synthesize favorites;
+(DataManager*)sharedDataManager {
#synchronized(self) {
if (!singletonDataManager) {
singletonDataManager = [[DataManager alloc] init];
}
}
return singletonDataManager;
}
- (DataManager*)init {
if (self = [super init]) {
eventList = [[NSMutableArray alloc] init];
favorites = [[NSMutableSet alloc] init];
}
return self;
}
------EDIT EDIT EDIT EDIT------
At #TechZen's suggestion, I moved my accessor methods into the data manager singleton. Here's what it now looks like:
#import "DataManager.h"
static DataManager *singletonDataManager = nil;
#implementation DataManager
#synthesize eventList;
#synthesize favorites;
+(DataManager*)sharedDataManager {
#synchronized(self) {
if (!singletonDataManager) {
singletonDataManager = [[DataManager alloc] init];
}
}
return singletonDataManager;
}
- (DataManager*)init {
if (self = [super init]) {
eventList = [[NSMutableArray alloc] init];
favorites = [[NSMutableSet alloc] init];
}
return self;
}
#pragma mark -
#pragma mark Data management functions
- (void)addToFavorites:(NSString *)guid
{
[self.favorites addObject:guid];
NSLog(#"Item added--we now have %d faves.", [favorites count]);
}
- (void)removeFromFavorites:(NSString *)guid
{
[favorites removeObject:guid];
NSLog(!"Item removed--we now have %d faves.", [self.favorites count]);
}
#end
I made my viewcontroller where this is happening call [[DataManager sharedManager] addToFavorites:Event.guid] instead of adding the item right to the favorites set itself, but I left the logging stuff that was there in place.
Here's my log:
2010-05-13 13:25:52.396 EverWondr[8895:207] Toggling on, adding 05db685f65e2
2010-05-13 13:25:52.397 EverWondr[8895:207] Item added--we now have 0 faves.
2010-05-13 13:25:52.398 EverWondr[8895:207] favorites array now contains 0 members
2010-05-13 13:25:53.578 EverWondr[8895:207] Toggling off
2010-05-13 13:25:53.579 EverWondr[8895:207] favorites array now contains 0 members
So.... the DataManager object can't even add anything to its own property! And it doesn't throw an exception like it would if it was a non-mutable type, it just silently fails!
Just for fun, I went through and changed it to an NSMutableArray, which I'm more familiar with. Same behavior.
As phellicks suggest above, you might not be returning a mutable set from data.favorites. Although, you should be getting a compiler warning if that is the case.
This 05db685f65e2 is not a real guid. It looks more like the address of an object. You should check the type on 'thisEvent.guid` to make sure your got an object and the correct type of object.
Unrelated to your main problem, I would add that (1) this:
NSMutableSet *favorites = data.favorites;
... is rather pointless and just adds another possible source of error. There is no reason not to just use data.favorites directly in the code. (see (3) below)
(2) When accessing an external object, even a singleton, it is good practice to make the reference to the external object a property of the class especially in the case of a critical object like a data model. This lets you control and track access to the external object.
(3) Don't treat singletons as naked global variables. This will lead to grief. Instead, wrap access to the data models internal data in specific methods. For example, instead of accessing the data.favorites directly create a method like:
- (void) addToFavoritesGuid:(id) aGuid;
or
- (void) addToFavoritesGuid:(GuidClass *) aGuid;
This will give your data model control over its internals and give it the ability to refuse to add objects that shouldn't belong there.
Edit
From comments:
Okay, re what I'm actually
returning... I just used debug to step
through my singleton's initializer.
Examining the ivars of my DataManager
object, I see that my favorites, which
is initialized in init with favorites
= [[NSMutableSet alloc] init]; is actually getting created as a NSCFSet,
and I don't know what that is nor what
to make of it..
NSSet like all the collections and strings is actually a class cluster i.e. a collection of subclasses that all share the same interface. When you create a set the actual class you get back maybe different depending on how it was created. In this case, you're getting back NS-Core-Foundation-Set which is the standard core class for NSSet.
Therefore, your problem is that favorites is initialized as a mutable set but is being assigned to a immutable set. This is why you can't add anything to it.
This initialization:
favorites = [[NSMutableSet alloc] init];
... is being disposed of by:
NSMutableSet *favorites = data.favorites;
If you have an instance variable and you create a local variable of the same name, the local symbol will dominate in the scope it was created in. This appears to work because as a subclass of NSSet, NSMutableSet responds to all the methods and attributes of NSSet.
However, you must be getting a spate of warnings from your linker when you build. You shouldn't ignore those errors. You should treat them as fatal errors because that's what they will be at runtime.
To resolve your problem:
(1) Declare data.favorites as a mutable array and just access it directly. Having another local variable assigned to the same address buys you nothing.
(2) Declare favorites as mutable array property of the current object. Initialize it from data.favorites like:
self.favorites=[NSMutableSet setWithCapacity:[data.favorites count]];
[self.favorites setSet:data.favorites];
// ... add or remove items
data.favorites = self.favorites;
(3) Move all the logic for adding or removing objects in data.favorites to custom methods in the data model object (see above)
Three is the best choice.
Edit02
It looks like the class clusters are hiding the true classes of all classes in the cluster. I ran the following test code:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSSet *s=[NSSet setWithObject:#"setWithObject"];
NSMutableSet *m=[NSMutableSet setWithCapacity:1];
[m addObject:#"Added String"];
NSMutableSet *n = [[NSMutableSet alloc] initWithCapacity:1];
[self showSuperClasses:s];
[self showSuperClasses:m];
[self showSuperClasses:n];
[self showSuperClasses:#"Steve"];
}
- (void) showSuperClasses:(id) anObject{
Class cl = [anObject class];
NSString *classDescription = [cl description];
while ([cl superclass])
{
cl = [cl superclass];
classDescription = [classDescription stringByAppendingFormat:#":%#", [cl description]];
}
NSLog(#"%# classes=%#",[anObject class], classDescription);
}
... and got this output:
NSCFSet classes=NSCFSet:NSMutableSet:NSSet:NSObject
NSCFSet classes=NSCFSet:NSMutableSet:NSSet:NSObject
NSCFSet classes=NSCFSet:NSMutableSet:NSSet:NSObject
NSCFString classes=NSCFString:NSMutableString:NSString:NSObject
Clearly, the report from the debugger and the class function are useless in figuring out the true class of any instance that belongs to cluster. It didn't used to be this way. This is a recent change. I presume its part of the "toll-free bridging" from Core Foundation.
You can add items to favorites because all definitions of favorites in both classes are NSMutableSet.
In any case, your problem is that you have two separate definitions of favorites in the same class. You are getting a warning from the linker saying:
Local declaration of "favorites" hides instance variable
I think the problem can be explained by the runtime confusing the two favorites. You add objects to one favorites but you log the other one.
The local redefinition of favorites serves absolutely no purpose. Remove it and see if the problem persist.
Been searching for the answer to this for a while now and I think due to the nature of my array set up, I may be searching for the wrong answer!
I have a class which handles adding items to my array:
// Item.h
#interface Item : NSObject {
NSString *name;
NSNumber *seconds;
}
#property(nonatomic,copy) NSString *name;
#property(nonatomic,copy) NSNumber *seconds;
- (id)initWithName:(NSString *)n seconds:(NSNumber *)sec;
#end
and...
//item.m
#implementation Item
#synthesize name, seconds;
- (id)initWithName:(NSString *)n seconds:(NSNumber *)sec {
self.name = n;
self.seconds = sec;
return self;
}
#end
So to add an item, I use
Item *item1 = [[Item alloc] initWithName:#"runnerA" seconds:[NSNumber numberWithInt:780]];
I have some code which allows a user to edit a textfield (runner name) and the time which is a UIdatepicker set to hours and minutes. In the save method, that's working fine. It's the UPDATE that I cannot get to work. I've tried alsorts! Here's the code at the moment...
mainAppDelegate *appDelegate = (mainAppDelegate *)[[UIApplication sharedApplication] delegate];
Item *item = [[Item alloc] initWithName:inputName.text seconds:[NSNumber numberWithInt:secs]];
[appDelegate.arrItems replaceObjectAtIndex:rowBeingEdited withObject:item];
The above is simply adding a new item to the array (which is what I don't want). I'm not sure how to replace values. At the function, I have the row I need to update (rowBeingEdited) and the fields inputName.text and secs are both OK. (NSLog out confirms this).
How do I use the replaceObjectAtIndex to actually replace it with the values?! It's driving me mad now!!
Since you are simply trying to edit a particular row, why not use those property accessors that you already have set up in Item? It would look something like this:
Item *item = (Item *)[appDelegate.arrItems objectAtIndex:rowBeingEdited];
[item setName:inputName.text];
[item setSeconds:[NSNumber numberWithInt:secs]];
An a side note, are you using garbage collection, or do you manually release the Item objects that you create when adding items to the array? If you are doing it manually, it should look like this:
Item *item1 = [[Item alloc] initWithName:#"runnerA"
seconds:[NSNumber numberWithInt:780]];
[appDelegate.arrItems addObject:item1];
[item1 release];
This follows the rule of thumb: if you alloc, copy or retain anything, you must also release it. Note that this works because the array will retain the item when it is added.
Are you using NSArray or NSMutableArray?
Assuming you are using NSMutableArray, how did you initialize and populate the array in the first place?
For example, it's not enough to use -initWithCapacity: or +arrayWithCapacity: which only sets aside space. You have to use -addObject: for the first round of population, before you can use -replaceObjectAtIndex:withObject::
Note that NSArray objects are not like C arrays. That is, even though you specify a size when you create an array, the specified size is regarded as a “hint”; the actual size of the array is still 0. This means that you cannot insert an object at an index greater than the current count of an array. For example, if an array contains two objects, its size is 2, so you can add objects at indices 0, 1, or 2. Index 3 is illegal and out of bounds; if you try to add an object at index 3 (when the size of the array is 2), NSMutableArray raises an exception.