What's the correct way to use inDatabase: within an async networking call? - afnetworking

I'm using AFNetworking along with FMDB to remove database rows from an array when I make a successful POST to a server. My problem at the moment is that my logging shows that I'm skipping every second item in the array, so I think I've incorrectly used FMDB in conjunction with AFNetworking's async request.
This is what the code looks like:
[manager POST:[_urlEndpoint absoluteString] parameters:data success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
[_dbQueue inDatabase:^(FMDatabase *db) {
for (int i = 0; i < arrayOfIds.count; ++i)
{
NSLog(#"Removing event at index: %#", arrayOfIds[i]);
[_db removeWithId:arrayOfIds[i]];
}
}];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
In the arrayOfIds, the logs shows that I hit 1, skip 2, hit 3, skip 4, etc. In this particular case, I've made sure I'm only sending one POST request so narrow down the root cause so I know there aren't multiple accesses to arrayOfIds.
What could I have done wrong that I may have missed?
(Possibly) related question I just asked before: FMDB: Does calling a method to make a query in the inDatabase block work the same way?
EDIT: Here are what my logs look like..
2014-07-13 03:08:28.684 PostTestApp[77268:6675567] Adding event to array to be sent: 1
2014-07-13 03:08:28.684 PostTestApp[77268:6675567] Adding event to array to be sent: 2
2014-07-13 03:08:28.684 PostTestApp[77268:6675567] Adding event to array to be sent: 3
2014-07-13 03:08:28.684 PostTestApp[77268:6675567] Adding event to array to be sent: 4
2014-07-13 03:08:28.684 PostTestApp[77268:6675567] Adding event to array to be sent: 5
2014-07-13 03:08:28.684 PostTestApp[77268:6675567] Adding event to array to be sent: 6
2014-07-13 03:08:29.191 PostTestApp[77268:6675567] JSON: {
description = "Server response from /events";
name = response;
}
2014-07-13 03:08:29.192 PostTestApp[77268:6675567] Removing event at index: 1
2014-07-13 03:08:29.192 PostTestApp[77268:6675567] Removed event from row: 1
2014-07-13 03:08:29.195 PostTestApp[77268:6675567] Removing event at index: 3
2014-07-13 03:08:29.195 PostTestApp[77268:6675567] Removed event from row: 3
2014-07-13 03:08:29.196 PostTestApp[77268:6675567] Removing event at index: 5
2014-07-13 03:08:29.197 PostTestApp[77268:6675567] Removed event from row: 5
"Adding event to array to be sent:" is a for-loop that adds database IDs into arrayOfIds.
"Removing event at index:" happens inside the inDatabase: block.
"Removed event from row:" happens
inside removeWithId:.
EDIT 2:
Full code block without edits: https://gist.github.com/jonalmeida/27bee72b9015d45434e8

According to your gist, your actual code looks like this:
for (int i=0; i < dbIndexArray.count; i++) {
NSLog(#"Removing event at index: %#", dbIndexArray[i]);
[_db removeEventWithId:[[dbIndexArray objectAtIndex:i] longLongValue]];
[dbIndexArray removeObjectAtIndex:i];
}
The problem is: you are modifying the array while you're iterating through it. When you remove object 0, and object 1 becomes object 0.
Try this:
NSMutableArray *removedIDs = [NSMutableArray array];
for (int i=0; i < dbIndexArray.count; i++) {
NSLog(#"Removing event at index: %#", dbIndexArray[i]);
[_db removeEventWithId:[[dbIndexArray objectAtIndex:i] longLongValue]];
[removedIDs addObject:dbIndexArray[i];
}
[dbIndexArray removeObjectsInArray:removedIDs];

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
}

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

__NSCFDictionary doesn't respond to any selectors

I am trying to Parse JSON that looks like this:
food = {
"food_id" = 4823;
servings = {
serving = (
{
calcium = 9;
calories = 221;
carbohydrate = "16.20";
cholesterol = 31;
I can successfully retrieve the array at the [food][servings][serving] level, which I confirm via a log statement has the appropriate class, __NSCFArray, but I run into a problem when iterating through that array and trying to do useful things with the contained information:
for (id foodId in resultsPerServing) {
NSLog(#"hree is a result %#", foodId);
foodObjectClass *foodObject = [foodObjectClass new];
NSDictionary *foodIdDictionary = (NSDictionary *)foodId;
if ([foodIdDictionary respondsToSelector:#selector(allKeys)]) {
[foodObject getDetailsFromResponseObject:foodIdDictionary];
} else {
unsigned int mc = 0;
Method * mlist = class_copyMethodList(object_getClass(foodIdDictionary), &mc);
NSLog(#"%d methods", mc);
for(int i=0;i<mc;i++)
NSLog(#"Method no #%d: %s", i, sel_getName(method_getName(mlist[i])));
[NSException raise:#"DIDN'T GET A DICTIONARY" format:#"here is the object %# which has class %#", resultsPerServing, [responseObject class]];
}
}
This causes the NSException to be raised without fail although in the NSException the output from printing the object looks like a dictionary. Additionally, when the exception prints out the class of the object which does not respond to allKeys, that object has class __NSCFDictionary. What gives? Why does this dictionary not have an allKeys method and how can I get a functional dictionary to work with?
Even more puzzling is that the runtime code below (copied from an SO post for which I have lost the link) indicates that the dictionary has 0 methods. Why does this object have no methods? How can I get an object that does have methods?
make a check to confirm that it as NSDictionary first?
if ([foodId isKindOfClass:[NSDictionary class]] ) {
foodIdDictionary = (NSDictionary *)foodId;
[foodObject getDetailsFromResponseObject:foodIdDictionary];
}

Get Index of specific instance of object while iterating through NSArray. (Duplicates are messing this up)

I've finally pinpointed the exact issue to a problem I've been having, but I'm not sure how to fix it. Here is the general gist of what is happening: I have an array called wordArray, and I am iterating through it. This array can contain strings that are identical to each other and different from one another. All I want to do is retrieve the index of the particular instance that I am currently iterating, so I the simplified version is this:
for(NSString* stringInWordArray in wordArray){
NSLog(#"String is: %# Index is: %d",stringInWordArray,[wordArray indexOfObject:stringInWordArray]);
}
This will obviously print out the String, and the index of the string, however, according to the apple documentation, the indexOfObject method "Returns the lowest index whose corresponding array value is equal to a given object." This is a problem for me because if I have an array that looks like this:
["fruit","fruit","panda","happy",nil];
Then this will print:
String is fruit Index is 0
String is fruit Index is 0
String is panda Index is 2
String is happy Index is 3
Where I want the indices to print 0,1,2,3. Any workarounds for this issue?
Use a "normal" loop:
for (NSUInteger i = 0; i < wordArray.count; i++) {
NSString *stringInWordArray = wordArray[i];
NSLog(#"String is: %# Index is: %u",stringInWordArray, i);
}
Or you can do:
NSUInteger i = 0;
for(NSString* stringInWordArray in wordArray) {
NSLog(#"String is: %# Index is: %u",stringInWordArray, i);
i++;
}
Or you can do:
[wordArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *stringInWordArray = obj;
NSLog(#"String is: %# Index is: %u",stringInWordArray, idx);
}];
You could use a NSDictionary with key = index and the value = your string:
NSDictionary *dict = #{#"0" : #"fruit", #"1" : #"friut", #"2" : #"panda", #"3" : #"happy"};
The keys are unique, so you know which object you use depending on the key.

Populate NSMutableArray from another array

I'd like to extract the friends' name of each of my five selected friends from a FBFriendPicker, and put them on a NSMutableArray.
for (id<FBGraphUser> user in self.friendPickerController.selection) {
[self.SelectedFriendsname addObject:user.name];
NSLog(#"Friend selected: %#", user.name);
}
NSLog(#"my friends are %#", self.SelectedFriendsname);
The "friend selected" line appears on console and works fine, but my array stays empty ((null) on console)
How should I proceed?
Solution from #ChintaN-Maddy-Ramani.
SelectedFriendsname is not initialized.
Objective-C
Resolved by preceding the loop with:
self.SelectedFriendsname = [NSMutableArray array];
Swift
You can directly map values instead of looping:
SelectedFriendsname = friendPickerController.selection.map { $0.name }

Resources