Gold challenge solution


#1

OK … here’s my shot at the gold challenge. I started by following Joe’s hint and added another method to JSONSerializable that converts the objects to a dictionary. The new method’s name is toJSONDictionary. Since this is a protocol it needs to be implemented in both RSSItem.m and RSSChannel.m. Here’s the code in RSSItem.m.

[code]- (NSDictionary *) toJSONDictionary
{
// Dictionary with href=link for song
NSDictionary *href = [NSDictionary dictionaryWithObjectsAndKeys: link, @“href”, nil];

// Dictionary with attributes=href (created above)
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: href, @“attributes”, nil];

// Create the array of attributes. The .mpa (player link) is the second one; so just use two attributes
NSArray *jLink = [[NSArray alloc]initWithObjects: attributes, attributes, nil];

// Dictionary with label=title of item
NSDictionary *label = [NSDictionary dictionaryWithObjectsAndKeys: title, @“label”, nil];

// Finally, dictionary with all of the above
NSDictionary *jsonDict = [NSDictionary dictionaryWithObjectsAndKeys: label, @“title”, jLink, @“link”, nil];

//NSLog(@"—%@+++", jsonDict);

return jsonDict;
}
[/code]

And in RSSChannel.m

[code]- (NSDictionary *) toJSONDictionary
{
NSMutableArray *dictionaryItems = [[NSMutableArray alloc]init];

// Create the array of entries
for (RSSItem *i in items) {
[dictionaryItems addObject: [i toJSONDictionary]];
}

// Dictionary with label=apple rights; this is the info of the channel.
NSDictionary *rights = [NSDictionary dictionaryWithObjectsAndKeys: infoString, @“label”, nil];

// Dictionary with label=title rights; this is the title of the channel.
NSDictionary *jTitle = [NSDictionary dictionaryWithObjectsAndKeys: title, @“label”, nil];

// Dictionary with feed including all of the above items
NSDictionary *feed = [NSDictionary dictionaryWithObjectsAndKeys:jTitle, @“title”, rights, @“rights”, dictionaryItems, @“entry”, nil];

// Finally, a dictionary entry with the feed as the only item
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithObjectsAndKeys: feed, @“feed”, nil];

NSLog(@"[[[%@]]]", jsonDictionary);

return jsonDictionary;
}
[/code]

I came up with the code by, basically, putting together dictionaries in the reverse order of parsing done by readFromJSONDictionary. Some of the dictionaries can probably be nested but I did them piece by piece to follow better what I was doing and for readability.

In RSSChannel.m, I added a method to help me convert the dictionary that stores all the objects into json data which is what is used to archive. This method takes advantage of isValidJSONObject to make sure that the dictionary is indeed a valid json object and then uses dataWithJSONObject to convert the dictionary to json data. Here’s the code:

[code]- (NSData *) dictionaryToJson
{
// Convert the channel data to a dictionary
NSDictionary *jsonDictionary = [self toJSONDictionary];

if ([NSJSONSerialization isValidJSONObject: jsonDictionary]) {

// convert the json object to NSData
NSError* error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary
                                                   options:kNilOptions
                                                     error:&error];

//NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
//NSLog(@"JSON Output: %@", jsonString);

if (!error)
  return jsonData;

}

return nil;
}
[/code]

Next, the data is archived in BNRFeedStore.m.

[code] [connection setCompletionBlock:^(RSSChannel *obj, NSError *err){
// This is the store’s completion code
// If everything went smoothly, save the channel to disk and set the cache
if (!err) {
[self setTopSongsCacheDate:[NSDate date]];
// Save the json data to the given path
[[obj dictionaryToJson] writeToFile:cachePath atomically:YES];
}

// This is the controller's completion code
block( obj,err);

}];
[/code]

Then I can read the archived data as shown below. After successfully reading the archive I can convert the json data to a dictionary using JSONObjectWithData. Finally, the dictionary is used to fill the channel’s objects using the same method as before (from the protocol); namely, readFromJSONDictionary.

[code] if (cacheAge>-300) {
// If its less than 300 seconds (5 minutes) old, return cache in completion block
NSLog( @“Reading cache …”);

  RSSChannel *cachedChannel = [[RSSChannel alloc]init];
  NSData *jsonData= [NSData dataWithContentsOfFile:cachePath];
  //NSLog(@"archived json data---%@++++", jsonData);

  if ( jsonData ) {
    // parse the JSON data into what is ultimately an NSDictionary
    NSError *parseError = nil;
    NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData: jsonData options:NSJSONReadingAllowFragments error:&parseError];
    
    if (! parseError) {
      // Parse the the dictionary into our objects
      [cachedChannel readFromJSONDictionary:jsonDictionary];
    
      // Execute the controller's completion block to reload its table
      [[NSOperationQueue mainQueue] addOperationWithBlock:^{
      block(cachedChannel, nil);
      }];
    
      // Don't need to make the request; just get out this method.
      return;
    }
  }
}

[/code]

No changes were needed anywhere else. As always, constructive criticism is welcomed! :slight_smile:


#2

Thanks a lot!


#3

Thank you,

I think Nerdfeed is an ultimate application with a lot of useful stuff tied together (WebService, caching, archiving…) and I will definitely use it as reference for my future projects. That’s why I’ve decided to refactor the code as much as possible (I put protocols method together and put pragma marks through the code, etc…) and I was wondering what will be the best approach to implement this challenge into it and I think your code is the right one. I’m using the same approach, but I simplified toJSONDictionary methods in both - RSSItem and RSSChannel. Here’s how:

In RSSItem.m

[code]- (NSDictionary *)toJSONDictionary
{
return @{
@“link”:
@[
@{@“attributes”:
@[
@{@“href”: [self link]}
]
},
@{@“attributes”:
@[
@{@“href”: [self link]}
]
},
],

            @"title":
                @[
                    @{@"label": [self title]}
                ]
        };

}[/code]

In RSSChannel.m

- (NSDictionary *)toJSONDictionary { return @{ @"feed": @{ @"title": [self title], @"entry": [self items] } }; }