NSMutableDictionary input into an array is somehow missing info on the way out - objective-c

I have a mutable array (downloadQueue) containing custom objects (AssetNode) and each object has an NSMutableDictionary as a property.
#interface AssetNode : NSObject {
NSMutableDictionary* allData;
}
#property (nonatomic, retain) NSMutableDictionary* allData;
When I remove an AssetNode from the downloadQueue, the .allData property for the remaining AssetNode objects somehow gets modified and ends up missing some value/key pairs.
-(void)removeAssetNodeFromQueue:(AssetNode*)aNode{
NSMutableArray *temp = [NSMutableArray array];
for (AssetNode* node in downloadQueue)
{
if ([aNode.nodeId isEqualToString:node.nodeId])
{
[temp addObject:node];
}
}
[downloadQueue removeObjectsInArray:temp];
}
Any idea why this happens or how to maintain the integrity of the dictionary info?

Add an NSLog or breakpoint at every point in your code where allData is modified. Log the entire dictionary. I would expect that you have a bug in your code, rather than the NSMutableDictionary accidentally losing your data.

Yeah, retain the dictionary of the object being removed
if ([aNode.nodeId isEqualToString:node.nodeId])
{
[temp addObject:node];
[temp.allData retain];
}
But I suggest storing the allData into another variable, otherwise you'll be leaking memory.
Check out apples Automatic Reference Counting (ARC), this doc is nice:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#//apple_ref/doc/uid/20000994-BAJHFBGH
Comment on my answer if you'd like for me to quickly explain retain count to you.
Cheers

Related

Mutual changes in two NSMutableArray objects

Assume, your .h file looks like this:
#property (retain, nonatomic) NSMutableArray *songs;
- (NSMutableArray *)popularSongs;
- (void)make20PercentDiscountToPopularSongs
Your .m file looks like this:
- (NSMutableArray *)popularSongs
{
NSMutableArray *popularSongs = [NSMutableArray array];
for (Song *song in self.songs) {
if (song.isPopular) {
[popularSongs addObject:song];
}
}
return popularSongs;
}
- (void)make20PercentDiscountToPopularSongs
{
for (Song *song in self.popularSongs) {
song.price = song.price * 0.8;
}
}
The code above adds a 20% discount to popular songs. I recognise that there is a more simplistic way of doing this. You could have the "make20PercentDiscountToPopularSongs" and "popularSongs" function in a single function but let's assume that the code is written the way it is written above.
In the example above, will the line:
song.price = song.price * 0.8;
Actually, make any changes to the object:
#property (retain, nonatomic) NSMutableArray *songs;
Or not? Because, it seems as if the line will change the newly created popularSongs NSMutableArray rather than the songs NSMutableArray, am I right? This problem is bothering me for a lot of time now. I would like to make changes just to the original songs array. To me popularSongs works as if you are storing the pointers to the songs in the songs array.
It changes neither array. It changes some of the objects in the songs array. And as the same objects are in the popularSongs array returned then they are 'changed' too.
This:
[popularSongs addObject:song];
Adds the pointer to the current song into the array. It doesn't create a new copy of the song.
You can create a quick mutable array like this:
NSMutableArray *popularSongs = self.songs.mutableCopy;
Rather than iterating through all the songs to determine if they are popular, you could use a filter predicate at the time you create the array.
NSMutableArray *popularSongs = [[self.songs filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:#"isPopular = %#", #YES]] mutableCopy];
With this one-liner you can avoid your methods altogether ;-)- Not sure what you want, but if you want to alter the original songs, leave out the mutableCopy part.

Remove pointer to object from NSMutableArray while keeping object?

I am trying to remove a pointer in an NSMutableArray that points to an object in another array without deleting the object itself. E.g.:
// In ViewController.m – Code abridged and somewhat simplified
#interface ViewController ()
#property (nonatomic, strong) NSMutableArray *objectPool;
#property (nonatomic, strong) NSMutableArray *objectsOwnedByFriend;
#property (nonatomic, strong) NSMutableArray *objectsOwnedByMe;
- (void)transferPointerToObjectFromFriendToMe;
- (void)copyPointerToObjectFromFriendToMe;
#end
#implementation ViewController
#synthesize objectPool = _objectPool;
#synthesize objectsOwnedByFriend = _objectsOwnedByFriend;
#synthesize objectsOwnedByMe = _objectsOwnedByMe;
- (void)setObjectPool:(NSMutableArray *)objectPool
{
_objectPool = objectPool;
}
- (NSMutableArray *)objectPool
{
if (!_objectPool) _objectPool = [[NSMutableArray alloc] initWithArray:self.objects]; // self.objects is a mutable array containing multiple NSObjects
return _objectPool;
}
- (void)setObjectsOwnedByFriend:(NSMutableArray *)objectsOwnedByFriend
{
_objectsOwnedByFriend = objectsOwnedByFriend;
}
- (NSMutableArray *)objectsOwnedByFriend
{
if (!_objectsOwnedByFriend)
{
_objectsOwnedByFriend = [[NSMutableArray alloc] init];
[_objectsOwnedByFriend addObjectsFromArray:self.objectPool];
}
return _objectsOwnedByFriend;
}
- (void)setObjectsOwnedByMe:(NSMutableArray *)objectsOwnedByMe
{
_objectsOwnedByMe = objectsOwnedByMe;
}
- (NSMutableArray *)objectsOwnedByMe
{
if (!_objectsOwnedByMe) _objectsOwnedByMe = [[NSMutableArray alloc] init];
return _objectsOwnedByMe;
}
- (void)transferPointerToObjectFromFriendToMe
{
[self.objectsOwnedByMe addObject:[self.objectsOwnedByFriend lastObject]];
[self.objectsOwnedByFriend removeLastObject];
}
- (void)copyPointerToObjectFromFriendToMe
{
[self.objectsOwnedByMe addObject:[self.objectsOwnedByFriend lastObject]];
}
#end
In the above code, when I use transferPointerToObjectFromFriendToMe, removing the last object removes both the pointer to it in self.objectsOwnedByFriend (as I want) and also the object itself in self.objectPool (which I don't want to happen).
What I would like is an array (self.objectPool) that contains all of the actual objects and then two mutable arrays (self.objectsOwnedByFriend and self.objectsOwnedByMe) that contains pointers to objects in self.objectPool and the ability to add and remove more pointers referencing objects in self.objectPool to self.objectsOwnedByFriend and self.objectsOwnedByMe.
Also, when I use either transferPointerToObjectFromFriendToMe or copyPointerToObjectFromFriendToMe, the object doesn't seem to be added properly, as a subsequent check via self.objectsOwnedByMe.count results in 0 instead of 1.SOLUTION = My lazy instantiation for self.objectsOwnedByMe was missing in my original code :SI was able to check whether self.objectsOwnedByMe was properly created via:
NSLog(#"self.objectsOwnedByMe = %#", self.objectsOwnedByMe);
** My first StackOverflow question! ** I hope I was clear...couldn't find a a similar question so apologies if I missed an old thread. Let me know if you need more info to diagnose. (I am trying to learn Obj-C.)
Typo :P Sorry peeps. In my actual code in Xcode I had:
- (void)setObjectPool:(NSMutableArray *)objectPool
{
_objectPool = objectPool;
}
- (NSMutableArray *)objectPool
{
if (!_objectPool) _objectPool = [[NSMutableArray alloc] initWithArray:self.objects];
return _objectsOwnedByFriend;
}
I think my mistake is super obvious (and if not, the mistake was that my getter for objectPool was returning _objectsOwnedByFriend...copy/paste error that I somehow missed).
Everything works now!
This is very peculiar and confusing code. I suspect the problem is that something is calling one of the setters, -setObjectPool: or -setObjectsOwnedByFriend:, with the array of the other object. Those setters simply make the ivar refer to the object that was passed in. Because of that, they are very prone to lead to objects being shared.
Typically, a property like that would be declared and implemented with copy semantics.
It looks like self.objectsOwnedByMe is never initialized and you are therefore always working with nil instead of an actual NSMutableArray.
Somewhere (perhaps in a custom getter for objectsOwnedByMe as below?) you need to create an array before you start using it:
- (NSMutableArray *)objectsOwnedByMe {
if (_objectsOwnedByMe == nil) {
_objectsOwnedByMe = [[NSMutableArray alloc] init];
}
return _objectsOwnedByMe;
}
This would explain both problems: Since it is nil it never retains the objects and therefore they go away when removed from the other array, and also why they are never added to the _objectsOwnedByMe array.

copy objects from one NSMutableArray to another NSMutableArray

I am trying to understand copying objects from one NSMutableArray to another. Consider the following 2 scenarios:
1 - copying original to clone where changes in the clone will affect the original.
2 - copying original to clone where the changes in the close will NOT affect the original.
First, I am trying to produce scenario #1 first with the following code. From what I understand, when copying array not using 'mutablecopy', the clone array will just hold the pointer to the same string objects in the original. So if I were to change the first element of the clone to a different object, the first element of the original would change too right? ... but that's not the result I am getting. Why?
Matter of fact, when I use mutablecopy
[self.cloneArray addObject:[[self.originalArray objectAtIndex:i] mutableCopy]];
I get the same result. I am confused.
ArrayClass.h
#interface ArrayClass : NSObject {
NSMutableArray *_originalArray;
NSMutableArray *_cloneArray;
}
#property (nonatomic, retain) NSMutableArray *originalArray;
#property (nonatomic, retain) NSMutableArray *cloneArray;
ArrayClass.m
#synthesize originalArray = _originalArray;
#synthesize cloneArray = _cloneArray;
_originalArray = [[NSMutableArray alloc] initWithObjects: #"one", #"two", #"three", #"four", #"five", nil];
_cloneArray = [[NSMutableArray alloc] initWithCapacity:[self.originalArray count]];
for (int i=0; i<5; i++) {
[self.cloneArray addObject:[self.originalArray objectAtIndex:i]];
}
// make change to the first element of the clone array
[self.cloneArray replaceObjectAtIndex:0 withObject:#"blah"];
for (int n=0; n<5; n++) {
NSLog(#"Original:%# --- Clone:%#", [self.originalArray objectAtIndex:n], [self.cloneArray objectAtIndex:n]);
}
...
2011-03-27 03:23:16.637 StringTest[1751:207] Original:one --- Clone:blah
2011-03-27 03:23:16.638 StringTest[1751:207] Original:two --- Clone:two
2011-03-27 03:23:16.639 StringTest[1751:207] Original:three --- Clone:three
2011-03-27 03:23:16.642 StringTest[1751:207] Original:four --- Clone:four
2011-03-27 03:23:16.643 StringTest[1751:207] Original:five --- Clone:five
You are thinking about this way too hard.
In Objective-C, you have references to objects. An NSString *foo; simply defines a variable foo that refers to an NSString. If you say NSString *bar = foo;, then bar will have a reference to whatever object foo was referring to. No more, no less.
An NSArray is just a collection of object references. So, if you say:
NSArray *b = [NSArray arrayWithArray: a];
You are creating an array b that contains all of the same references to the exact same set of objects as a. If you modify an object referred to by a, that'll be the exact same object in b and the modification will be reflected.
When you copy an object, you are creating a new object that has the identical internal state as the original. I.e. when you say NSMutableString *foo = [barString mutableCopy];, then foo is a reference to a new string; a different one than barString.
So... when creating a new array, the question is do you want the array to contain the exact same contents as the original array or do you want it to contain a new set of objects that you can modify?
You have a misunderstanding of what's going on. The replaceObjectAtIndex:withObject: call isn't modifying objects in the array, it's modifying the array itself. After this line:
[self.cloneArray replaceObjectAtIndex:0 withObject:#"blah"];
you've replaced the object in your clone array, but you haven't changed the original array at all. If you actually modified the NSString object you put in the arrays, you might be able to get the behaviour you were expecting. You won't be able to do it with the objects you've put into the original array in your example, though, since they're immutable string objects. If you stuck mutable strings in there, used the same loop to 'clone' your array, and then did something along the lines of:
[[self.cloneArray objectAtIndex:0] appendString:#"some junk to append"];
you would actually modify the string object at index 0. Since both arrays still contain that same object, you'd get the 'modify original array by changing the objects in the clone array' behaviour.

NSMutableDictionary error

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.

Array of pointers causes leaks

-(void)setUserFilters{
//init the user filters array
userFilters = [[NSMutableArray alloc] init];
SearchCriteria *tmpSc= [[SearchCriteria alloc] init];
for(int i=0;i<[searchFilters count];i++)
{
tmpSc=[self.searchFilters objectAtIndex:i];
if(tmpSc.enabled==TRUE)
[userFilters addObject:tmpSc];
}
}
searchFilters is a list of filters that can be setted to true or false and I use userFilters to populate a table view with the filters that are only setted to TRUE
But the line SearchCriteria *tmpSc= [[SearchCriteria alloc] init]; causes leaks, and I don't know how to solve because if I release at the end of the function I loose my pointers and it crashes
Any ideas?
twolfe18 has made the code >much slower if searchFilters can be large. -objectAtIndex: is not a fast operation on large arrays, so you shouldn't do it more than you have to. (While true that FE is faster than objectAtIndex:, this overstated the issue and so I've striken it; see my other comments on the advantages of Fast Enumeration.)
There are a number of problems in your code:
Never create a method that begins "set" but is not an accessor. This can lead to very surprising bugs because of how Objective-C provides Key-Value Compliance. Names matter. A property named userFilters should have a getter called -userFilters and a setter called -setUserFilters:. The setter should take the same type that the getter returns. So this method is better called -updateUserFilters to avoid this issue (and to more correctly indicate what it does).
Always use accessors. They will save you all kinds of memory management problems. Your current code will leak the entire array if -setUserFilters is called twice.
Both comments are correct that you don't need to allocate a temporary here. In fact, your best solution is to use Fast Enumeration, which is both very fast and very memory efficient (and the easiest to code).
Pulling it all together, here's what you want to be doing (at least one way to do it, there are many other good solutions, but this one is very simple to understand):
#interface MyObject ()
#property (nonatomic, readwrite, retain) NSMutableArray *userFilters;
#property (nonatomic, readwrite, retain) NSMutableArray *searchFilters;
#end
#implementation MyObject
#synthesize userFilters;
#synthesize searchFilters;
- (void)dealloc
{
[searchFilters release];
serachFilters = nil;
[userFilters release];
userFilters = nil;
[super dealloc];
}
- (void)updateUserFilters
{
//init the user filters array
// The accessor will retain for us and will release the old value if
// we're called a second time
self.userFilters = [NSMutableArray array];
// This is Fast Enumeration
for (SearchCriteria *sc in self.searchFilters)
{
if(sc.enabled)
{
[self.userFilters addObject:sc];
}
}
}
It seems that your initially creating a SearchCriteria object and before you use it or release it your reassigning the variable to another object from self.searchFilters. So you don't need to create the initial object and why it's leaking and not being released.
Try:
SearchCriteria *tmpSc = nil;
Hope that helps.
first of all, the worst n00b code you can write involves if(condition==true) do_something(), just write if(condition) do_something().
second, there is no reason to have tempSc at all (never mind alloc memory for it), you can just do the following:
-(void)setUserFilters{
//init the user filters array
userFilters = [[NSMutableArray alloc] init];
for(int i=0;i<[searchFilters count];i++)
{
if([self.searchFilters objectAtIndex:i].enabled)
[userFilters addObject:[self.searchFilters objectAtIndex:i]];
}
}