Anyone do the gold challenge? (Spoiler alert)


#1

I wasn’t completely sure what the authors had in mind, but here’s what I did.

(1.) Reverse the logic in the readFromJSONDictionary methods to build new createJSONDictionary methods.

(2.) Replace archiving/unarchiving operations with binary read/write operations of JSON dictionaries.

For RSSItem:


- (NSDictionary *)createJSONDictionary
{
    NSMutableDictionary *json = [[NSMutableDictionary alloc] init];
    
    // add title iVar to json
    NSDictionary *titleDict = [NSDictionary dictionaryWithObject:[self title] forKey:@"label"];
    [json setObject:titleDict forKey:@"title"];
    
    // add link iVar to json
    NSDictionary *hrefDict = [NSDictionary dictionaryWithObject:[self link] forKey:@"href"];
    NSDictionary *attributesDict = [NSDictionary dictionaryWithObject:hrefDict forKey:@"attributes"];
    NSArray *linkArray = [NSArray arrayWithObjects:@"empty", attributesDict, nil];   // see Note #1 below
    [json setObject:linkArray forKey:@"link"];
    
    return json;
}

For RSSChannel:


- (NSDictionary *)createJSONDictionary
{
    NSMutableDictionary *json = [[NSMutableDictionary alloc] init];
    NSMutableDictionary *feedDict = [[NSMutableDictionary alloc] init];
    
    // add items to json
    NSMutableArray *arrayOfEntries = [[NSMutableArray alloc] init];
    NSDictionary *entry;
    for (RSSItem *i in items) {
        entry = [i createJSONDictionary];
        [arrayOfEntries addObject:entry];
    }
    [feedDict setObject:arrayOfEntries forKey:@"entry"];
    
    // add title to json
    [feedDict setObject:[self title] forKey:@"title"];
    
    [json setObject:feedDict forKey:@"feed"];
    
    return json;
}

From the data store’s fetchTopSongs:withCompletion: method:

// to load JSON
RSSChannel *cachedChannel = [[RSSChannel alloc] init];
[cachedChannel readFromJSONDictionary:[[NSDictionary alloc] initWithContentsOfFile:cachePath]]; 

// to cache JSON
[[obj createJSONDictionary] writeToFile:cachePath atomically:YES];  // binary write operation

Note #1: you need a filler object here because the good stuff is kept at index 1


#2

Hi, I write a similar solution for the challenge:

I have created a new method for the protocol

JSONSerializable.h

Then implement it in the classes that conform to this protocol
In RSSChannel.m

[code]-(NSDictionary *)writeToJSONDictionary
{
//we create a dictionary with the same structure that the one we get from the web service
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:
[[NSMutableDictionary alloc] initWithObjectsAndKeys:
[[NSDictionary alloc] initWithObjectsAndKeys:
[self title],
@“label”,
nil],
@“title”,
nil],
@“feed”,
nil];

NSMutableArray *arrayItems = [[NSMutableArray alloc] init];
for(RSSItem *it in items){
    NSDictionary *itemDict = [it writeToJSONDictionary];
    [arrayItems addObject];
}

//save the entry array in the dictionary
[[dict objectForKey:@"feed"] setObject:arrayItems forKey:@"entry"];
return dict;

}[/code]

and in RSSItem.m

[code]-(NSDictionary *)writeToJSONDictionary
{

//we create a dictionary with the same structure that the one we get from the web service
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:
                      [[NSDictionary alloc] initWithObjectsAndKeys:
                       [self title],
                       @"label",
                       nil],
                      @"title",
                      [NSArray arrayWithObject:[[NSDictionary alloc] initWithObjectsAndKeys:
                                                [[NSDictionary alloc] initWithObjectsAndKeys:
                                                 [self link],
                                                 @"href",
                                                 nil],
                                                @"attributes",
                                                nil]],
                      @"link",
                      nil];

return dict;

}[/code]

Finally just change the form of retrieving data from and saving to the cache, just like zzzzzzzzzzz
So in -(void)fetchTopSongs:(int)count withCompletion:(void (^)(RSSChannel *obj, NSError *err))block from BNRFeedStore.m I have

[code]-(void)fetchTopSongs:(int)count withCompletion:(void (^)(RSSChannel *obj, NSError *err))block
{

//NSLog(@"Stored date: %@", [self topSongsCacheDate]);

//make the path for saving cache
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
cachePath = [cachePath stringByAppendingPathComponent:@"apple.archive"];

//checking for the date of the cache, if exist
NSDate *tscDate = [self topSongsCacheDate];
if(tscDate){
    //how old is the cache?
    NSTimeInterval cacheAge = [tscDate timeIntervalSinceNow];
    
    if(cacheAge > -300.0){//-300
        //if it is older than 5 minutes, here we go
        //NSLog(@"Reading caché!");
        
        //we try to get the data from the cache file
        NSData *data = [NSData dataWithContentsOfFile:cachePath];
        RSSChannel *cachedChannel;
        
        //if the file is not created yet, data = nil, so don't do anything with the cache
        if(data){
            NSDictionary *cachedDictionary = [NSJSONSerialization JSONObjectWithData:data
                                                                             options:0
                                                                               error:nil];
            cachedChannel = [[RSSChannel alloc] init];
            [cachedChannel readFromJSONDictionary:cachedDictionary];
        }else{
            cachedChannel = nil;
        }
        
        if(cachedChannel){
            //controller block, that way the block is invoked after fetchEntries finish its task
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                block(cachedChannel, nil);
            }];
            
            //there is nothing to do, because there is no cache
            return;
        }
    }
}

//prepare the url
NSString *requestString = [NSString stringWithFormat:@"http://itunes.apple.com/us/rss/topsongs/limit=%d/json", count];

NSURL *url = [NSURL URLWithString:requestString];

//setting the connexion
NSURLRequest *request = [NSURLRequest requestWithURL:url];

RSSChannel *channel = [[RSSChannel alloc] init];

XLConnection *connection = [[XLConnection alloc] initWithRequest:request];

[connection setCompletionBlock:^(RSSChannel *obj, NSError *err){
    
    //block of the store
    //if everything goes fine, save the data into the caché in json format, and topSongsCacheDate in User preferences
    if(! err){
        [self setTopSongsCacheDate:[NSDate date]];
        
        NSDictionary *dict = [obj writeToJSONDictionary];
        
        //just for debugging, the format is OK
        //NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];
        //NSLog(@"%@", [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]);
        
        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:kNilOptions error:nil];
        
        //saving the file in disk
        [jsonData writeToFile:cachePath atomically:YES];
        
        
    }
    
    //ejecutamos el bloque que viene del controlador
    block(obj, err);
}];

[connection setJsonRootObject:channel];

[connection start];

}[/code]

I think that is what the author proposed in the challenge, and that is working fine, but any advice would be apreciated.


#3

@ zzzzzzzzzzz & xiledo

Thanks guy, this is just brilliant !
Your posts helped me pass a few hurdles and better understand how to structure the JSON format.

Fred


#4

Yeap. See my post. Somewhat similar to what others did. Perhaps my only contribution is the use of isValidJSONObject. :slight_smile: