Here is my code:
I want to be able to create a global NSMutableArray that can store Budget* objects that can then be written to a .pList file... I'm only learning what pLists are, and I am a bit hazy about how to implement them...
Where am I going wrong here?
- (IBAction)btnCreateBudget:(id)sender
{
Budget *budget = [[Budget alloc] init];
budget.name = self.txtFldBudgetName.text;
budget.amount = [self.txtFldBudgetAmount.text intValue];
// Write the data to the pList
NSMutableArray *anArray = [[NSMutableArray alloc] init]; // I want this to be a global variable for the entire app. Where do I put this?
[anArray addObject:budget];
[anArray writeToFile:[self dataFilePath] atomically:YES];
/* As you can see, below is where I test the code. Unfortunately,
every time I run this, I get only 1 element in the array. I'm assuming
that this is because everytime the button is pressed, I create a brand new
NSMutableArray *anArray. I want that to be global for the entire app. */
int i = 0;
for (Budget * b in anArray)
{
i++;
}
NSLog(#"There are %d items in anArray",i);
}
-(NSString *) dataFilePath
{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [path objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"BudgetData.plist"];
}
edit: I'd like to add that I am creating the anArray array so that it can be accessible by other views. I understand that this can be done with NSNotification? or Should I do this the appDelegate classes? The end goal is to have the anArray object populate a UITableView that is in a separate View.
Just put the declaration outside the method instead of inside it.
NSMutableArray *anArray = nil;
- (IBAction)btnCreateBudget:(id)sender
{
...
if ( anArray == nil )
anArray = [[NSMutableArray alloc] init];
...
}
If it's only used inside the one file, make it "static" instead to prevent name collisions with other files:
static NSMutableArray *anArray = nil;
If it's only used inside the one method, make it "static" and put it inside that method:
- (IBAction)btnCreateBudget:(id)sender
{
static NSMutableArray *anArray = nil;
...
if ( anArray == nil )
anArray = [[NSMutableArray alloc] init];
...
}
Note that people usually use some kind of naming convention for global variables, like "gArray", to easily differentiate them from local variables, instance variables, or method parameters.
Global variable is not necessary in this case. You can do something like this:
Read old data to mutable array (initWithContentsOfFile:).
Add new record to the array.
Save the array to same file.
But the second problem in your code is that if your Budget class is not a property list type (NSString, NSData, NSArray, or NSDictionary objects) writeToFile: will not save it sucessfully.
You need to make sure that your Budget class invokes NSCoder and then the NSCoder initWithCoder: and NSCoder decodeWithCoder: methods. Otherwise, writeToFile: will not work for you NSObject class.
But I digress. The answer to the original question should be the following.
In your .h file you need to do the following.
#interface WhateverClassName : UIViewController
{
NSMutableArray *anArray;
}
#property(nonatomic, retain) NSMutableArray *anArray;
#end
Then, you need to make sure you #synthesize the NSMutableArray so that you don't get any freaky warnings. This is done just after the #implementation line in your .m file.
Then, within the function that you want it to be allocated into memory, simply do the following.
anArray = [[NSMutableArray alloc] initWithObjects:nil];
This is now a global variable. It is global in the sense that it can be used from any function and is not limited to use in one function.
If you would like to have data accessible to the entire application or context ("global"), you can use a singleton. However, do this with care and make sure it is actually necessary and appropriate. I would suggest doing plenty of reading up on it prior to any implementation of a singleton. Carter Allen has a good basic implementation here.
According to "The end goal is to have the anArray object populate a UITableView that is in a separate View" you wouldn't need to write anything to a file, database or singleton. Just set the object. Like stated by Sebastien Peek.
If you wish for offline data storage, look into sqlite, json, plist , etc
Related
So, when I modify things inside of an NSMutableArray I don't get the result I expect. I think the best way to frame this question is with an example. The following code prints "george" (as expected):
NSMutableArray *originalArray = [[NSMutableArray alloc] initWithObjects:#"sally",#"george", nil];
NSMutableArray *secondArray = [[NSMutableArray alloc] init];
[secondArray addObject:originalArray[1]];
secondArray[0] = #"priscilla";
NSLog(#"%#",originalArray[1]);
But this code prints "priscilla":
TestClass *test1 = [[TestClass alloc] init];
test1.clientName = #"sally";
TestClass *test2 = [[TestClass alloc] init];
test2.clientName = #"george";
NSMutableArray *originalArray = [[NSMutableArray alloc] initWithObjects:test1,test2, nil];
NSMutableArray *secondArray = [[NSMutableArray alloc] init];
[secondArray addObject:originalArray[1]];
TestClass *objectTakenFromSecondArray = secondArray[0];
objectTakenFromSecondArray.clientName = #"priscilla";
NSLog(#"%#", ((TestClass *)originalArray[1]).clientName);
I thought that addObject: always copied the object before adding it to the array receiving the addObject: message. Is this not the case?
Thanks!
p.s. here is the interface and implementation for TestClass in case it is pertinent:
#interface TestClass : NSObject
#property (strong,nonatomic) NSString *clientName;
#end
#implementation TestClass
#synthesize clientName = _clientName
#end
I thought that addObject: always copied the object before adding it to the array receiving the addObject: message. Is this not the case?
addObject: does not copy the object. NSArray does not require that its contents even be copyable (not everything is). That probably explains the confusion. If you want to copy it, you need to do so yourself.
You pretty much answered your own question. When you create an NSMutableArray and add an object to it, you are just creating a pointer to that object, wherever it is stored. If you add the same object to another NSMutableArray, that too contains a pointer to the same thing. You might not need the analogy, but for anyone else confused - the NSMutableArray is like a postman with an address to post to, and the object is the house at that address. Two postmen (or two arrays) can have an address for the same house, but there is only one house still. (That is, unless someone explicitly 'copies' the house).
So in your second to last line of code, where you change that .clientName property, you are changing the property of the original *test2 object.
Worth noting in this case, that if you remove that second array, you don't remove the objects it contains necessarily. So in your case, removing that second NSMutableArray from memory does not mean that all of its objects also disappear from memory - unless everything else that points to those objects also is removed. The array does not contain pointers to unique copy of those objects - it just points to the originals.
I am trying to develop a function to add Dictionaries into Arrays.
This function looks this way but doesn't work:
in the implementation of the CBLModel I have the property emailList. The property I want to be an NSArray of NSDictionary's.
-(void) addEmail: (NSDictionary*) c{
NSMutableArray* mutArrayEmail= [NSMutableArray arrayWithObject: self.emailList];
[mutArrayEmail addObject:c];
self.emailList = [NSArray arrayWithObject:mutArrayEmail];
}
And the implementation would be something like:
[persInf addEmail: email1];
[persInf addEmail: email2];
[persInf addEmail: email3];
Any help?
EDIT
Thanks to everyone for your reply.
I have inserted in the main code the following (in order to not test everything easier):
NSDictionary *inventory = #{
#"Mercedes-Benz SLK250" : [NSNumber numberWithInt:13],
#"Mercedes-Benz E350" : [NSNumber numberWithInt:22],
#"BMW M3 Coupe" : [NSNumber numberWithInt:19],
#"BMW X6" : [NSNumber numberWithInt:16],
};
NSMutableArray *nsmut= [[NSMutableArray alloc] init];
[nsmut addObject: inventory];
[nsmut addObject: inventory];
[nsmut addObject: inventory];
NSArray *nsarray= [NSArray arrayWithArray:nsmut];
[persInf setEmailList:nsarray];
After this I get an error. I post this code sample as may be easier to see where the error can be
You only want to create the array if it doesn't already exist:
- (void)addEmail:(NSDictionary *) c{
if (!self.emailList) {
self.emailList = [[NSMutableArray alloc] init];
}
[self.emailList addObject:c];
}
This is lazy initialization as the array is created on first use. This is fine, as long as you realise that the array can be nil at times in the object's lifetime (which is less of a problem in Objective-C than in other languages).
A more conventional approach is to override an init method and allocate the array there.
Declare your NSMutableArray outside of your addEmail: method. You can use addObject: as-is.
Since emailList is a property you should override the getter to lazily instantiate it when its needed, like so.
- (NSMutableArray *)emailList {
if(!_emailList) {
_emailList = [NSMutableArray array];
}
return _emailList
}
To be thorough you could write that code using the ternary and make it a bit shorter:
- (NSMutableArray *)emailList {
return _emailList = _emailList ?: [NSMutableArray array];
}
Finally once you've overrode the getter your addEmail method becomes:
- (void)addEmail: (NSDictionary*)c {
[self.emailList addObject:c];
}
If you have to store emailList as an NSArray, I guess you need to set it with
self.emailList = [NSArray arrayWithArray:mutArrayEmail];
I'm far from an expert, though, so there may be problems with this approach. I'd welcome comments to that effect!
You can declare additional property of NSMutableArray (it can be in your class extension):
#property(nonatomic, copy)NSMutableArray *tempArray;
Initialize it in the init
_tempArray = [[NSMutableArray alloc] init];
And change your setter method to:
-(void) addEmail: (NSDictionary*) c{
[self.tempArray addObject:c];
}
And change your getter to:
- (NSArray *)emailList {
return [NSArray arrayWithArray:[self.tempArray copy]];
}
I see you are new to SO, so a little (friendly!) advice: you need to provide more detail in your questions of exactly what you've tried and what has gone wrong; e.g. "After this I get an error." does not help others help you.
Now reading your question and comments two things pop out: assertions about CBLModel, and how to add items to an array.
A. First, CBLModel. You've stated a few times you need an NSArray as:
I can't declare it as Mutable array or it won't work in the CBLModel
What do you mean by this? If you are saying that the methods in CBLModel take NSArray type arguments then you can pass NSMutableArray's as an NSMutableArray is an NSArray (i.e. it is a subclass) - this is one of the central pillars of inheritance-based languages like Objective-C.
If instead you mean that CBLModel does not function correctly if you pass it an NSMutableArray, say because you yourself mutate the array after passing it and that effects CBLModel in an invalid way[*], then you can simply create an immutable copy ([NSArray arrayWithArray:someMutableArray]) or just another mutable copy ([someMutableArray copy]) which you pass only to CBLModel.
B. Adding items to an array. Let's look at your code, line by line, so you can see what it is doing:
NSMutableArray* mutArrayEmail= [NSMutableArray arrayWithObject: self.emailList];
This creates a new NSMutableArray initialised with a single element obtained from self.emailList, and stores a reference to it in mutArrayEmail. E.g. if before execution self.emailList references an array [1, 2, 3], then after execution mutArrayEmail references a (mutable) array [ [1, 2, 3] ] - a single element array whose element is itself an array.
[mutArrayEmail addObject:c];
You add the object referenced by c, so using the same example you now have [ [1, 2, 3], c ] - an array with two elements.
self.emailList = [NSArray arrayWithObject:mutArrayEmail];
And finally you create a new array with the single object referenced by mutArrayEmail. Keeping the same example you now have [ [ [1, 2, 3], c ] ]. That is probably not what you wanted, or what CBLModel accepts.
The class method arrayWithArray: creates an array with the same elements as another array. If you used that method the above example would produce [1, 2, 3, c], which is probably what you want.
HTH
[*] Any classes which accept objects of types with mutable subtypes [arrays, strings, etc.] would normally be written such that a mutation after the object is passed does not effect the behaviour in undesirable ways, i.e. the classes would copy objects if needed. This it is recommended that properties of such type are defined with the copy attribute.
It's a bad idea, generally, to expose a mutable array to the outside world if it's part of an object's state. It means the object's state can be amended without the object knowing about it.
Here's how I would do this:
Have a readonly property that lets people get the array.
Have a method to add objects to the array.
#interface MyClass : NSObject
#property (nonatomic, readonly, strong) NSArray* emailList;
-(void) addEmail: (NSDictionary*) newEmail;
#end
In the implementation, have an mutable instance variable to back the property, initialise it in your designated initialiser. Implement your add method.
// All code assumed to be using ARC
#implementation MyClass
{
NSMutableArray* _emailList;
}
#synthesize emailList = _emailList;
-(id) init
{
self = [super init];
if (self != nil)
{
_emailList = [[NSMutableArray alloc] init];
}
return self;
}
-(void) addEmail: (NSDictionary*) newEmail
{
[_emailList addObject: newEmail];
}
#end
Although, the actual object returned by the property is an NSMutableArray the rule is to program to the interface, which means that when an interface says it returns an immutable object, even if you know it's really a mutable subclass, you are not allowed to send it mutating methods.
If, however, you are paranoid, implement the getter for the property like this:
-(NSArray*) emailList
{
return [_emailList copy];
}
I have a method that creates a dictionary from NSJSONSerialization class. I then enumerate the json, and create objects to store state for each instance.
- (void)fetchedData:(NSData *)responseData {
NSError* error;
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSArray *moviesJson = [json objectForKey:#"movies"];
for(NSDictionary *element in moviesJson)
{
RABMovie *myMovie = [[RABMovie alloc] initWithJson:element];
// RABMovie *myMovie = [RABMovie new];
// myMovie.json = element;
[_myMovieNames addObject: myMovie];
myMovie = nil;
}
[self.movieTableView reloadData];
}
Problem: I want to create my object by passing in element in the allocator, however when I do this, my UITTableView rows all contain the same movie data. It is always the last item, leading me to believe I am working with only one memory address, and the last update affects the whole collection.
If I uncomment the code to use the json as a property instead of a alloc param, I no longer have the issue and all works well. I've also tried creating a completely new NSDictionary via a deep copy of element to no avail.
Does someone know why this is happening? BTW, I am using ARC. -Thanks for the time.
Edit: Added more code. I've included a property movieName to illustrate how I use the ivar _json.
#implementation RABMovie
NSDictionary *_json;
- (id) initWithJson: (NSDictionary*) jsonAsDictionary
{
if (self = [super init])
{
_json = jsonAsDictionary;
}
return self;
}
- (NSString*) movieName
{
return [_json objectForKey:#"title"];
}
I think you meant to declare _json as an instance variable. Instead it's a globally visible (at least within that class) variable - not 100% sure on the scoping rules, but regardless, it's not an instance variable - it's a single variable shared by all instances! Try this instead:
#implementation RABMovie {
NSDictionary *_json;
}
/* ...rest of class */
#end
Putting it inside the curly braces after the #implementation directive makes it an instance variable. Hope this helps!
EDIT: Do you have a property called json on RABMovie already? Then you can skip the instance declaration altogether and the compiler will generate the an instance variable for you. That's probably happening already actually, which is why it works when you go through the property - it's accessing the ivar rather than the "global".
I've been reading about NSArrays and NSDictionaires and I think I need the later. I'm trying to populate an object from a small database table. So I can access the string values via a record id. I have to do this several times so putting it into an object makes sense.
I have the basics...
- (void)viewDidLoad {
// WORKING START
NSMutableDictionary *dictCategories = [[NSMutableDictionary alloc] init];
[dictCategories setValue:#"Utility" forKey:#"3"];
[dictCategories setValue:#"Cash" forKey:#"5"];
NSString *result;
result = [dictCategories objectForKey:#"3"];
NSLog(#"Result=%#", result);
// WORKING END
// Can't get this bit right, current error Request for member
// 'getCategories' in something not a structure or union
NSMutableDictionary *dictCategories2 = self.getCategories;
NSLog(#"Result2=%#", [dictCategories2 objectForKey:#"5"]);
[super viewDidLoad];
}
-(NSMutableDictionary*)getCategories {
NSMutableDictionary *dictCategories = [[NSMutableDictionary alloc] init];
[dictCategories setValue:#"Utility" forKey:#"3"];
[dictCategories setValue:#"Cash" forKey:#"5"];
return dictCategories;
}
you are calling the method wrong,try [self getCategories]
You're not being clear on what isn't working, but a few things that are obviously wrong (JonLOo might be spot on though) ...
Firstly. You're using the wrong methods, or at least there's a better one -- setValue:forKey: should/could be setObject:forKey: instead. This might be one of the reasons for your issue.
Secondly. You're over-allocating and not releasing properly. dictCategories2 in your viewDidLoad will vanish into the void and bring with it the allocated memory for dictCategories defined in the getCategories method. An easy standard fix for this is to change
NSMutableDictionary *dictCategories = [[NSMutableDictionary alloc] init];
in getCategories into
NSMutableDictionary *dictCategories = [NSMutableDictionary dictionary];
It will be autoreleased using the latter method by the system.
Thirdly. You want to read up on #property. Instead of getFoo, setBar, the Ob-C standard is to use #properties to (pre)define setters and getter methods. You can then override these to populate default data into your methods when appropriate. You also (probably) want to store the dictionary in your interface as an instance variable, rather than letting it be deallocated all the time. Example of a #property implementation that does this:
#interface foo {
NSMutableDictionary *ingredients;
}
#property (nonatomic, retain) NSMutableDictionary *ingredients;
#end
// ....
#implementation foo
#synthesize ingredients;
// ...
// the #synthesize command above will create getter and setter methods for us but
// we can override them, which we need to do here
- (NSMutableDictionary *)ingredients
{
if (ingredients != nil) {
// we've already got an ingredients variable so we just return it
return ingredients;
}
// we need to create ingredients
ingredients = [[NSMutableDictionary alloc] init];
[ingredients setObject:#"foo" forKey:#"bar"]
return ingredients;
}
In the viewDidLoad method (or anywhere else where you think ingredients might not have been initialized yet), you would do e.g.
NSMutableDictionary *dict = self.ingredients;
Anywhere else you can opt to use just ingredients without self, but if it's nil, your method will never be called, and you will get nil thrown at you.
This is useful in many cases, and is necessary if we want to ever read or write the ingredients variable from outside of our class. It's outside of what you're asking about, but I brought it up because you're trying to do something similar with self.getCategories.
Hope that helps.
I want to use NSMutableDictionary to cache some data i will use later. My custom object is following:
#interface MyData : NSObject {
NSRange range;
NSMutableArray *values;
}
#property (nonatomic, retain) NSMutableArray *values;
and implement:
- (id)init {
if (self = [super init]) {
values = [[NSMutableArray alloc] init];
}
return self;
}
and when i wanna cache it, i use it like this:
NSMutableDictionary *cache = [[NSMutableDictionary alloc] init];
NSString *key = #"KEY";
MyData *data = [[MyData alloc] init];
// save some data into data
[data.values addObject:"DATA1"];
[data.values addObject:"DATA2"];
//... ...
[cache setObject:data forKey:key];
My questions is the count of cache.values is zero when i retrieve this object later as follow:
[cache objectForKey:#"KEY"];
i can retrieve "data" and the object's memory address is the same as the address when i put it into cache.
what's wrong? i need some kind guys help, any info is helpful. thanks
As Carl Norum pointed out, you're passing C strings to addObject:. addObject:, as its name suggests, requires a pointer to a Cocoa object; a C string is a pointer to characters. You need to pass NSString objects there; for literal strings, this simply requires prefixing them with #: "Fred" is a constant C string, whereas #"Fred" is a constant NSString object.
Is cache an instance variable? It looks like it's not; it appears to be a local variable, which means you're creating a new dictionary object every time. That's why there's nothing you've added previously (to previous dictionaries) in the new one. It also means you're leaking those previous dictionaries, since you're not releasing them (not in the code you showed, anyway).
Make cache an instance variable and only create the dictionary when you don't already have one (i.e., when cache == nil). Creating the dictionary in your init method is one good way. And make sure you manage its lifetime appropriately, so you don't leak and/or crash.
First of all your objects your adding don't look right it should have an # before the string. Like #"DATA1"
Second when you add an object to a dictionary or an array it does not make an actual copy of it. It just creates a pointer to it so if those objects are destroyed or moved somewhere also they are also gone out of your dictionary. A better way to make a cache of your values would be to copy the objects like so:
MyData* cache = [[MyData alloc] init];
for (int i = 0; i < [data.values count]; i ++){{
[cache.values addObject:[NSString stringWithString:[data.values objectAtIndex:i]]];
}
Don't use a dictionary in this situation.