How does this block return work? - ios

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];
}];

Related

Remove object in array enumeration

[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];
}
}];

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];
}

How can I implement my logic properly to populate my UITableView

Sorry guys, this problem I am running into is pretty trivial. I just can't wrap my head around it so hope someone can help me. Your help is really appreciated. I am getting JSON data through NSURLConnectDelegate with a web API. I get something like this back:
(
{
id = 340
name = Vicent },
{
id = 339
name = Johny },
{
id = 338
name = Eric }
)
and I save it in a NSMutableArray as a global variable. Now, I have a NSSet of "ids". For example:
{
340, 339
}
In the numberOfRowsInSection, I return the set's count. I am trying to load only the ids in the NSSet from the array with the data saved from the webAPI, so I do something like this in cellForRowIndexPath:
for (NSNumber *num in [set allObjects]) {
NSString *newString = [[savedArray objectAtIndex:indexPath.row]
NSString *new = [num stringValue];
if ([new isEqual:newString]) {
}}
How can I just populate the ids I want?
The JSON makes it look like you have an array of dictionaries, which is a reasonable data structure to use as the data source for a table view.
It sounds like you're trying to filter your array to only include the items that are in your set. Is that right?
If so, you could write code that would create a new array containing the subset of your array elements who's ID is also in your set. There are at least a half-dozen ways to do that. One fairly simple approach would be to use the NSArray method indexesOfObjectsPassingTest. You'd pass that method a block of code that would check each array element to see if it's id object was in your set.
That would give you an NSIndexSet with the indexes of the items in your array who's ID are in your set. Then you could use the NSArray method objectsAtIndexes to get an array of only the objects that are also in the set. Something like this (Assuming that your array of dictionaries is called savedArray and your set is called allObjects:
//get the indexes of items in the array savedArray who's id appears in the set allObjects
NSIndexSet *indexes = [savedArray indexesOfObjectsPassingTest:
^(NSDictionary *obj,
NSUInteger idx,
BOOL *stop)
{
return [allObjects member: obj[#"id"]] != nil;
}
];
//Now build an (immutable) array of just the objects who's ID are in the set
NSArray *subArray = [savedArray objectsAtIndexes: indexes];
The array subArray created above is immutable. If you need a mutable array you would need to make a mutable copy, which is a one-line change.
Disclaimer: I still struggle a little with block syntax, so the above might not be exactly correct, but it gives you the general idea.

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.

"Incompatible pointer type.." when trying to sort using `sortedArrayUsingFunction`

I am trying to sort an NSMutableArray of NSMutableDictionarys basing on a price field.
NSString* priceComparator(NSMutableDictionary *obj1, NSMutableDictionary *obj2, void *context){
return #"just for test for the moment";
}
//In other function
arrayProduct = (NSMutableArray*)[arrayProduct sortedArrayUsingFunction:priceComparator context:nil];//arrayProduct is NSMutableArray containing NSDictionarys
On the statement above, I am getting the following warning which I want to fix:
Incompatible pointer types sending 'NSString*(NSMutableDictionary *__strong,NSMutableDictionary *__strong,void*)' to parameter of type 'NSInteger (*)(__strong id, __strong id, void*)'
As the error states, your priceComparator function needs to be declared as returning NSInteger, not NSString *:
NSInteger priceComparator(NSMutableDictionary *obj1, NSMutableDictionary *obj2, void *context){
if (/* obj1 should sort before obj2 */)
return NSOrderedAscending;
else if (/* obj1 should sort after obj2 */)
return NSOrderedDescending;
else
return NSOrderedSame;
}
Better yet, you could use NSSortDescriptors if the price you need to sort by is a simple numeric value that's always at a given key in these dictionaries. I think this is the syntax:
id descriptor = [NSSortDescriptor sortDescriptorWithKey:#"price" ascending:YES];
NSArray *sortedProducts = [arrayProduct sortedArrayUsingDescriptors:#[descriptor]];
Also note that all of the sortedArray... methods return a new, plain NSArray object, not a NSMutableArray. Thus the sortedProducts declaration in the sample code above. If you really do need your sorted array to still be mutable, you could use the sortUsingFunction:context: or sortUsingDescriptors: method of NSMutableArray to sort the array in-place. Note that these methods return void, so you wouldn't assign the result to any variable, it would modify your arrayProduct object in-place.

Resources