Removing duplicates of a key value from an array of dictionaries - ios

I am doing a Facebook API request to give me back all the names of the albums from a particular Facebook group. I get back an array of dictionaries with 3 keys/values, one of which being the key 'name' which maps to the album name, along with the keys 'id' and 'created_time'.
Only problem is that for some reason i'm getting back duplicate 'name' values of albums... but only a couple. And when i go to the Facebook page there's only one instance of that album anyway, no duplicates.
Also, their 'id' values are different, but it's only the first dictionary from the group of duplicates that has a Facebook id that actually points to valid data, the other Facebook id values just don't return anything when you perform a Facebook graph search with them, so it's the first of the duplicates that i want.
How can i remove these useless duplicate dictionaries from my array and keep the one with a valid Facebook id?? Thanks! :)

First I'd like to say that it's probably more beneficial to find a way to get a 'clean' list from faceBook as opposed to covering up problems afterwards. This might not be possible right now, but at least find out what the reason is for this behaviour or file a bug report.
Barring that, this should do the trick:
-(NSMutableArray *) groupsWithDuplicatesRemoved:(NSArray *) groups {
NSMutableArray * groupsFiltered = [[NSMutableArray alloc] init]; //This will be the array of groups you need
NSMutableArray * groupNamesEncountered = [[NSMutableArray alloc] init]; //This is an array of group names seen so far
NSString * name; //Preallocation of group name
for (NSDictionary * group in groups) { //Iterate through all groups
name =[group objectForKey:#"name"]; //Get the group name
if ([groupNamesEncountered indexOfObject: name]==NSNotFound) { //Check if this group name hasn't been encountered before
[groupNamesEncountered addObject:name]; //Now you've encountered it, so add it to the list of encountered names
[groupsFiltered addObject:group]; //And add the group to the list, as this is the first time it's encountered
}
}
return groupsFiltered;
}

Related

Make sure async methods execute in same order as they were called

Let's say I have 3 async methods which all do the same thing: send some data to a server and add the response data to an array (all of them add to the same array).
I call these methods at the same time, but they send different amounts of data to the server.
If the first method sends more data to the server than the third method, the third method will get the response back sooner, thus add the data to the array sooner. The response data consits of coordinates, so the order in which they are in the array matters.
How can I make sure, that even if the third method gets the repsonse sooner than the first or the second one, it won't add the data to the array before the previous methods did? Thus, preserving the order of the coordinates in the array.
The methods are NSURLConnections and they all send an async request.
EDIT: Here is the working code:
//Array of snapped points from the JSON
NSArray *snappedPoints = [result objectForKey:#"snappedPoints"];
NSMutableArray *locationsArray = [[NSMutableArray alloc] init];
//Loop through the snapped points array and add each coordinate to a temporary array
for (int i = 0; i<[snappedPoints count]; i++) {
NSDictionary *location = [[snappedPoints objectAtIndex:i] objectForKey:#"location"];
double latitude = [[location objectForKey:#"latitude"] doubleValue];
double longitude = [[location objectForKey:#"longitude"] doubleValue];
CLLocation *loc = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude];
[locationsArray addObject:loc];
}
//Add these temporary location arrays to the dictionary with the key as the request number
[tempLocationDictionary setObject:locationsArray forKey:[NSString stringWithFormat:#"%i",requestNumber]];
//If all requests have completed get the location arrays from the dicitonary in the same order as the request were made
if([tempLocationDictionary count] == numberOfRequests){
//Just a path because I am drawing these coordinates on a map later
GMSMutablePath *path = [GMSMutablePath path];
//Loop through the dictionary and get the location arrays in the right order
for (int i = 0; i<[tempLocationDictionary count]; i++) {
//Create a dummy array
NSArray *array = [tempLocationDictionary objectForKey:[NSString stringWithFormat:#"%i",i+1]];
//Get the coordinates from the array which we just got from the dictionary
for (CLLocation *location in array) {
[path addCoordinate:location.coordinate];
}
}
One way would be to replace NSURLConnection with NSURLSession and its related classes. It's newer and generally a superior choice. For your specific situation, it's possible to use a custom NSURLSessionConfiguration where you would set HTTPMaximumConnectionsPerHost to 1. That way the connections would be forced to run in order, solving your problem.
Of course, not having simultaneous connections might slow things down. In that case you'll have to accumulate the data temporarily without adding it to your array and only update the array when all the connections are complete. There are various ways you might do this depending on exactly what data comes back from the server.
One relatively simple way: If you know you'll always have three connections, use an NSMutableDictionary with integer NSNumber objects as the keys. After each connection you'd do something like
mutableDictionary[#1] = // your server data here
Use #2 or #3 for the other connections. Every time you add data, check to see if you have results for all three, and if so, add everything to your array. There are lots of other ways around this, the key is to have some kind of temporary structure where you can (a) accumulate data until all the connections are complete, and (b) keep track of which data came from which connection, either simply by number, or by URL, or by some other unique data that the server provides.
This is a problem to be solved with NSOperationQueue and dependencies.
Check out Advanced NSOperations [transcript] from WWDC 2015:
When the WWDC app starts up, there's a bunch of setup that we need to do.
First, we are going to download a small configuration file, and this file will tell us small things like what's the most recently supported version of the application, what features we have enabled, and so on.
So after we download this file, we are going to perform a version check to make sure that you are running the latest version of the WWDC app.
And then after we check the version of the app, we can start downloading useful pieces of information, such as the news that we show in the News tab and the schedule for the conference.
After we've downloaded the schedule, then we can start importing any favorites that you've saved to iCloud, any feedback that you've submitted so you can see it in the app, and we are also going to start downloading the list of videos.
Applying dependencies as they describe for these operations should sort you nicely.

iOS: Fetching CNContact Using Identifier?

I'm having trouble refetching a CNContact using an identifier. Early on I enumerate through all contacts and do something with the phone Numbers, while storing the identifiers in an array.
Later on I'd like to fetch a specific contact using one of the stored identifiers using the following (where currentIdentifier is the identifier I stored earlier):
CNContact *currentContact = [[CNContact alloc] init];
currentContact = [self.contactStore unifiedContactWithIdentifier:currentIdentifier keysToFetch:#[[CNContactFormatter descriptorForRequiredKeysForStyle:CNContactFormatterStyleFullName]] error:&error];
However, I keep getting NULL when I log it.

Merge two objects of same type

I have two objects:
deviceConfigInfo and deviceStatusInfo
Both contain an array of devices (so theres a third device object actually).
For each device returned in deviceConfigInfo there are these properties:
uuid
name
somethingElse
lookAnotherOne
and for deviceStatusInfo
uuid
name
somethingElse
someStatusInfo
someMoreStuff
(If you hadn't guessed, I just made up some random properties)
So back to that third object I mentioned, device, I created it with all the properties combined. Now, my question is, say the deviceStatusInfo gets updated, how can I update the device object without losing the "old" data that isn't overwritten (in this case, the lookAnotherOne property).
Does it have to be a manual process of getting the device with the matching uuid and then updating each of the properties for deviceStatusInfo or is there a quicker way of doing this? Imagine there were loads of properties.
Hopefully this makes sense. If it helps, I am using Mantle to create the objects/models.
I noticed that Mantle has the following function which I was able to use:
mergeValueForKey:fromModel:
So in my device model, I added two functions:
mergeConfigInfoKeysFromModel:
mergeStatusInfoKeysFromModel:
These functions have access to an array that contains NSString values representing the properties/keys. There is one array for the configInfo and another for statusInfo properties/keys.
I then loop through the keys and use valueForKey to check it has an actual value. If it does, I then call the mergeValueForKey:fromModel:.
Example Code:
- (void)mergeConfigInfoKeysFromModel:(MTLModel *)model
{
NSArray *configInfoKeys = #[#"uuid", #"name", #"somethingElse", #"lookAnotherOne"];
for (NSString *key in configInfoKeys) {
if ([model valueForKey:key]) {
[self mergeValueForKey:key fromModel:model];
}
}
}
All I have to do now, is call the appropriate merge function on the device object when I get an update, passing over the updated device object. Just as below:
[self.device mergeConfigInfoKeysFromModel:deviceUpdate];

Indexed tableView with Array of Unknown Contents?

I want to create an index for my tableView songsTable with the quick-jump index on the right. songsTable displays the contents of songsArray, which shows a list of songs from the user's iPod library:
However, all tutorials that show how to index a tableView already know their contents. These are the tutorials I've seen so far: AppCoda, iPhoneDevCentral, Innofied, among a few others. They all use NSDictionaries to store their contents, I'm using an NSArray.
I tied to follow the AppCode tutorial and I got stuck at the first hurdle:
-(void)createAlphabetArray
{
self.alphabetArray = [[NSMutableArray alloc]initWithCapacity:26];
for (int i=0; i< songs.count; i++)
{
NSString *firstletter=[[songs objectAtIndex:i]substringToIndex:1];
if (![self.alphabetArray containsObject:firstletter]) {
[self.alphabetArray addObject:firstletter];
}
}
[songs sortUsingSelector:#selector(localizedCaseInsensitiveCompare:)]; //sorting array in ascending array
NSLog(#"%#", self.alphabetArray);
}
I'm really, really confused and I can't find any other tutorials that are useful for this situation. Any help would be much appreciated. Thanks.
EDIT: I say 'unknown contents' because I don't know what the contents of songsArray is, like the other tutorials, which actually provide the content of the tables.
You can't add objects to NSArray one by one. You can either add them all in one line, or copy an array to your array.
What you're probably looking for is NSMutableArray instead of NSArray.
This allows you to add objects at will.
You was probably supposed to implement the logic inside loop slightly different:
NSString *firstLetter = [songsArray[i] substringToIndex:1];
if (![self.alphabetArray containsObject:firstLetter]) {
[self.alphabetArray addObject:firstLetter];
}
UPDATE
This would help you to fix the crash. But I don't still understand what do you mean under unknown content. I don't see anything related to this problem in the provided code.

Saving NSDictionary in NSUserDefaults - hash fails

I am trying to save a NSDictionary to NSUserDefaults, and am using MD5 hash to check for integrity, using this helpder class: Secure-NSUserDefaults.
The code to set the Dictionary:
#import "NSUserDefaults+MPSecureUserDefaults.h"
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setSecureObject:aDictionary forKey:aKey];
[defaults synchronize];
The code to retrieve it:
BOOL valid = NO;
NSDictionary * aDictionary = [defaults secureDictionaryForKey:aKey valid:&valid];
if (!valid) {
//... hash doesn't match
} else {
//... hash matches
}
This works great as long as the app is running (testing in the simulator right now), but when I exit the simulator and restart the app, the hash value is different than before.
It's as if exiting the app changes the dictionary value (when it's saved to disk perhaps?) in some way. It's not adding visible characters, though, because it looks exactly the same in the debugger.
Would appreciate any ideas from more experienced programmers!
EDIT:
So this seems to work for me. Thoughts?
Change NSUserDefaults+MPSecureUserDefaults.m like so:
- (NSString *)_hashObject:(id)object
{
if (_secretData == nil) {
// Use if statement in case asserts are disabled
NSAssert(NO, #"Provide a secret before using any secure writing or reading methods!");
return nil;
}
// Copy object to make sure it is immutable (thanks Stephen)
object = [object copy];
//added check for array or dictionary
if ([NSJSONSerialization isValidJSONObject:object]) {
NSMutableData *archivedData = [[NSJSONSerialization dataWithJSONObject:object options:0 error:nil] mutableCopy];
[archivedData appendData:_secretData];
if (_deviceIdentifierData != nil) {
[archivedData appendData:_deviceIdentifierData];
}
NSString *hash = [self _hashData:archivedData];
return hash;
}
// Archive & hash
NSMutableData *archivedData = [[NSKeyedArchiver archivedDataWithRootObject:object] mutableCopy];
[archivedData appendData:_secretData];
if (_deviceIdentifierData != nil) {
[archivedData appendData:_deviceIdentifierData];
}
NSString *hash = [self _hashData:archivedData];
////[archivedData release];
return hash;
}
The code you are using, Secure-NSUserDefaults, is incorrect.
The code makes assumption about NSKeyedArchiver's archivedDataWithRootObject: which are invalid - namely that if two dictionaries are the same then the archived version of them is the same. The internal ordering of key/value pairs in a dictionary is not defined, two dictionaries can be semantically the same while being structurally different - and if they are structurally different their archived version of them may be also.
Either write your own or fix the library you have used. You need to deal with dictionaries as an ordered collection of key/values pairs - say by sorting based on the key as NSLog does when printing them.
HTH
Addendum: After question edit
NSJSONSerialization suffers from the same problem (for this usage) as NSKeyedArchiver, as the simple test I posted on GitHub will show.
It seems you may be missing the core problem here. A dictionary is an unordered collection of key/value pairs. The code you are using is attempting to generate a sequence of bytes which is identical (or at least produces the same hash value) for different dictionaries which contain the same key/value pairs in any order. The issue is compounded as dictionaries/arrays can contain other arrays/dictionaries to any nesting depth.
The obvious way to do generate a byte sequence independent of the (internal) ordering is to order the key/values pairs when producing the byte sequence. However dictionary keys are not required to have an ordering, only an equality, relation.
As there is no ordering requirement on keys the NSKeyedArchiver and NSJSONSerialization cannot assume one exists and so do not guarantee to produce the same byte sequence for dictionaries with the same key/value pairs which are ordered (internally to the type) differently. Furthermore NSKeyedArchiver is preserving the object graph, including any sharing, see Object Graphs, which could also contribute to the differences you observe.
However you are writing property lists and for a dictionary to be valid for inclusion in a property list the keys must be strings (see Apple's About Property Lists). Now strings do have an ordering, e.g. NSString's compare: method, so in this particular case you can order the key/value pairs. So you can either write your own code, or find pre-written code, which produces a byte stream for property list types and orders the dictionary key/value pairs while doing so; then you can use this code in the library you are trying to adopt.
Just an idea how this class may be fixed:
NSDictionary should be archived with NSKeyedArchiver not only to calculate hash over it, but also to be saved like that (archived) in the NSUserDefaults (in opposite to the direct storing as it is done now).
In the get method, upon the hash validation, it will be needed additionally to unarchive it with NSKeyedUnarchiver to get back original value.
Thanks.

Resources