Error when using setObject in NSMutableDictionary - objective-c

I am getting the exception:
-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object'
The offending line is:
[delegate.sharedData.dictFaves setObject:#"test" forKey:#"4"];
Delegate is initialized thus in MyViewController.m:
delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
This is how my delegate is defined in AppDelegate.h:
#import "CommonData.h"
...
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
NSString *tempFave;
CommonData *sharedData;
}
#property (strong, nonatomic) NSString *tempFave;
#property (strong, nonatomic) CommonData *sharedData;
sharedData is initialized in AppDelegate.m thus:
#import "AppDelegate.h"
...
#implementation AppDelegate
#synthesize sharedData;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
sharedData = [[CommonData alloc] init];
return YES;
}
sharedData is defined in CommonData.h:
#interface CommonData : NSObject
{
NSMutableDictionary *dictAffirms;
NSMutableDictionary *dictFaves;
}
#property (nonatomic, copy) NSMutableDictionary *dictAffirms;
#property (nonatomic, copy) NSMutableDictionary *dictFaves;
shared data implementation file CommonData.m:
#import "CommonData.h"
...
#implementation CommonData
#synthesize dictAffirms;
#synthesize dictFaves;
#end
I have declared the members of CommonData to be Mutable. Apparently that is insufficient. What else must I do in order to write to the Dictionaries inside CommonData?

I have seen this error before when trying to write to a dictionary that is filled from a plist. If you use
yourMutableDictionary = [someDataSource objectForKey:#"someKey"];
your dictionary will be immutable, even if it is declared mutable. Use instead
yourMutableDictionary = [someDataSource mutableArrayValueForKey:#"someKey"];
and your problem will go away, assuming this is in fact your problem. It might be something like:
yourMutableDictionary = [[NSDictionary alloc] init];
or
yourMutableDictionary = [NSDictionary new];
and you just are accidentally creating immutable objects, which is pretty much the same problem as above, just different.
It would be nice to see the code used to initialize the NSMutableDictionaries.
Edit: Maybe try something like this, as I'm curious as to what the results would be. Instead of using:
[delegate.sharedData.dictFaves setObject:#"test" forKey:#"4"];
try
NSMutableDictionary* dict = [[NSMutableDictionary alloc] initWithDictionary:delegate.sharedData.dictFaves];
[dict setObject:#"test" forKey:#"4"];
delegate.sharedData.dictFaves = dict;
[dict release];

You've declared the dictFaves to be mutable, but that doesn't mean you actually stored a mutable object in there. Check your initializer. You'll probably have something like the following:
dictFaves = [[NSDictionary alloc] init];
If so, you need to change that to NSMutableDictionary instead.

Your problem is that your property setter for the dictionary is declared as copy. NSMutableDictionary's copy method returns an immutable NSDictionary (in general, copy almost always returns an immutable object). So assuming you're using the standard synthesized setter, any time you set that property, you're assigning the wrong type behind the scenes. It should probably be strong instead.

Related

Class Method Exposure and Property Definition

I have an academic question about Class Method exposure. There is something that I obviously don't understand about this and would like some clarification from those in the know.
Background:
I have a simple example of two classes named ViewController and ClassB. Class B contains an array with a method named returnArray. The ViewController accesses the array's data. I have exposed the returnArray method in the ClassB.h file.
Question:
Why is it that I can access the array's data in ViewController without having to define a property? I thought that the property would create a getter to allow access to the array. My example (only exposing the method) allows me to access the data without the creation of the #property.
Class Method:
ClassB.h
#interface ClassB : UIViewController
+(NSArray *) returnArray;
//#property (nonatomic, strong) NSArray *returnArray;
ClassB.m
#implementation ClassB
+(NSArray *) returnArray
{
NSArray *locationArray = #[#"Place1", #"Place2"];
return locationArray;
}
ViewController.m
- (void)viewDidLoad
{
NSArray *location = [ClassB returnArray];
NSLog (#"The count of the location is %d", [location count]);
NSLog (#"The second item in testArray is %#", location[1]);
}
Instance method: After reviewing answers
ClassB.h
*/
{
#private
NSArray *returnArray;
}
- (void)setReturnArray:(NSArray*)returnArray;
-(NSArray *) returnArray;
*/
#property (nonatomic, strong) NSArray *returnArray;
#end
ClassB.m - no change
ViewController.h - no change
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
//Create instance of ClassB
ClassB *classB = [ClassB new];
//Access the instance of returnArray
NSArray *location = [classB returnArray];
NSLog (#"The count of the location is %d", [location count]);
NSLog (#"The second item in testArray is %#", location[1]);
}
#property is a shorthand notation for creating an instance variable and associated accessor methods (with defined access / modification criteria).
What you have is a class method, which internally creates an array and returns it.
That's why you call [ClassB returnArray]; instead of [instanceOfB array];.
These are completely different things. If you wanted to use a property then you would need to create an instance of ClassB and then access the property. This would work, assuming that the array was created when the instance of ClassB was created.
Wain's answer addresses the difference between #property and Class methods, so it's worth a read. My answer assumes you know the difference between class and instance methods, and focuses on the difference between creating a #property versus creating an instance variable with an associate setter and getter.
The reason is because returnArray is a public method that returns an NSArray object on your ClassB.
A #property is merely a convenient way of creating three things at the same time: an instance variable, a setter, and a getter. It has the added bonus of allowing dot-syntax.
But at the end of the day, dot-syntax aside, all you're doing by declaring a #property is equivalently equal to this:
#interface ClassB : NSObject {
#private
NSArray *returnArray;
}
- (void)setReturnArray:(NSArray*)returnArray;
- (NSArray*)returnArray;
This is the same as this:
#property NSArray *returnArray;
Except of course, the dot syntax.
When you do:
NSArray *myArray = classB.returnArray;
You're not actually directly accessing the array you created when you declared the #property.
What you're doing is calling the getter method that was automatically generated when you declared the #property.

Array loses all values after go trough its own method - Objective C

I have this piece of code below and I'm trying to add Objects(String elements) to an array, problem is that every time I'm out its adding's method, it goes to nil, it doesn't retain the objects.
I know I'm doing wrong, even that I already tried lot of combinations and variations, even with my own constructor _MyArray etc etc, same result... it works, but not further...
Could you help me please?
#interface ArraysModel()
#property (nonatomic, retain) NSMutableArray *MyArray;
#end
#implementation ArraysModel
#synthesize MyArray;
-(void)AddObjectToTheList:(NSString *)object {
if(!MyArray) MyArray = [[NSMutableArray alloc] init];
[MyArray addObject:object];
NSLog(#"%#",self.MyArray);
NSLog(#"Object added %u",[self.MyArray count]);
}
-(NSMutableArray *)ObjectList {
return self.MyArray;
NSLog(#"%#",self.MyArray);
NSLog(#"Object added %u",[self.MyArray count]);
}
#end
The header is like this:
#interface ArraysModel : NSObject
-(void)AddObjectToTheList:(NSString *)object;
And here is my call from my ViewController:
- (IBAction)AddToTheList {
ArraysModel *MyObjectToAdd = [[ArraysModel alloc] init];
[MyObjectToAdd AddObjectToTheList:TextArea.text];
[self.view endEditing:YES];
Well, there's your problem -- you're alloc init'ing a new instance of ArraysModel, and therefore a new array with every call. You need to create a strong reference to your instance, and check for whether it exits, and only init if it doesn't.
In the .h:
#property (strong, nonatomic) ArraysModel *myObjectToAdd;
in the .m:
-(IBAction)AddToTheList {
if (! self.myObjectToAdd) {
self.myObjectToAdd = [[ArraysModel alloc] init];
}
[self.myObjectToAdd AddObjectToTheList:TextArea.text];
[self.view endEditing:YES]
}

sharedApplication value is not visible

Well I have those 2 variables, an NSString and an NSMutable Array which is value I want to be global in all my tabs. So far I had only one (the NSString) and I could get its value normally. No that I tried having one more variable I get (null) value.
here is my code:
AppDelegate.h
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (nonatomic,retain) NSString *myDate;
#property (nonatomic,copy) NSMutableArray *tracks_condition;
#end
and in the HomeView.m when i assign the values
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
parsed_date=[res objectForKey:#"date"];
NSLog(#"Date:%#",parsed_date);
delegate.myDate = parsed_date;
NSString *parsed_track=[res objectForKey:#"Data1"];
[delegate.tracks_condition addObject:parsed_track];
NSLog(#"Track 0 Static:%#",parsed_track);
NSLog(#"Track 0:%#",[delegate.tracks_condition objectAtIndex:0]);
parsed_date is being printed out normally and I can access it in aother tabs.
But I get:
Track 0 Static:0
Track 0: (null)
And i cannot understand why, as I add it as an object in the MutableArray. Of course i get (null) in every other tab.
Can you help me? i prefer by fixing that code and not suggesting something entirely different because I am not very experienced in IOS. Unless it is that easy.
Most likely this is because you are never instantiating a mutable array for your tracks_condition property. Just declaring a property on your AppDelegate class doesn't create the object for you. You still need to alloc/init the object at some point before you can use it. Until then tracks_condition remains null, and any messages sent to it (such as attempting to add an object to the array) will be ignored.
You can fix this by instantiating an object and setting the value of your property before using it. You could do this in your app delegate's application:didFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.tracks_condition = [[NSMutableArray alloc] init];
}

Use of undeclared identifier in Objective C

I'm very new to Objective C and am having trouble with some very basic things.
In AppDelegate.m, I'm getting the following errors:
Use of undeclared identifier 'health'
Use of undeclared identifier 'attack'
Code (respectively):
[Troll setValue:100 forKeyPath:health];
[Troll setValue:10 forKeyPath:attack];
I'm not really sure how to declare the identifiers.
AppDelegate.m
#import "AppDelegate.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSObject *Troll = [[NSNumber alloc]init];
[Troll setValue:100 forKeyPath:health];
[Troll setValue:10 forKeyPath:attack];
return YES;
}
#end
AppDelegate.h
#import `<UIKit/UIKit.h>`
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#end
#interface Troll : NSObject {
NSNumber *health;
NSNumber *attack;
}
#end
Keys are strings, and not something else (like dangling syntactic garbage). Furthermore, '100' and '10' are not objects. Even after this, you don't want to set the properties of the class itself but of its instances. Try
[troll setValue:[NSNumber numberWithInt:100] forKeyPath:#"health"];
[troll setValue:[NSNumber numberWithInt:10] forKeyPath:#"attack"];
instead.
The first thing to say is that you are not instantiating a Troll object, but a NSNumber... why? You would have to do Troll *troll = [[[Troll alloc] init] autorelease];
The way to set and get attributes from classes uses to be by declaring properties on the class. This way the compiler will manage the memory for you (retains and releases). Another way would be accessing directly your ivars.
However, if you want to use the setValue:forKeyPath: you have to use a NSString for the second paremeter, which is the name of the variable.
#interface Troll : NSObject
{
NSNumber *_health;
NSNumber *_attack;
}
#property (nonatomic, retain) NSNumber *health;
#property (nonatomic, retain) NSNumber *attack;
#end
#implementation Troll
#synthesize health = _health;
#synthesize attack = _attack;
- (void)dealloc
{
[_health release];
[_attack release];
[super release];
}
- (void)customMethod
{
//using properties
[self setHealth:[NSNumber numberWithInteger:100];
[self setAttack:[NSNumber numberWithInteger:5];
//accessing ivars directly - remember to release old values
[_health release];
_health = [[NSNumber numberWithInteger:100] retain];
[_attack release];
_attack = [[NSNumber numberWithInteger:5] retain];
}
#end
Good luck!
Although you are defining a class called Troll with a 'health' and 'attack', you aren't instantiating one. You probably want one of the following in your application didFinishLaunchingWithOptions:
Troll *troll = [[Troll alloc]init];
troll.health = [NSNumber numberWithInt:100];
troll.attack = [NSNumber numberWithInt:10];
OR
Troll *troll = [[Troll alloc]init];
[troll setHealth:[NSNumber numberWithInt:100]];
[troll setAttack:[NSNumber numberWithInt:10]];
These two are equivalent.

Pass array from one Objective-C class to another

Im attempting to pass an array that is created in one class into another class. I can access the data but when I run count on it, it just tells me that I have 0 items inside the array.
This is where peopleArray's data is set up, it's in a different class than the code that is provided below.
[self setPeopleArray: mutableFetchResults];
for (NSString *existingItems in peopleArray) {
NSLog(#"Name : %#", [existingItems valueForKey:#"Name"]);
}
[peopleArray retain];
This is how I get the array from another class, but it always prints count = 0
int count = [[dataClass peopleArray] count];
NSLog(#"Number of items : %d", count);
The rest of my code:
data.h
#import <UIKit/UIKit.h>
#import "People.h"
#class rootViewController;
#interface data : UIView <UITextFieldDelegate>{
rootViewController *viewController;
UITextField *firstName;
UITextField *lastName;
UITextField *phone;
UIButton *saveButton;
NSMutableDictionary *savedData;
//Used for Core Data.
NSManagedObjectContext *managedObjectContext;
NSMutableArray *peopleArray;
}
#property (nonatomic, assign) rootViewController *viewController;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain) NSMutableArray *peopleArray;
- (id)initWithFrame:(CGRect)frame viewController:(rootViewController *)aController;
- (void)setUpTextFields;
- (void)saveAndReturn:(id)sender;
- (void)fetchRecords;
#end
data.m(some of it at least)
#implementation data
#synthesize viewController, managedObjectContext, peopleArray;
- (void)fetchRecords {
[self setupContext];
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"People" inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"Name" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
// This is a serious error and should advise the user to restart the application
}
// Save our fetched data to an array
[self setPeopleArray: mutableFetchResults];
for (NSString *existingItems in peopleArray) {
NSLog(#"Name : %#", [existingItems valueForKey:#"Name"]);
}
[peopleArray retain];
[mutableFetchResults release];
[request release];
//NSLog(#"this is an array: %#", eventArray);
}
login.h
#import <UIKit/UIKit.h>
#import "data.h"
#class rootViewController, data;
#interface login : UIView <UITextFieldDelegate>{
rootViewController *viewController;
UIButton *loginButton;
UIButton *newUser;
UITextField *entry;
data *dataClass;
}
#property (nonatomic, assign) rootViewController *viewController;
#property (nonatomic, assign) data *dataClass;
- (id)initWithFrame:(CGRect)frame viewController:(rootViewController *)aController;
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField;
#end
login.m
#import "login.h"
#import "data.h"
#interface login (PrivateMethods)
- (void)setUpFromTheStart;
- (void)loadDataScreen;
-(void)login;
#end
#implementation login
#synthesize viewController, dataClass;
-(void)login{
int count = [[dataClass peopleArray] count];
NSLog(#"Number of items : %d", count);
}
Is it the same object? If so, what you have should work. Check to see how you are getting the dataClass instance -- if you alloc a new one, you don't get the array from the other object.
Edit: From your comments below, it appears that you are having some confusion on the difference between classes and objects. I will try to explain (I'm going to simplify it):
A class is what you write in Xcode. It's the description that lets your application know how to create and access objects at run-time. It is used to figure out how much memory to allocate (based on instance variables) and what messages can be sent, and what code to call when they are. Classes are the blueprints for creating objects at runtime.
An object only exists at run-time. For a single class, many objects of that class can be created. Each is assigned its own memory and they are distinct from each other. If you set a property in one object, other objects don't change. When you send a message to an object, only the one you send it to receives it -- not all objects of the same class.
There are exceptions to this -- for example if you create class properties (with a + instead of a - at the beginning), then they are shared between all objects -- there is only one created in memory, and they all refer to the same one.
Also, since everything declared with a * is a pointer -- you could arrange for all pointer properties to point to the same data. The pointer itself is not shared.
Edit (based on more code): dataClass is nil, [dataClass peopleArray] is therefore nil, and then so is the count message call. You can send messages to nil, and not crash, but you don't get anything useful.
I don't see how the login object is created. When it is, you need to set its dataClass property.
Try running the code in the debugger, setting breakpoints, and looking at variables.
From the code, it looks like you are passing a mutable array.
[self setPeopleArray: mutableFetchResults];
Probably the items of the array are removed somewhere in your calling class / method. Or the array is reset by the class from which you get the mutableFetchResults in the first place.