NSArrayM was mutated while being enumerated - adding objects, not removing - ios

I tried looking over all these problems on StackOverflow and couldn't find anything that helped me. I can't even replicate it myself, i got this from actual users through iTunes Connect. This are the crash logs on Xcode:
This is my full serializedLocations method that is crashing, nothing stripped from it:
- (NSMutableArray *)serializedLocations:(NSArray *)locations withTimestamp:(NSInteger)timestamp{
NSMutableArray *serializedLocations = [NSMutableArray new];
if(locations){
for (CLLocation *location in locations) {
NSInteger locationTimeInterval = floor([location.timestamp timeIntervalSince1970] * 1000);
NSInteger t = locationTimeInterval - timestamp;
NSMutableDictionary *serializedLocation = [NSMutableDictionary new];
serializedLocation[#"x"] = [NSNumber numberWithDouble:location.coordinate.latitude];
serializedLocation[#"y"] = [NSNumber numberWithDouble:location.coordinate.longitude];
serializedLocation[#"a"] = [NSNumber numberWithDouble:location.horizontalAccuracy];
serializedLocation[#"v"] = [NSNumber numberWithDouble:location.speed];
serializedLocation[#"o"] = [NSNumber numberWithDouble:location.course];
serializedLocation[#"t"] = [NSNumber numberWithLong:t];
[serializedLocations addObject:serializedLocation];
}
}
return serializedLocations;
}
I can't seem to find the flaw.
I'm creating a temp new array.
I don't change the array that I'm enumerating.
I'm adding new objects to the temp array.
I'm returning that new temp array
EDIT:
The parent that gets all the other methods going is called like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[ApiClient insertEvent:event withLocations:locations];
});

I'm guessing that locations, although typed here as an NSArray, is in fact an NSMutableArray — and that this is the array that is being mutated while you are enumerating it. If you are going to run this code on a background thread, you need to ensure that locations is not being mutated "behind your back". An easy way to do that would be to make a deep copy of locations at the call site and pass that rather than the mutable array. (Note that it is not sufficient to make the deep copy here in serializedLocations:withTimestamp:, as locations could be mutated during the copy.)

Related

replacing an NSDecimalNumbers with another in NSArray crashes

I have a NSMutabelArray and I want to do some additions inside of it. I do this by calling a functions with then create a subarray with the items where the calculations have to be done on.
- (NSDecimalNumber *)calculate:(NSMutableArray *)arrayToCalculate {
while ([arrayToCalculate containsObject:(#"+")]) {
NSUInteger signeLocation = [arrayToCalculate indexOfObject:(#"+")];
[arrayToCalculate replaceObjectAtIndex:(signeLocation-1)
withObject:([[arrayToCalculate objectAtIndex:(signeLocation-1)]
decimalNumberByAdding:[arrayToCalculate objectAtIndex:(signeLocation+1)]])];
[arrayToCalculate removeObjectsAtIndexes:
[NSIndexSet indexSetWithIndexesInRange:NSMakeRange((signeLocation), 2)]];
}
return [arrayToCalculate lastObject];
}
I initialised the arrayToCalculate by:
NSMutableArray *subArray =
[inputArray subarrayWithRange:(rangeOfCalculationItems)];
Every time I run this code it crashes. I am pretty sure it is bc I used subarray on an NSMutableArray and initialised it as NSMutableArray even when the message gives me back a NSArray, but I don't know how I could fix it or it is even the problem.
I copied your method and tested it like this:
NSArray *items = #[
[[NSDecimalNumber alloc] initWithString:#"1"],
#"+",
[[NSDecimalNumber alloc] initWithString:#"2"]
];
NSLog(#"%g", [self calculate: [items mutableCopy]].floatValue);
The code works and the printed result was 3. Your issue must be somewhere else. Are you sure your array is in fact mutable? Note [items mutableCopy].

Strange dictionary sort ios objective c

I want to do kind of a weird dictionary sort. I have non-unique values and keys and get something like this
NSArray *counts = [#"1",#"2",#"2",#"3",#"6",#"10"];
NSArray *names =[#"Jerry",#"Marge",#"Jerry",#"Marge",#"Jen",#"Mark"];
The output that I want is an descending ordered list by counts with unique names. I don't want lower values of the same person in my outputted arrays. The output should be.
sortedNames=[#"Mark",#"Jen",#"Marge",#"Jerry"]
sortedCounts=[#"10",#"6",#"3",#"2"];
I would really appreciate some help on this.
NSMutableArray *userNameArray = [[NSMutableArray alloc] init];
NSMutableArray *countArray = [[NSMutableArray alloc] init];
for (NSDictionary *dict in bigDick) {
NSString *nameString =[dict objectForKey:#"Name"];
NSString *countString =[dict objectForKey:#"Count"];
NSInteger countInt = [countString integerValue];
NSNumber *countNumber =[NSNumber numberWithInt:countInt];
[userNameArray addObject:nameString];
[countArray addObject:countNumber];
}
NSArray *namesAscending =[[userNameArray reverseObjectEnumerator]allObjects];
NSArray *countsAscending=[[countArray reverseObjectEnumerator]allObjects];
// Put the two arrays into a dictionary as keys and values
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:countsAscending forKeys:namesAscending];
// Sort the first array
NSArray *sortedCountArray = [[dictionary allValues] sortedArrayUsingSelector:#selector(compare:)];
// Sort the second array based on the sorted first array
// NSArray *sortedNameArray= [dictionary objectsForKeys:sortedCountArray notFoundMarker:[NSNull null]];
NSMutableArray *nameArray =[[NSMutableArray alloc] init];
for (int i=1; i<sortedCountArray.count; i++) {
NSString *name = [dictionary allKeysForObject:sortedCountArray[i]];
if (sortedCountArray[i]!=sortedCountArray[i-1]) {
[nameArray addObject:name];
}
}
an old method is to manual sort the array with numbers, by searching on every iteraton for the biggest value, and when you find the max value take the name from the other vector at index of the max number and move it in new vector...
max = counts[0];
counter = 0;
for (int i=0;i<counts.count;i++)
{
temp = counts[i];
if (max<temp)
max = temp;
counter = i;
}
[new_names addObject: [names objectAtIndex:counter]];
[new_numbers addObject: max];
[numbers removeObjectAtIndex: counter];
[names removeObjectAtIndex:counter];
Try something like this. It should work if you do it this way.
Important! do not remove elements in for from array that you count for the for length.
Your problem is in your algorithm design, if you step through it a line at a time in the debugger you should see what it does and where it goes wrong.
We're not here to write you code, but let's see if we can go through one step of an algorithm to help you one your way:
Useful fact: If you lookup a key in a dictionary and that key does not exist the return value will be nil.
From this: you can use a dictionary to keep track of the names you have seen paired with the highest score so far. You obtain a name,score pair, lookup the name in the dictionary - if you get nil its a new name with a new high score. If it's not nil its the currently known high score, so you can compare and update.
That's a rough algorithm, let's try it. Before we start rather than using literal strings for keys everywhere let's define some constants. This has the advantage that we won't mistype the strings, the compiler will spot if we mistype the constant names. These can be defined at the file level or within a method:
const NSString *kName = #"Name";
const NSString *kCount = #"Count";
Now to the code, in a method somewhere, we'll need a dictionary:
NSMutableDictionary *highScores = [NSMutableDictionary new]; // a single dictionary rather than your two arrays
Now start your loop as before:
for (NSDictionary *dict in bigDict) // same loop as your code
{
and extract the two values as before:
NSString *nameString = dict[kName]; // same as your code, but using modern syntax
NSInteger countInt = [dict[kCount] integerValue]; // condense two lines of your code into one
Now we can lookup the name in our dictionary:
NSNumber *currentScore = highScores[nameString]; // get current high score for user, if any
If the name exists as a key this will return the current associated value - the score in this case, if there is no matching key this will return nil. We can test for this in a single if:
if (currentScore == nil // not seen user before, no high score
|| currentScore.integerValue < countInt) // seen user, countInt is greater
{
The above condition will evaluate to true if we either need to add the name or update its score. Adding & updating a key/value pair is the same operation, so we just need the line:
highScores[nameString] = #(countInt); // add or update score for user
and a couple of braces to terminate the if and for:
}
}
Let's see what we have:
NSLog(#"Output: %#", highScores);
This outputs:
Output: {
Jen = 6;
Jerry = 2;
Marge = 3;
Mark = 10;
}
Which is a step in the right direction. (Note: the dictionary is not sorted, NSLog just displays the keys in sorted order.)
Make sure you understand why that works, copy the code and test it. Then try to design the next phase of the algorithm.
If you get stuck you can ask a new question showing the algorithm and code you've developed and someone will probably help. If you do this you should include a link to this question so people can see the history (and know you're not trying to get an app written for you through multiple questions!)
HTH
Try this.
sortedArray = [yourArray sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
After sort your array then remove duplicates using following.
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray: sortedArray];
NSArray *arrayWithoutDuplicates = [orderedSet array];

iOS Cocoa Touch Add Object to NSArray

I have an NSArray that has to be an array for portions of the project so nothing will change it. I need to add an object to the array. The method I used was to convert to an NSMutableArray, add the object, and then convert back to an NSArray. The method:
- (void)addAdj:(NSString *)obj{
NSMutableArray *ary = [self.adj mutableCopy];
[ary addObject:obj];
self.adj=[ary copy];
for(int i = 0; i<[self.adj count]; i++){
NSLog(#"%#, ", [self.adj objectAtIndex:i]);
}
}
The for loop and log statement are included to print the array but it does not print anything at all. I have seen similar questions but people always tell the OP to just use an NSMutable array from the start. I'd like to know why this bit of code does not work as is. In advance Thanks!
May be you have already forgot to initialize self.adj in viewDidload. You try to add below code to viewDidload:
self.adj = [[NSArray alloc]init];
Or you can show code in your viewDidload. I will be more help.

NSMutableArray can't insert a dictionary

can you help me? I´m searching here for a while and testet many things... no solution found!
I have set a NSMutableArray in the .h File:
#interface ViewController : UIViewController
{
NSMutableArray *Transactions;
}
In the .m file in the ViewDidLoad Method I initialized it and load the Array from the UserDefaults:
Transactions = [NSMutableArray array];
or
Transactions = [NSMutableArray new];
Transactions = [defaults objectForKey:#"transactions"];
Later i add a new Dictionary to it:
[insert setObject:confirmPaymentStatus forKey:#"status"];
[insert setObject:confirmPaymentAmount forKey:#"amount"];
[insert setObject:confirmPaymentDiscription forKey:#"description"];
[insert setObject:timestamp forKey:#"time"];
NSLog(#"%#",insert);
[Transactions addObject:insert];
NSLog(#"Transactions Array:\n%#",Transactions);
[defaults setObject:Transactions forKey:#"transactions"];
[defaults synchronize];
The insert Dictonary is full of data and then i got this from the Log:
2014-02-05 20:56:53.691 PPEasyPay Pro Pro[21907:60b] {
amount = "0.91";
description = "Polaris 123123";
status = COMPLETED;
time = "2014-02-05T11:56:51.248-08:00";
}
2014-02-05 20:56:53.692 PPEasyPay Pro Pro[21907:60b] Transactions Array:
(null)
It sounds like [defaults objectForKey:#"transactions"] is returning null, and therefore you're assigning your array to null. From there, you add an object to your non existent array, and since the array doesn't exist, nothing actually happens. Instead, you should create your array, and only add the objects from the other array to it if they exist.
NSArray *newStuff = [defaults objectForKey:#"transactions"];
transactions = [NSMutableArray new];
if (newStuff) {
[transactions addObjectsFromArray:newStuff];
}
Side note, your instances should be camelCase starting with a lowercase letter.
Transactions = [NSMutableArray new];
Transactions = [defaults objectForKey:#"transactions"];
When you do that, you are creating a new NSMutableArray, then that array is being discarded when you assign the result of [defaults objectForKey:#"transactions"] to the same variable.
I'd be willing to guess that the result of [defaults objectForKey:#"transactions"] is nil, which is why your array is nil.
Also, by convention, the Transactions variable should begin with a lowercase letter, or even an underscore followed by a lowercase letter.
The problem is that Transactions itself was nil to start with. You then say:
[Transactions addObject:insert];
But this has no effect: sending addObject: to nil still leaves it as nil.
What you need to do is this: after you fetch Transactions from the defaults, look to see if it is nil. If it is, set it to an empty mutable array instead.
Also, please note that this code is utterly stupid:
Transactions = [NSMutableArray array];
Transactions = [defaults objectForKey:#"transactions"];
You set Transactions to an empty mutable array, but then you immediately throw away that empty mutable array and replace it by whatever comes from the defaults (i.e. nil).
Transactions = [NSMutableArray array];
or
Transactions = [NSMutableArray new];
Transactions = [defaults objectForKey:#"transactions"];
You have several problems here. The first is that what you've written is the object equivalent of this:
int i;
i = 5;
i = 3;
What's i? Well, 3 of course. What does the i = 5 line do? Absolutely nothing. (Except possibly take CPU time, depending on optimization.)
The second problem is that [defaults objectForKey:] is going to return an immutable object.
From Apple's docs for objectForKey::
Special Considerations
The returned object is immutable, even if the value you originally set was mutable.
The reason this works without error is that objectForKey: returns an id; you can assign an id to any object variable without an error. But this is like a C pointer cast, not a type coercion. You haven't made the return value a NSMutableArray, you've just shown the compiler you don't care what it is.
What you want is this:
NSArray *readOnlyTransactions = [defaults objectForKey:#"transactions"];
if (readOnlyTransactions == nil) {
readOnlyTransactions = #[];
}
_transactions = [readOnlyTransactions mutableCopy];
You'll notice I renamed your instance variable to _transactions. This is the convention for instance variables, and you should probably adapt to it.
You can do this more succinctly with the ?: operator, if you're comfortable with using it:
_transactions = [([defaults objectForKey:#"transactions"] ?: #[]) mutableCopy];
What this does:
Read [defaults objectForKey:#"transactions"].
If that returns nil, use an empty list instead.
Make a mutable copy of it.
Assign the thing to _transactions.

Objective-C: Fixing memory management in a method

I'm almost there understanding simple reference counting / memory management in Objective-C, however I'm having a difficult time with the following code. I'm releasing mutableDict (commented in the code below) and it's causing detrimental behavior in my code. If I let the memory leak, it works as expected, but that's clearly not the answer here. ;-) Would any of you more experienced folks be kind enough to point me in the right direction as how I can re-write any of this method to better handle my memory footprint? Mainly with how I'm managing NSMutableDictionary *mutableDict, as that is the big culprit here. I'd like to understand the problem, and not just copy/paste code -- so some comments/feedback is ideal. Thanks all.
- (NSArray *)createArrayWithDictionaries:(NSString *)xmlDocument
withXPath:(NSString *)XPathStr {
NSError *theError = nil;
NSMutableArray *mutableArray = [[[NSMutableArray alloc] init] autorelease];
//NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc] init];
CXMLDocument *theXMLDocument = [[[CXMLDocument alloc] initWithXMLString:xmlDocument options:0 error:&theError] retain];
NSArray *nodes = [theXMLDocument nodesForXPath:XPathStr error:&theError];
int i, j, cnt = [nodes count];
for(i=0; i < cnt; i++) {
CXMLElement *xmlElement = [nodes objectAtIndex:i];
if(nil != xmlElement) {
NSArray *attributes = [NSArray array];
attributes = [xmlElement attributes];
int attrCnt = [attributes count];
NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc] init];
for(j = 0; j < attrCnt; j++) {
if([[[attributes objectAtIndex:j] name] isKindOfClass:[NSString class]])
[mutableDict setValue:[[attributes objectAtIndex:j] stringValue] forKey:[[attributes objectAtIndex:j] name]];
else
continue;
}
if(nil != mutableDict) {
[mutableArray addObject:mutableDict];
}
[mutableDict release]; // This is causing bad things to happen.
}
}
return (NSArray *)mutableArray;
}
Here's an equivalent rewrite of your code:
- (NSArray *)attributeDictionaries:(NSString *)xmlDocument withXPath:(NSString *)XPathStr {
NSError *theError = nil;
NSMutableArray *dictionaries = [NSMutableArray array];
CXMLDocument *theXMLDocument = [[CXMLDocument alloc] initWithXMLString:xmlDocument options:0 error:&theError];
NSArray *nodes = [theXMLDocument nodesForXPath:XPathStr error:&theError];
for (CXMLElement *xmlElement in nodes) {
NSArray *attributes = [xmlElement attributes];
NSMutableDictionary *attributeDictionary = [NSMutableDictionary dictionary];
for (CXMLNode *attribute in attributes) {
[attributeDictionary setObject:[attribute stringValue] forKey:[attribute name]];
}
[dictionaries addObject:attributeDictionary];
}
[theXMLDocument release];
return attributeDictionaries;
}
Notice I only did reference counting on theXMLDocument. That's because the arrays and dictionaries live beyond the scope of this method. The array and dictionary class methods create autoreleased instances of NSArray and NSMutableDictionary objects. If the caller doesn't explicitly retain them, they'll be automatically released on the next go-round of the application's event loop.
I also removed code that was never going to be executed. The CXMLNode name method says it returns a string, so that test will always be true.
If mutableDict is nil, you have bigger problems. It's better that it throws an exception than silently fail, so I did away with that test, too.
I also used the relatively new for enumeration syntax, which does away with your counter variables.
I renamed some variables and the method to be a little bit more Cocoa-ish. Cocoa is different from most languages in that it's generally considered incorrect to use a verb like "create" unless you specifically want to make the caller responsible for releasing whatever object you return.
You didn't do anything with theError. You should either check it and report the error, or else pass in nil if you're not going to check it. There's no sense in making the app build an error object you're not going to use.
I hope this helps get you pointed in the right direction.
Well, releasing mutableDict really shouldn't be causing any problems because the line above it (adding mutableDict to mutableArray) will retain it automatically. While I'm not sure what exactly is going wrong with your code (you didn't specify what "bad things" means), there's a few general things I would suggest:
Don't autorelease mutableArray right away. Let it be a regular alloc/init statement and autorelease it when you return it ("return [mutableArray autorelease];").
theXMLDocument is leaking, be sure to release that before returning. Also, you do not need to retain it like you are. alloc/init does the job by starting the object retain count at 1, retaining it again just ensures it leaks forever. Get rid of the retain and release it before returning and it won't leak.
Just a tip: be sure that you retain the return value of this method when using it elsewhere - the result has been autoreleased as isn't guaranteed to be around when you need it unless you explicitly retain/release it somewhere.
Otherwise, this code should work. If it still doesn't, one other thing I would try is maybe doing [mutableArray addObject:[mutableDict copy]] to ensure that mutableDict causes you no problems when it is released.
In Memory Management Programming Guide under the topic Returning Objects from Methods (scroll down a bit), there are a few simple examples on how to return objects from a method with the correct memory managment.

Resources