Remove object in array enumeration - ios

[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (condition) {
[array removeObject:obj];
}
}];
sometimes it worked,and sometimes it crashed,why?

Imagine how you would write the code for -enumerateObjectsUsingBlock: if you were working at Apple and asked to add this. It would probably look something like:
-(void) enumerateObjectsUsingBlock: (void(^)(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop))myBlock
{
NSUInteger numItems = [self count];
BOOL stop = false;
for( NSUInteger x = 0; x < numItems && stop == false; x++ )
{
myBlock( [self objectAtIndex: x], x, &stop );
}
}
Now if your block calls removeObject:, that means that the array:
0: A
1: B
2: C
3: D
After myBlock( A, 0 ) changes to:
0: B
1: C
2: D
Now x++ gets executed, so next call is myBlock( C, 1 ) -- already you see that the 'B' is now skipped, and the item originally at index 2 is deleted second (instead of the one at index 1). Once we have deleted that, we loop again and the array looks like this:
0: B
1: D
So when -enumerateObjectsUsingBlock: tries to delete the item at index 2, it runs off the end of the array, and you get a crash.
In short, the documentation for -enumerateObjectsUsingBlock: doesn't say anywhere that you may modify the array while you're iterating it, and there is no way for the block to tell the looping code in -enumerateObjectsUsingBlock: that it just deleted an object, so you can't rely on that working.
(You can try this out yourself ... rename this version of enumerateObjectsUsingBlock: to myEnumerateObjectsUsingBlock:, declare it in a category on NSArray, and step through it in the debugger with a program like: [myArray myEnumerateObjectsUsingBlock: ^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop){ [myArray removeObject: obj]; }];)
If you expect you'll be deleting items from the array, there are several workarounds. One, you can make a copy of the array, loop over that, and delete objects from the original array. Another option is to iterate backwards over the array, which means that the indexes of earlier items won't change (try modifying the version of -enumerateObjectsUsingBlock: above and watch what happens in the debugger).
Yet another approach is to write your own method that filters the array. You give it a block that is expected to return YES if you are to keep the object, NO if you shouldn't. Then you loop over the original array, call the block on each item, and create a new array, to which you add all objects for which the block returns YES.

This is unsafe way. I don't know well internal execution of this language, btw I think when you remove the object, then the size of array decreases, and it would be error when you reach the last element of array.
I think in enumeration block, you are not allowed to change array. it is same issue on other languages.
You can get further information in this url.
http://ronnqvi.st/modifying-while-enumerating-done-right/

After deletion, your objects are not proper. so make copy as:
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (condition) {
int counter = [array indexOfObject:obj];
[array removeObjectAtIndex:counter];
}
}];

Related

How to access index of NSMutable Array in Objective-C?

for(int i=0;i<[serviceNamesFilterArray count];i++){
NSLog(#"state : %#", [serviceNamesFilterArray objectAtIndex:i]);
NSString *str = [serviceNamesFilterArray objectAtIndex:i];
if (tag_id == [serviceNamesFilterArray indexOfObject:str] ) {
// filterButtonArray = serviceNamesFilterArray;
[filterButtonArray addObject:str];
NSLog(#"%#",filterButtonArray);
}
}
I want to access index of serviceNamesFilterArray. How can i access index's of my array so that i can compare it with integer tag_id?
Even Objective-C provides smarter filter APIs than a loop.
index will contain the index of the object in the array matching tag_id
NSInteger index = [self.serviceNamesFilterArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return (NSString *)obj.integerValue == tag_id;
}];
you can compare i value with your tag_id as follows:
for(int i=0;i<[serviceNamesFilterArray count];i++) {
NSLog(#"state : %#", [serviceNamesFilterArray objectAtIndex:i]);
NSString *str = [serviceNamesFilterArray objectAtIndex:i];
if (tag_id == i) {
//perform your logic here
}
}
you can use the enumerateObjectsUsingBlock method, like
[serviceNamesFilterArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// ...
}];
Preamble: you are using [array objectAtIndex:index] in your code, while this was the way to index historically in modern Objective-C you simply write array[index]. If you are learning Obj-C from a book/website you might want to look for a newer text.
It is unclear what you are asking let’s see what we can figure out.
You start with a loop:
for(int i=0;i<[serviceNamesFilterArray count];i++)
Here i is going to be used as an index into the array serviceNamesFilterArray. Inside the loop you then access the object at index i (updating your code as above):
NSString *str = serviceNamesFilterArray[i];
and having obtained the object at index i you ask what is the index of that object:
[serviceNamesFilterArray indexOfObject:str]
There are two possible answer here:
i – this is the most obvious answer and will be the result if there are no duplicates in serviceNamesFilterArray. It will be the answer as you just obtained str from index i of the array.
j where j < i – this will be the answer if the array contains duplicates and the same string is found at indices j and i. This result happens because indexOfObject: returns the first index at which the object occurs with the array.
The most likely result seems to be (1) in your case (guessing you do not have duplicate “service names”). In this case your conditional is equivalent to:
if (tag_id == i) {
[filterButtonArray addObject:str];
}
However if this is your intention then the loop is completely unnecessary as your code is equivalent to:
NSString *str = serviceNamesFilterArray[tag_id];
[filterButtonArray addObject:str];
If the serviceNamesFilterArray does contain duplicates then your code as written may add the string at index tag_id multiple times to filterButtonArray or it may add it no times – we'll leave figuring out why as an exercise, and we doubt this is your intention anyway.
At the time of writing #vadian has made a different guess as to your aim. Their solution finds the index, if any, where the string value if interpreted as an integer is equal to the value of tag_id (an integer). If that is your aim then #vadian’s solution provides it.
Of course both our and #vadian’s guesses might be wrong at to what your aim is. If so you can edit the question to explain, or delete it and ask a new one instead – given this question has at the time of writing 3 answers already deletion in this case might be better to reduce future confusion when people read the (revised) question and (outdated) answers.
HTH
Maybe you just need to judge whether the tag_id is less than count of serviceNamesFilterArray, then you can get the value by tag_id directlly.
if (tag_id < [serviceNamesFilterArray count]){
NSString *str = [serviceNamesFilterArray objectAtIndex:tag_id];
// other logic here
}

Failed to remove a CKReference from a CKReferenceList using CKRecord removeObject

I have a CKReferenceList for a list of employee, something may look like this
(
"<CKReference: 0xa5bc930; 83a97165-2635-4bda-a7eb-fabf5a725bed:(_defaultZone:__defaultOwner__)>",
"<CKReference: 0xa50a2e0; 9B7F4269-D8BA-4CE9-9BCF-AD2047B73EB5:(_defaultZone:__defaultOwner__)>"
)
Then I have a another CKReference which points to a employee record. Which might look like this
"<CKReference: 0xa5bc330; 83a97165-2635-4bda-a7eb-fabf5a725bed:(_defaultZone:__defaultOwner__)>"
Notice that the reference of this specific employee is in the employee list, they have the same reference name, namely " 83a97165-2635-4bda-a7eb-fabf5a725bed".
When I tried to remove using the code below
NSMutableArray<CKReference*>* employeeList = [[NSMutableArray alloc]initWithArray:self.employeeList];
if(employeeList != nil){
[employeeList removeObject:employeeReference];
}
This won't remove the reference from the list even though the reference name is equal(the same happens to contain method).
I think the the removeObject uses the isEqual: method as a criteria, so when comparing two CKReference, they are actually comparing the address, but not based on the string name.
Should I just overwrite the isEqual implementation to provide my own in order to remove a given CKReference from a list of CKReference.
Or is there any alternative approach for this?
You can call indexOfObjectPassingTest: and compare whatever you need:
NSMutableArray <CKReference*>* employeeList = [[NSMutableArray alloc] init];
NSUInteger index = [employeeList indexOfObjectPassingTest:^BOOL(CKReference * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
return [obj.recordID.recordName isEqualToString:recordNameToRemove];
}];
if (index != NSNotFound) {
[employeeList removeObjectAtIndex:index];
}

Should I compare NSNotFound to NSInteger or NSUInteger?

Here's my code:
NSArray *allIds = [self.purchasableObjects valueForKey:#"productIdentifier"];
NSInteger index = [allIds indexOfObject:productId];
if (index == NSNotFound)
return;
Which one to compare to NSNotFound... NSInteger or NSUInteger and why?
The reason why he's asked this question is because the AppKit is inconsistent with Foundation indexes, especially for NSMenu and NSTableView:
id obj = ...;
NSMenu * menu = ...;
/* This returns an NSInteger, returning -1 (which when casted, is NSNotFound) */
NSInteger index = [ menu indexOfItemWithRepresentedObject:obj ];
/* This returns an NSUInteger, since arrays cannot have negative indices. */
NSUInteger idx = [ [ menu itemArray ] indexOfObjectIdenticalTo:obj ];
Because of this, one must cast back and forth, but this creates new problems:
Because indexes cannot be negative, the number of rows (or submenus,
etc) is lower BY HALF of what normal arrays can be.
If you attempt to read or insert an object to a negative index (-2) in a UI element, you do not get range exceptions. If you cast to unsigned, the index is undefined. So this code, which is wrong, compiles without error:
id obj = ...;
NSMenu * menu = ...;
/* Or the result of some integer arithmetic. */
NSInteger idx = -2;
/* Compiles, doesn't throw exception. Index COULD be NSIntegerMax - 1, but the ending index is undefined. */
[ menu insertItem:obj atIndex:idx ];
/* Compiles, but throws NSRangeException. */
[ [ menu mutableArrayValueForKey:#"items" ] insertObject:obj atIndex:( NSUInteger )idx ];
In the first case, when inserting an item beyond menu.numberOfItems (which is an NSInteger), in general (but not guaranteed), the method is equivalent to [ menu addItem ]. So, index is transformed thus:
[ menu insertItem:obj atIndex:MIN( idx, menu.numberOfItems ) ];
But in older versions of AppKit, index is rounded up!:
[ menu insertItem:obj atIndex:MAX( MIN( idx, menu.numberOfItems ), 0 ) ];
So a programmer must take great care when inserting and/or retrieving indexes from UI elements, or use an NSArrayController instead.
For all cases, though it is safe to check both [ menu indexOfItem:obj ] and [ array indexOfObjectIdenticalTo:obj ] are equal NSNotFound, so long as you use an explicit cast. While NSNotFound is has a declared type of NSUInteger, it is defined as NSUIntegerMax, which when cast equals -1. NEVER check if a value is less than NSNotFound as you will create yet another infinite loop. But feel free to:
NSInteger index = [ menu indexOfItem:nonexistantObject ];
if( ( NSUInteger )index == NSNotFound ) {
...blah...
}
or
NSUInteger index = [ array indexOfItem:nonexistantObject ];
if( index == NSNotFound ) {
...blah...
}
However, if the return type is NSInteger, you should not assume that if the returned index isn't NSNotFound that it is a valid index (especially if you're using a 3rd-party framework or library). Instead, you should check to see if the returned index is in a valid range:
NSInteger index = [ menu indexOfItem:nonexistantObject ];
if( ( NSUInteger )index == NSNotFound ) {
/* Can't find it, so insert or do something else. */
} else if( !( index >= 0 && index <= menu.numberOfItems ) ) {
/* Throw an invalid range exception. There's a major bug! */
} else {
/* woo hoo! we found it! */
}
Of course that check is computationally expensive, so it should only be used in code that runs infrequently, is not in a loop, or in debug code. If you want to ensure that you always get a valid index as quickly as possible, you should use block enumeration, which is faster than just about any other method:
id obj = ..;
/* Get an array from the UI, or just use a normal array. */
NSArray * array = [ menu itemArray ];
/* Find an index quickly */
NSUInteger index = [ array indexOfObjectWithOptions:NSEnumerationConcurrent passingTest:^BOOL( NSUInteger idx, id testObj, BOOL * stop ) {
if( obj == testObj ) {
*stop = TRUE;
return TRUE;
}
} ];
The returned index is guaranteed to be valid, either NSNotFound or in NSMakeRange( 0, array.count - 1 ). Notice I did not use -isEqual: as I was checking for a specific instance not an object that could be interpreted as the same as another.
If you need to do something with the indices in the loop, you can use the same technique (notice the indices are passed to the block, and are always valid):
[ array enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^( NSUInteger idx, id obj, BOOL * stop ) {
/* do something to this object, like adding it to another array, etc */
} ];
Using the block methods on arrays always get you valid indices, that can be cast to NSIntegers for use in UI elements. There are also range-specific block methods that can restrict the values even further. I'd recommend reading through the NSArray and NSMutableArray documentation for pointers. It's also quite easy to misuse blocks, by adding code to the [enumerate...withBlock:][3] methods, especially if you are adding the objects to another collection class:
NSArray * source = ...;
NSMutableArray * dest = ...;
[ source enumerateObjectsUsingBlock:^( NSUInteger idx, id obj, BOOL * stop ) {
if( /* some test */ ) {
/* This is wasteful, and possibly incorrect if you're using NSEnumerationConcurrent */
[ dest addObject:obj ];
}
} ];
It's better to do this instead:
/* block removed for clarity */
NSArray * source = ...;
NSMutableArray * dest = ...;
NSIndexSet * indices = [ source indexesOfObjectsPassingTest:testBlock ];
[ dest addObjects:[ source objectsAtIndexes:indices ] ];
Sorry for the long-winded response, but this is a problem with Cocoa indexing (and especially NSNotFound) that my developers have to solve and re-solve, so I thought it would be helpful to share.
You should use NSUInteger
The reason behind this is "The array index is not going to minus (i.e object at index -5)"
typedef int NSInteger;
typedef unsigned int NSUInteger;
NSInteger must be used when there are probability to get values in plus or minus.
Hope it helps.
Thanks

Asserting properties in a custom NSMutableArray

I'm not too sure if I should of made another question for this or expanded on the last question, please correct me if I wasn't meant to.
I've currently got this code working:
if ([ myArray containsObject:#"Object1" ] && [ myArray containsObject:#"Object 2"]) {
return YES;
}
else {
return NO;
}
What I'm needing to do is modify this so it iterates through an array and accesses an Objects property value. For example:
if (myArray contains obj.ID 1 & 2) {
return YES
}
else{
return NO;
}
Any suggestions on what I should look at? I've been at this for a couple of hours and tried different permutations with no luck.
Thank you!
You can use -indexOfObjectPassingTest: to check if an object with a particular attribute value is in your array. The method returns either the object's index if it is found or NSNotFound if not.
Thus, assuming your objects are e.g. NSDictionaries and they have NSNumbers as IDs, you could do something like this:
if([myArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [[obj objectForKey:#"ID"] intValue]==1;
}]!=NSNotFound && [myArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [[obj objectForKey:#"ID"] intValue]==2;
}]!=NSNotFound)
{
//Array contains objects
}
If you only want the first object, you can use -indexOfObjectPassingTest: as mpramat says. If you want all the objects in the array that match your criteria, use indexesOfObjectsPassingTest:.
It takes a block as a parameter. The block evaluates each object and returns YES or know to let the method know if that object should be part of the set of objects that pass the test.

How does this block return work?

Im trying to interpret this block code:
SCENARIO 1
NSIndexSet* indexes = [[self.orderItems allKeys] indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
IODItem* key = obj;
return [searchItem.name isEqualToString:key.name] && searchItem.price == key.price;
}];
Ok so I get allKeys of the self.orderItems dictionary and pass them as id obj to the block {}. Inside the block, that obj is assigned to an IODItem *key. Then both the call isEqualToString returns a BOOL as does the comparator ==. This makes sense to me because the block is of return type BOOL. So how does that fill an NSIndexSet of indexes?
SCENARIO 2
NSArray* keys = [[self.orderItems allKeys] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
IODItem* item1 = (IODItem*)obj1;
IODItem* item2 = (IODItem*)obj2;
return [item1.name compare:item2.name];
}];
So I get allKeys again for that dictionary. I then sortArrayUsingComparator and I pass in the keys as obj1 & obj2? This is confusing. obj1 & obj2 are just keys in the [self.orderItems allKeys]-array?
Then I take those 2 objs and assign each to a different IDOItem. Then I actually return the items1 & 2? This is confusing again. I thought I was filling in an NSArray *keys. Why am i returning 2 things?
SCENARIO 3
// 3 - Enumerate items and add item name and quantity to description
[keys enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
IODItem* item = (IODItem*)obj;
NSNumber* quantity = (NSNumber*)[self.orderItems objectForKey:item];
[orderDescription appendFormat:#"%# x%#\n", item.name, quantity];
}];
im enumerating through the keys array (which is gotten from SCENARIO 2 actually) and using the key obj and assigning it to an IODItem item. But here is where i got lost...I take the objectForKey item and use it as a quantity? If objectForKey returns the object paired to that key, and the key "item" refers to the id obj, then that id obj is a key from the sorted keys array. So its a key, not a value! Isnt it?
It's not too confusing if you think of blocks as a method with arguments and a return type. That first method is just iterating through the entire set of keys and determining if they pass the test you created or not. If your block returns 'YES' then it is added to the new index set. If the block returns 'NO' then it is ignored. The resulting index set will refer to a complete set of keys that passed the test.
The second method is a way for you to perform a custom sort on an array. Depending on which object should be closer to the beginning of the array, you either return NSOrderedAscending, NSOrderedSame or NSOrderedDescending. This could change based on the criteria you have for the sort. What you have basically done is called the compare: method on the first object's 'name' property. Depending on what data type this is (I'm assuming it's an NSString), your block will call the compare: method from NSString. If it was an NSNumber or another class, it would call that class's compare: method, etc. etc. The compare: method also returns an NSComparisonResult (one of the three options listed above).
Added Scenario 3:
For scenario 3 while enumerating through the 'keys', each 'id obj' is a key in the dictionary, NOT an IODItem as you've coded it. To get the applicable IODItem, you will probably need to do something like this (I'm assuming self.orderItems refers to an NSDictionary object):
__block NSMutableString *orderDescription;
__block NSNumber *quantity;
[keys enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString* currentKey = (NSString*)obj;
IODItem *item = [self.orderItems objectForKey:currentKey];
quantity = item.quantity;
[orderDescription appendFormat:#"%# x%#\n", item.name, quantity];
}];

Resources