Once and For All — Data Source Adapter Pattern

In iOS and Mac development a plague is spreading around the world.
Many view controller become so heavy and obese that we must expect our devices to suffer from Type-II Diabetes. This disease is super-contangeous and it's ground zero is well known: developer.apple.com

What are the symptoms of this syndrome?

Among others others they are the usual suspects when it come to obisity:

  • hard to move
  • unflexibel
  • hard to breed
  • no sex appeal

Where and how did the current outbreak start?

It is not known by now where the gems that transport this disease came to existence, but once they reached developer.apple.com, it broke loose.
Since than it is spreading through the main transport infrastructure like Apple's sample codes, mailinglists, stackoverflow.com, Books,…

How do I know my view controllers are infected?

Simple answer: If you are asking this ion they probably are already.
Infected view controllers are showing symptoms known from ADHD: They want to do everything at the same time and are failing in focussing on one thing — controlling the view.

If you find your view controller implementing a bunch of protocols, it is certain: it is infected

The Cure

There are quite a few possible cures, excotic ones like Intentions (possible by-effect: Massive Storyboards), moving MVC to MVVM, …

The cure that I want to present here is so simple that I classify it as home remedy

Data Source Adapter Pattern

Following the Single Responsibility Principle a view controller should only do one and exactly one thing: Controlling one view (that actually might be formed by a view hierachy). It should not do any of the following: perform network request, parse data, fetch from disk, perform computation as redering and resizing images,…

But how can we achieve this? Simply by applying our knowledge form CS101 — patterns!

If it is the single responsibility of a viewcontroller to controll the views, than it should be single responsibility of a data source should be to provide the data that is about to be displayed — but not to fetch that data from somewhere. That would be responsibility of a data fetcher object.

So by now we identified three classes we need:

  • View Controller for handling views
  • Data Source to provide the view controller with data to display
  • Data Fetcher to get/render/calculate that data

By implementing the adapter pattern for Source and fetcher we are gaining simple to use and re-use classes.

Demo

I want to demonstrate a simple UITableView that has a twist: every section gets it objetcs to display from another source.

#import <Foundation/Foundation.h>

@protocol OFADataFetcher <NSObject>

- (void)fetchSuccess:(void (^)(void))success;
- (void)fetchedData:(id)obj onDataFetcher:(id<OFADataFetcher>)dataFetcher;
- (void)fetchingDataFaildWithError:(NSError *)error onDataFetcher:(id<OFADataFetcher>)dataFetcher;
- (NSArray *)objects;
@end

The OFADataFetcher protocol describes the contract a data fetcher object must fulfill to be usable by the data source.

- (void)fetchSuccess:(void (^)(void))success;

This method is called by the data source. The block is a call back mechanism. We could use dlegation instead.

- (void)fetchedData:(id)obj onDataFetcher:(id<OFADataFetcher>)dataFetcher;

Called in case of successful fetching.

- (void)fetchingDataFaildWithError:(NSError *)error onDataFetcher:(id<OFADataFetcher>)dataFetcher;

called if fetching failed.

As I said, I want to have different sources for the different sections of the table view, but it can have only one data source. We have to have a tree of sources.

#import <UIKit/UIKit.h>
#import "OFASectionDataSource.h"
@protocol OFASectionDataSource;

@interface OFASectionedTableViewDataSource : NSObject <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, weak, readonly) UITableView *tableView;
@property(nonatomic, strong, readonly) NSArray    *sectionDataSources;


- (instancetype)initWithTableView:(UITableView *)tableView;
- (void)addSectionDataSource:(id<OFASectionDataSource>)sectionDataSource;
- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath;

- (NSArray *)selectedObjects;
@end

- (void)addSectionDataSource:(id<OFASectionDataSource>)sectionDataSource;

allows us to add a data source that fulfills OFASectionDataSource for every section.

#import "OFASectionedTableViewDataSource.h"

@interface OFASectionedTableViewDataSource ()
@property (nonatomic, weak) UITableView     *tableView;
@property(nonatomic, strong) NSMutableArray *sectionDataSources;

@end

@implementation OFASectionedTableViewDataSource

- (instancetype)initWithTableView:(UITableView *)tableView
{
  self = [super init];
  if (self) {
    _tableView            = tableView;
    _tableView.dataSource = self;
    _tableView.delegate   = self;
    _sectionDataSources   = [@[] mutableCopy];
  }
  return self;
}

- (void)addSectionDataSource:(id<OFASectionDataSource>)sectionDataSource
{
  [(NSMutableArray *)self.sectionDataSources addObject:sectionDataSource];
}

- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath
{
  id<OFASectionDataSource> sectionDataSource = self.sectionDataSources[indexPath.section];
  if ([sectionDataSource respondsToSelector:@selector(selectRowAtIndexPath:)]) {
    [sectionDataSource selectRowAtIndexPath:indexPath];
  }
}

- (NSArray *)selectedObjects
{
  NSMutableArray *array = [@[] mutableCopy];
  [self.sectionDataSources enumerateObjectsUsingBlock:^(id < OFASectionDataSource > ds, NSUInteger idx, BOOL *stop) {
    if ([ds respondsToSelector:@selector(selectedObjects)])
    [array addObjectsFromArray:[ds selectedObjects]];
    }];
    return array;
  }

  - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  {
    return self.sectionDataSources.count;
  }

  - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  {
    id<OFASectionDataSource> sectionDataSource = self.sectionDataSources[section];
    return [sectionDataSource tableView:tableView numberOfRowsInSection:section];
  }

  - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
  {
    id<OFASectionDataSource> sectionDataSource = self.sectionDataSources[section];
    return [sectionDataSource tableView:tableView titleForHeaderInSection:section];
  }

  - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
  {
    id<OFASectionDataSource> sectionDataSource = self.sectionDataSources[section];
    return [sectionDataSource tableView:tableView titleForFooterInSection:section];
  }



  - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  {
    id<OFASectionDataSource> sectionDataSource = self.sectionDataSources[indexPath.section];
    UITableViewCell       *cell             = [sectionDataSource tableView:tableView cellForRowAtIndexPath:indexPath];
    cell.editing = NO;
    return cell;
  }

  -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  {
    id<OFASectionDataSource> sectionDataSource = self.sectionDataSources[indexPath.section];
    if ([sectionDataSource respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)]) {
      return [sectionDataSource tableView:tableView heightForRowAtIndexPath:indexPath];
    }
    return 44.0;
  }

  -(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
  {
    id<OFASectionDataSource> sectionDataSource = self.sectionDataSources[indexPath.section];
    if ([sectionDataSource respondsToSelector:@selector(tableView:willDisplayCell:forRowAtIndexPath:)]) {
      [sectionDataSource tableView:tableView willDisplayCell:cell forRowAtIndexPath:indexPath];
    }
  }

  - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
  {
    NSMutableArray *sectionTitles = [@[] mutableCopy];
    [self.sectionDataSources enumerateObjectsUsingBlock:^(id < OFASectionDataSource > obj, NSUInteger idx, BOOL *stop) {
      if ([obj sectionIndexTitle])
      [sectionTitles addObject:[obj sectionIndexTitle](idx)];
      }];
      return sectionTitles;
    }

    - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
    {
      id<OFASectionDataSource> sectionDataSource = self.sectionDataSources[indexPath.section];
      if ([sectionDataSource respondsToSelector:@selector(tableView:canMoveRowAtIndexPath:)]) {
        return [sectionDataSource tableView:tableView canMoveRowAtIndexPath:indexPath];
      }
      return NO;
    }

    - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
    {
      id<OFASectionDataSource> sectionDataSource = self.sectionDataSources[sourceIndexPath.section];
      if ([sectionDataSource respondsToSelector:@selector(tableView:moveRowAtIndexPath:toIndexPath:)]) {
        [sectionDataSource tableView:tableView moveRowAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath];
      }
    }

    - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
    {
      id<OFASectionDataSource> sectionDataSource = self.sectionDataSources[indexPath.section];
      if ([sectionDataSource respondsToSelector:@selector(tableView:canEditRowAtIndexPath:)]) {
        return [sectionDataSource tableView:tableView canEditRowAtIndexPath:indexPath];
      }
      return NO;
    }

    - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
    editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
    {
      id<OFASectionDataSource> sectionDataSource = self.sectionDataSources[indexPath.section];
      if ([sectionDataSource respondsToSelector:@selector(tableView:editingStyleForRowAtIndexPath:)]) {
        return [sectionDataSource tableView:tableView editingStyleForRowAtIndexPath:indexPath];
      }
      return UITableViewCellEditingStyleNone;
    }

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
      [self selectRowAtIndexPath:indexPath];
      id<OFASectionDataSource> ds = self.sectionDataSources[indexPath.section];

      if (ds.didSelect) {
        ds.didSelect([ds sectionObjects][indexPath.row],ds, indexPath, self.tableView);
      }
    }

    - (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
    {
      [self selectRowAtIndexPath:indexPath];
      id<OFASectionDataSource> ds = self.sectionDataSources[indexPath.section];
      if (ds.didSelect) {
        ds.didSelect([ds sectionObjects][indexPath.row],ds, indexPath, self.tableView);
      }
    }

    - (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath
    {
      return NO;
    }

    - (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
    {
      id<OFASectionDataSource> ds = self.sectionDataSources[sourceIndexPath.section];
      if ([ds respondsToSelector:@selector(tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:)]) {
        return [ds tableView:tableView targetIndexPathForMoveFromRowAtIndexPath:sourceIndexPath toProposedIndexPath:proposedDestinationIndexPath];
      }
      return sourceIndexPath;

    }

    @end

So it basically proxies any method call to a section data source chosen by the indexPath's section.

A section data source implements the OFASectionDataSource protocol or might subclass the OFASectionDataSource class.

@protocol OFASectionDataSource <UITableViewDataSource, UITableViewDelegate>
- (void)setObjectsFromArray:(NSArray *)array;
- (void)addObjectsFromArray:(NSArray *)array;
@property (nonatomic, strong, readonly) NSArray   *sectionObjects;


@optional
@property (nonatomic, copy) NSString * (^sectionHeaderTitle)(NSUInteger section);
@property (nonatomic, copy) NSString * (^sectionFooterTitle)(NSUInteger section);
@property (nonatomic, copy) NSString * (^sectionIndexTitle)(NSUInteger section);
@property (nonatomic, copy) CGFloat (^cellHeight)(NSIndexPath *indexPath, id obj);
@property (nonatomic,copy) void (^didSelect)(id object,id<OFASectionDataSource> dataSource, NSIndexPath *indexPath, UITableView *tableView);

- (id<OFADataFetcher>)                               dataFetcher;
- (NSString *)                                    cellIdentifier;
- (void (^)(id, UITableViewCell *, NSIndexPath *, UITableView*))cellConfigurator;
- (Class)                                         cellClass;

- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath;
- (NSArray *)selectedObjects;
@end


@interface OFASectionDataSource : NSObject <OFASectionDataSource>
- (instancetype)initWithTableView:(UITableView *)tableView
cellClass:(Class)cellClass
cellIdentifier:(NSString *)cellIdentifier
dataFetcher:(id<OFADataFetcher>)dataFetcher
configureCell:(void (^)(id obj, UITableViewCell *cell, NSIndexPath *indexPath, UITableView *tableView))configureCell;

@property (nonatomic, weak, readonly) UITableView *tableView;

@end
#import "OFASectionDataSource.h"
#import "OFADataFetcher.h"

@interface OFASectionDataSource ()
@property (nonatomic, weak) UITableView *tableView;
@property (nonatomic, copy) NSString    *cellIdentifier;
@property (nonatomic, strong) Class     cellClass;
@property (nonatomic, copy) void (^configureCell)(id obj, UITableViewCell *cell, NSIndexPath *indexPath, UITableView *tableView);
@property (nonatomic, strong) id<OFADataFetcher>datafetcher;
@end

@implementation OFASectionDataSource
@synthesize sectionHeaderTitle;
@synthesize sectionFooterTitle;
@synthesize sectionIndexTitle;
@synthesize cellHeight;
@synthesize didSelect;
@synthesize sectionObjects = _sectionObjects;

- (instancetype)initWithTableView:(UITableView *)tableView
cellClass:(Class)cellClass
cellIdentifier:(NSString *)cellIdentifier
dataFetcher:(id<OFADataFetcher>)dataFetcher
configureCell:(void (^)(id obj, UITableViewCell *cell, NSIndexPath *indexPath, UITableView *tableView))configureCell;

{
  self = [super init];
  if (self) {
    _tableView      = tableView;
    _cellIdentifier = cellIdentifier;
    _cellClass      = cellClass;
    [tableView registerClass:cellClass forCellReuseIdentifier:cellIdentifier];
    _datafetcher = dataFetcher;

    __weak typeof(self) weakSelf = self;
    [dataFetcher fetchSuccess:^{
      dispatch_async(dispatch_get_main_queue(), ^{
        typeof(self) strongSelf = weakSelf;
        if (strongSelf) {
          [strongSelf setObjectsFromArray:[strongSelf.datafetcher objects]];
          [strongSelf.tableView reloadData];

        }
        });
        }];

        _configureCell  = configureCell;
        _sectionObjects = @[];
      }
      return self;
    }


    - (void (^)(id, UITableViewCell *, NSIndexPath *, UITableView*))cellConfigurator
    {
      return self.configureCell;
    }

    - (void)setObjectsFromArray:(NSArray *)array
    {
      self.sectionObjects = array;
    }

    - (void)addObjectsFromArray:(NSArray *)array
    {
      self.sectionObjects = [self.sectionObjects arrayByAddingObjectsFromArray:array];
    }

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
      return 0;
    }

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
      return self.sectionObjects.count;
    }

    -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
      if (self.cellHeight) {
        return self.cellHeight(indexPath, self.sectionObjects[indexPath.row]);
      }
      return 44.0;
    }

    - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
    {
      if (self.sectionHeaderTitle)
          return self.sectionHeaderTitle(section);
      return nil;
    }

    - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
    {
      if (self.sectionFooterTitle)
          return self.sectionFooterTitle(section);
      return nil;
    }

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
      UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier forIndexPath:indexPath];
      return cell;
    }

    -(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
    {
      [self cellConfigurator](self.sectionObjects[indexPath.row], cell, indexPath, tableView);
    }


    - (void)setSectionObjects:(NSArray *)sectionObjects
    {
      _sectionObjects = sectionObjects;
      [self.tableView reloadData];
    }

    - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
    {
      return UITableViewCellEditingStyleNone;
    }

    @end

From an architectual point of view we are done.

But the fun justs starts:

Let's say we want to have a section where I can select 1 to 4 rows.

#import "OFASectionDataSource.h"

@interface OFAMinMaxSelectionSectionDataSource : OFASectionDataSource

@property (nonatomic, assign, readonly) NSUInteger  min;
@property (nonatomic, assign, readonly) NSUInteger  max;


- (instancetype)initWithTableView:(UITableView *)tableView
                        cellClass:(Class)cellClass
                   cellIdentifier:(NSString *)cellIdentifier
                 minimumSelection:(NSUInteger)min
                 maximumSelection:(NSUInteger)max
                      dataFetcher:(id<OFADataFetcher>)dataFetcher
                    configureCell:(void (^)(id object, UITableViewCell *cell, NSIndexPath *indexPath, UITableView *tableView))configureCell;
@end
#import "OFAMinMaxSelectionSectionDataSource.h"

@interface OFAMinMaxSelectionSectionDataSource ()
@property (nonatomic, assign) NSUInteger          min;
@property (nonatomic, assign) NSUInteger          max;
@property (nonatomic, strong) NSMutableOrderedSet *selectedIndexSets;
@end

@implementation OFAMinMaxSelectionSectionDataSource
- (instancetype)initWithTableView:(UITableView *)tableView
cellClass:(Class)cellClass
cellIdentifier:(NSString *)cellIdentifier
minimumSelection:(NSUInteger)min
maximumSelection:(NSUInteger)max
dataFetcher:(id<OFADataFetcher>)dataFetcher
configureCell:(void (^)(id object, UITableViewCell *cell, NSIndexPath *indexPath, UITableView *tableView))configureCell
{
  self = [super initWithTableView:tableView cellClass:cellClass cellIdentifier:cellIdentifier dataFetcher:dataFetcher configureCell:configureCell];
  if (self) {
    _min               = min;
    _max               = max;
    _selectedIndexSets = [[NSMutableOrderedSet alloc] init];
    tableView.allowsSelectionDuringEditing = YES;
  }
  return self;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
  cell.editing        = NO;
  cell.selectionStyle = UITableViewCellSeparatorStyleNone;
  return cell;
}

- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath
{
  UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
  if ([self.selectedIndexSets containsObject:indexPath] && [self.selectedIndexSets count] > self.min) {
    [self.selectedIndexSets removeObject:indexPath];
    cell.accessoryType = UITableViewCellAccessoryNone;
    } else {
      [self.selectedIndexSets addObject:indexPath];
      cell.accessoryType = UITableViewCellAccessoryCheckmark;
      if ([self.selectedIndexSets count] > self.max) {
        NSIndexPath *firstIndexPath = [self.selectedIndexSets firstObject];
        cell               = [self.tableView cellForRowAtIndexPath:firstIndexPath];
        cell.accessoryType = UITableViewCellAccessoryNone;
        [self.selectedIndexSets removeObject:firstIndexPath];
        [self.tableView deselectRowAtIndexPath:firstIndexPath animated:NO];
      }
    }
    [self.tableView deselectRowAtIndexPath:indexPath animated:NO];

  }

  - (NSArray *)selectedObjects
  {
    NSMutableIndexSet *indexSets = [NSMutableIndexSet indexSet];
    [self.selectedIndexSets enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
      [indexSets addIndex:indexPath.row];
      }];

      return [self.sectionObjects objectsAtIndexes:indexSets];
    }

    @end

And we want the data source to fetch a file from the file system and break up it's lines

#import <Foundation/Foundation.h>
@import OFADelegateDataSource;

@interface FileDataFetcher : NSObject <OFADataFetcher>
@property (nonatomic, strong, readonly) NSArray *objects;

-(instancetype)initWithFilePath:(NSString *)path;
@end
#import "FileDataFetcher.h"


@interface FileDataFetcher ()
@property (nonatomic, strong, readwrite) NSArray *objects;
@property (nonatomic, copy) NSString *path;
@property (nonatomic, copy) void(^success)(void);
@end


@implementation FileDataFetcher

- (instancetype)initWithFilePath:(NSString *)path
{
  self = [super init];
  if (self) {
    _path = path;
  }
  return self;
}

-(void)fetchSuccess:(void (^)(void))success
{
  self.success = success;
  NSError *error;
  NSString *string = [NSString stringWithContentsOfFile:self.path encoding:NSUTF8StringEncoding error:&error];
  if (!string) {
    [self fetchingDataFaildWithError:error onDataFetcher:self];
    } else {
      [self fetchedData:[string componentsSeparatedByString:@"\n"]
      onDataFetcher:self];
      self.success();
    }
  }

  -(void)fetchedData:(id)obj onDataFetcher:(id<OFADataFetcher>)dataFetcher
  {
    self.objects = (NSArray *)obj;
  }

  -(void)fetchingDataFaildWithError:(NSError *)error onDataFetcher:(id<OFADataFetcher>)dataFetcher
  {
    NSLog(@"%@", [error  localizedDescription]);
  }

  @end

And now we stick it together. Note that the data source also implements the delegate, but isnt it the responibility of the VC to manage the cells? Yes it is. Just for simplicity I let my data source act a proxy so that I can send back row's index path and the corresponding object the VC by using a block

In VC's viewDidLoad:

FileDataFetcher *lineDataFetcher = [[FileDataFetcher alloc] initWithFilePath:[[NSBundle mainBundle] pathForResource:@"File2" ofType:@"txt"]];


UIFont *font = [UIFont systemFontOfSize:20];
OFASectionDataSource *lineDataSource = [[OFAMinMaxSelectionSectionDataSource alloc] initWithTableView:self.tableView
                                                                                            cellClass:[UITableViewCell class]
                                                                                      cellIdentifier:@"line"
                                                                                    minimumSelection:1
                                                                                    maximumSelection:2
                                                                                         dataFetcher:lineDataFetcher
                                                                                       configureCell:
^(NSString *object, UITableViewCell *cell, NSIndexPath *indexPath, UITableView *tableView)
{
  cell.textLabel.text = object;
  cell.textLabel.font= font;
  cell.textLabel.numberOfLines = 0;
  cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
  [cell.textLabel sizeToFit];
  }];


  self.dataSource = ({
    OFASectionedTableViewDataSource *ds= [[OFASectionedTableViewDataSource alloc] initWithTableView:self.tableView];
    [ds addSectionDataSource:lineDataSource];
    ds;
  });

That's all.

How about exchanging the text with photos form flickr?

#import "FlickerPhotoFetcher.h"
#import "FlickrKit.h"

#import "flickerCredentials.h"

@interface FlickerPhotoFetcher ()
@property (nonatomic, strong) NSMutableArray *photos;
@property (nonatomic, copy) void (^success)(void);
@end

@implementation FlickerPhotoFetcher

+(void)initialize
{
  [[FlickrKit sharedFlickrKit] initializeWithAPIKey:FLICKR_KEY
  sharedSecret:FLICKR_SECRET];
}

- (instancetype)init
{
  self = [super init];
  if (self) {
    self.photos = [@[] mutableCopy];
  }
  return self;
}

-(NSArray *)objects
{
  @synchronized(self.photos){
    return [self.photos copy];
  }
}


-(void)fetchSuccess:(void (^)(void))success
{
  self.success = success;
  FlickrKit *fk = [FlickrKit sharedFlickrKit];
  FKFlickrInterestingnessGetList *interesting = [[FKFlickrInterestingnessGetList alloc] init];
  [fk call:interesting completion:^(NSDictionary *response, NSError *error) {
    // Note this is not the main thread!
    if (response) {
      NSMutableArray *photoURLs = [NSMutableArray array];
      for (NSDictionary *photoData in [response valueForKeyPath:@"photos.photo"]) {
        NSURL *url = [fk photoURLForSize: [[UIScreen mainScreen] scale] > 1 ? FKPhotoSizeMedium640 : FKPhotoSizeSmall320 fromPhotoDictionary:photoData];
        [photoURLs addObject:url];
      }
      [self fetchPhotos:photoURLs];
    }
  }];

}

-(void)fetchingDataFaildWithError:(NSError *)error onDataFetcher:(id<OFADataFetcher>)dataFetcher
{

}

-(void)fetchedData:(id)obj onDataFetcher:(id<OFADataFetcher>)dataFetcher
{
  dispatch_async(dispatch_get_main_queue(), ^{
    self.success();
  });
}

-(void)fetchPhotos:(NSArray *)photpURLs
{
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  __weak typeof(self) weakSelf = self;

  [[photpURLs subarrayWithRange:NSMakeRange(0, 10)] enumerateObjectsUsingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
    [queue addOperationWithBlock: ^ {
      typeof(weakSelf) strongSelf = weakSelf;
      if(strongSelf){
        NSError *error = nil;
        NSData *data = [NSData dataWithContentsOfURL:url options:0 error:&error];
        UIImage *image = nil;
        if (data){
          image = [UIImage imageWithData:data];
        }
        if (image) {
          @synchronized(self.photos){
            [self.photos addObject:image];
            [self fetchedData:self.photos onDataFetcher:self];
          }
        }

      }
    }];
  }];

}

@end
OFASectionDataSource *flickrSectionDataSource = [[OFAMinMaxSelectionSectionDataSource alloc] initWithTableView:self.tableView
                                                                                                     cellClass:[UITableViewCell class]
                                                                                                cellIdentifier:@"thirdSectionCell"
                                                                                              minimumSelection:1
                                                                                              maximumSelection:2
                                                                                                   dataFetcher:flickrPhotoFetcher
                                                                                                 configureCell:^(UIImage *image, UITableViewCell *cell, NSIndexPath *indexPath, UITableView *tableView){
  cell.backgroundView  = ({
    UIImageView *iv = [[UIImageView alloc] initWithFrame:cell.bounds];
    iv.image =image;
    iv.contentMode = UIViewContentModeScaleAspectFill;
    iv.center = cell.center;
    iv;
  });
  cell.clipsToBounds = YES;
}];
[flickrSectionDataSource setSectionHeaderTitle:^NSString *(NSUInteger section) {
  return @"Flickr";
}];

flickrSectionDataSource.cellHeight = ^(NSIndexPath *ip, UIImage *obj){
  CGFloat factor = obj.size.width / self.view.frame.size.width;
  return obj.size.height / factor;
};