6
\$\begingroup\$

A job application of mine has been declined because the test project I submitted was not coded in a clean and straightforward way.

Fine, but that's all the feedback I got. Since I like to continuously improve my coding skills, are there people here who want to check out this project at Github? It's not complicated of course and it would be really helpful for me.

The README contains the assignment specifics.

GitHub

Crux of the requirements:

Functionality

The main should define and run 3 requests SIMULTANEOUSLY, each request is defined below:

  1. 10thLetterRequest:

    Grab a website’s content from the web Hold the web page content as a String and make it accessible from the Main Process the web page content: Find the 10th letter in the web page text and report it back to the Main program via a callback.

  2. Every10thLetterRequest:

    Grab a website’s content from the web Hold the web page content as a String and make it accessible from the Main Process the web page content: Find every 10th letter(i.e: 10th, 20th, 30th etc.) in the web page text and report it back to the Main program via a callback. This callback should bring an appropriate data structure.

  3. WordCounterRequest:

    Grab a website’s content from the web Hold the web page content as a String and make it accessible from the Main Process the web page content: Split the text into words by using whitespace characters (i.e: space, linefeed etc.) and write a simple algorithm to count every word in the document and report it back to the Main program via a callback. You can disregard html/javascript etc. and treat every word equally. The callback should bring an appropriate data structure of words and counts. So the main program should be able to ask how many times a certain word appears in the website.

Core code:

//
//  dataRequest.m
//  assignment
//
//  Created by Mathijs on 2013-12-19.
//  Copyright (c) 2013 Mathijs Vreeman. All rights reserved.
//

#import "dataRequest.h"

@implementation dataRequest

@synthesize urlString, occurrencesOfWordOutput, tenthLetterOutput, everyTenthOutput;

- (void)getStringFrom:(NSURL *)url done:(void(^)())done {
    NSLog(@"URL is loaded only once");

    // get contents of the URL
    NSError *error;
    urlString = [NSString stringWithContentsOfURL:url encoding:NSASCIIStringEncoding error:&error];
    if (error) { NSLog(@"%@", error); }

    // callback that URL is loaded
    done();
}

- (void)getTenthLetterAndWhenComplete:(void(^)())completionCallback {
    NSLog(@"getTenthLetter called");

    // strip white space to make sure to return a letter
    NSString *urlStringWithoutWhiteSpace = [urlString stringByReplacingOccurrencesOfString:@" " withString:@""];
    urlStringWithoutWhiteSpace = [urlStringWithoutWhiteSpace stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    urlStringWithoutWhiteSpace = [urlStringWithoutWhiteSpace stringByReplacingOccurrencesOfString:@"\t" withString:@""];
    urlStringWithoutWhiteSpace = [urlStringWithoutWhiteSpace stringByReplacingOccurrencesOfString:@"\r" withString:@""];

    // locate tenth letter and set output
    NSString *tenthLetter = [urlStringWithoutWhiteSpace substringWithRange:NSMakeRange(10, 1)];
    tenthLetterOutput = [NSString stringWithFormat:@"the tenth letter is: %@", tenthLetter];

    // callback that work is done
    completionCallback();
}

- (void)getEveryTenthLetterAndWhenComplete:(void(^)())completionCallback {
    NSLog(@"getEveryTenthLetter called");

    //strip white space
    NSRegularExpression *regEx = [[NSRegularExpression alloc] initWithPattern:@"[a-zA-Z]" options:0 error:NULL];

    // loop to get every tenth letter and add it to an array
    NSMutableArray *everyTenth = [[NSMutableArray alloc] init];
    for (int i = 1; i < floor([urlString length]/10); i++)
    {
        NSString *letter = [urlString substringWithRange:NSMakeRange(i*10, 1)];
        NSInteger match = [regEx numberOfMatchesInString:letter options:0 range:NSMakeRange(0, [letter length])];

        if (match > 0) {
            [everyTenth addObject:letter];
        }
    }

    // create an output string from this array
    NSMutableString *stringFromEveryTenthArray = [[NSMutableString alloc] init];
    for (NSString *object in everyTenth)
    {
        [stringFromEveryTenthArray appendString:[object description]];
        [stringFromEveryTenthArray appendString:@", "];
    }
    everyTenthOutput = stringFromEveryTenthArray;

    // callback that work is done
    completionCallback();
}

- (void)getOccurrencesOfWordInTotal:(NSString *)wordToCount complete:(void(^)())completionCallback {
    NSLog(@"getOccurrencesOfWordInTotal called");

    //replace linebreaks and tabs by spaces to make the separation easier
    NSString *urlStringWithSpaces = [urlString stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
    urlStringWithSpaces = [urlStringWithSpaces stringByReplacingOccurrencesOfString:@"\t" withString:@" "];
    urlStringWithSpaces = [urlStringWithSpaces stringByReplacingOccurrencesOfString:@"\r" withString:@" "];

    // count total number of words
    NSArray *wordsInUrlString = [urlStringWithSpaces componentsSeparatedByString:@" "];
    NSInteger totalWordCount = [wordsInUrlString count];

    // count the wordToCount and generate output string
    if (![wordToCount isEqualToString:@""])
    {
        // count occurrences of specific word
        NSInteger occurrencesOfWord = [urlString length] - [[urlString stringByReplacingOccurrencesOfString:wordToCount withString:@""] length];
        occurrencesOfWord = occurrencesOfWord / [wordToCount length];
        occurrencesOfWordOutput = [NSString stringWithFormat:@"\"%@\" was found %d times in %d words", wordToCount, occurrencesOfWord, totalWordCount];
    }
    else
    {
        occurrencesOfWordOutput = [NSString stringWithFormat:@"no search word was found"];
    }

    // callback that work is done
    completionCallback();
}

@end
\$\endgroup\$
  • \$\begingroup\$ I know that these requests should run simultaneously but you should have us review portions of your code rather than the whole thing, please post a portion of one of the requests as a single review question, maybe a portion that you think looks sloppy or unclear but can't figure out how to write it more clearly. \$\endgroup\$ – Malachi Jan 9 '14 at 14:49
  • \$\begingroup\$ the point is tat I really don't know. I wrote it as clean and straightforward as I could. My guess that it's more about the whole structure. Do you see some sloppy stuff on first sight? \$\endgroup\$ – Mathijs Jan 9 '14 at 14:54
  • \$\begingroup\$ @Mathijs Oh yes.. Please add a license file in your github repository that states we can copy the code, otherwise I have to remove the code and then this question will get closed. \$\endgroup\$ – konijn Jan 9 '14 at 15:16
3
\$\begingroup\$

You don't stick to the naming conventions. A class should be named in CamelCase, not camelCase. dataRequest would be DataRequest. furthermore it would be helpful to tell bit more about the request, as you also could request the local file system for something. Some name like HTTPRequest would be better.

Also a method name should explain what it does (but not how). getStringFrom:done: would be better called fetchStringFromURL:completionHandler: or fetchFromURL:completionHandler: as it would be possible to fetch an image or other kind of data.
the done: part of the name is also problematic. if you only see the name, you won't know, if you pass in a callback (block, object, selector), or if it is a BOOL reference to tell you, if the fetching went well.
Actually you have no way to tell, if it went well. Either pass in an error object into the completion block, or have to blocks, one for success, one for failure.

fetchFromURL:successHandler:failureHandler:

Alternatively, you could call it fetchFromURL:onSuccess:onFailure: as this would clearly tell the user that an action will be fired. You should pass in the response from the fetch into both blocks (or take care that in failure there error object has it), so the programmer using it can react.


You are exposing ivars instead of properties in the headers. also names like textView1 are not really good. Synthesizing is not needed since several years (2011) now.


The asynchronous dispatching should be hidden from the view controller.


My DataRequest could look like:

@implementation DataRequest


- (void)fetchFrom:(NSURL *)url
        onSuccess:(void(^)(id response))successBlock
        onFailure:(void (^)(NSError *))failureBlock
{

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSError *error;
        NSString *urlString = [NSString stringWithContentsOfURL:url encoding:NSASCIIStringEncoding error:&error];

        dispatch_async(dispatch_get_main_queue(), ^{
            if (error) {
                failureBlock(error);
            } else {
                successBlock(urlString);
            }
        });
    });

}



-(NSArray *)_wordsFromString:(NSString *)string
{
    NSRegularExpression *regEx = [[NSRegularExpression alloc] initWithPattern:@"[a-zA-Z]+" options:0 error:NULL];

    NSArray *matches = [regEx matchesInString:string
                                      options:0
                                        range:NSMakeRange(0, [string length]-1)];
    NSMutableArray *wordArray = [@[] mutableCopy];

    [matches enumerateObjectsUsingBlock:^(NSTextCheckingResult *match, NSUInteger idx, BOOL *stop) {
        [wordArray addObject:[string substringWithRange:[match range]]];
    }];
    return wordArray;

}


-(NSString *) _flattendedString:(NSString *)string keepWords:(BOOL)keepWords
{
    return (keepWords) ? [[self _wordsFromString:string] componentsJoinedByString:@" "]
                       : [[self _wordsFromString:string] componentsJoinedByString:@""] ;
}


-(void)everyTenthLetterFromURL:(NSURL *)url
                     onSuccess:(void (^)(NSArray *))success
                     onFailure:(void (^)(NSError *))failureBlock
{
    [self fetchFrom:url
          onSuccess:^(id response) {
        if ([response isKindOfClass:[NSString class]]) {
            NSString *responseString = (NSString *)response;
            responseString = [self _flattendedString:responseString keepWords:NO];
            NSMutableArray *letterArray = [@[] mutableCopy];

            [responseString enumerateSubstringsInRange:NSMakeRange(0, [responseString length])
                                               options:(NSStringEnumerationByComposedCharacterSequences)
                                            usingBlock:^(NSString *substring,
                                                         NSRange substringRange,
                                                         NSRange enclosingRange,
                                                         BOOL *stop) {
                                                if (substringRange.location % 10 == 9) {
                                                    [letterArray addObject:substring];
                                                }
            }];
            success(letterArray);
        }
    } onFailure:^(NSError *error) {
        failureBlock(error);
    }] ;

}

-(void)tenthLetterFromURL:(NSURL *)url
                onSuccess:(void (^)(NSString * letter))success
                onFailure:(void (^)(NSError *))failureBlock
{
    [self fetchFrom:url
          onSuccess:^(id response) {
              if ([response isKindOfClass:[NSString class]]) {
                  NSString *responseString = (NSString *)response;

                  responseString = [self _flattendedString:responseString keepWords:NO];
                  if ([responseString length] > 10) {
                      success([responseString substringWithRange:NSMakeRange(9, 1)]);
                  }
              }
          } onFailure:^(NSError *error) {
              failureBlock(error);
          }] ;
}

-(void) allWordsFromURL:(NSURL *)url
              onSuccess:(void (^)(NSArray *words))success
              onFailure:(void (^)(NSError *))failureBlock
{
    [self fetchFrom:url
          onSuccess:^(id response) {
              if ([response isKindOfClass:[NSString class]]) {
                  NSString *responseString = (NSString *)response;

                  success([self _wordsFromString:responseString]);
              }
          } onFailure:^(NSError *error) {
              failureBlock(error);
          }] ;
}
@end

Note that I condensed

NSString *urlStringWithoutWhiteSpace = [urlString stringByReplacingOccurrencesOfString:@" " withString:@""];
urlStringWithoutWhiteSpace = [urlStringWithoutWhiteSpace stringByReplacingOccurrencesOfString:@"\n" withString:@""];
urlStringWithoutWhiteSpace = [urlStringWithoutWhiteSpace stringByReplacingOccurrencesOfString:@"\t" withString:@""];
urlStringWithoutWhiteSpace = [urlStringWithoutWhiteSpace stringByReplacingOccurrencesOfString:@"\r" withString:@""];

to

NSString* returnString = [[string componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]componentsJoinedByString:@""];

And I would use it in the ViewController like

@interface ViewController ()

@end

@implementation ViewController


- (IBAction)runButton:(id)sender {
    NSURL *url = [NSURL URLWithString:@"https://www.google.com/"];
    NSString *wordToCount = @"google";

    DataRequest *request = [[DataRequest alloc] init];
    [request tenthLetterFromURL:url
                        onSuccess:^(NSString* letter) {
                            [textView1 setText:letter];
                        } onFailure:^(NSError* error) {
                                    [[[UIAlertView alloc] initWithTitle:@"error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"dissmiss" otherButtonTitles: nil] show];
                        }];

    [request everyTenthLetterFromURL:url
                           onSuccess:^(NSArray * letters){
                               [textView2 setText: [letters componentsJoinedByString:@""]];
                           } onFailure:^(NSError * error) {
                               [[[UIAlertView alloc] initWithTitle:@"error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"dissmiss" otherButtonTitles: nil] show];
                           }];


   [request  allWordsFromURL:url
                onSuccess:^(NSArray *words) {
                    __block NSUInteger count =0;

                    [words enumerateObjectsUsingBlock:^(NSString *word, NSUInteger idx, BOOL *stop) {
                        if ([word compare:wordToCount options:NSCaseInsensitiveSearch]) {
                            ++count;
                        }
                    }];
                    [textView3 setText:[NSString stringWithFormat:@"\"%@\" was found %d times in %d words", wordToCount, count, [words count]]];

                } onFailure:^(NSError *error) {
                    [[[UIAlertView alloc] initWithTitle:@"error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"dissmiss" otherButtonTitles: nil] show];
                }];

@end

I want to mention that an object should do exactly one thing, but that perfectly.

So the DataRequest should only fetch data. While I also gave it the responsibilities for getting the tenth letter and such stuff, that should actually be in a separate class.

GitHub

\$\endgroup\$
6
\$\begingroup\$

I have to agree with the evaluation.

10thLetterRequest and Every10thLetterRequest are pretty much exactly the same, except that 10thLetterRequest only requires the first letter.

Yet, you decided to use different regexes and have no re-use of code.

The last assignment asks you return all words with a word count, instead of that you search for 1 word. You basically misunderstood the question completely.

Other minor things :

  • The name getTenthLetterAndWhenComplete lies, since you return a string, I would expect this to return just the 10th letter.

  • Returning a comma separate string with the tenth letters is not an appropriate data structure for getEveryTenthLetterAndWhenComplete, even worse you build up an entire array and then throw it away.

\$\endgroup\$
  • \$\begingroup\$ Another minor thing could be a big maximum line length? \$\endgroup\$ – 34730 Jan 9 '14 at 15:25
2
\$\begingroup\$

As I mentioned in my other answer, an object should have just one purpose.

Here I try to achieve that for the DataRequest class by executing a list of blocks for each network request.


#import <Foundation/Foundation.h>
typedef void(^DataResponseBlock)(id resposeObject);
typedef void(^FailurBlock)(NSError *error);

@interface DataRequest : NSObject
@property (nonatomic, strong) NSArray *operations;
@property (nonatomic, copy) FailurBlock failureBlock;
- (void)fetchFrom:(NSURL *)url;

@end

#import "DataRequest.h"

@implementation DataRequest


- (void)fetchFrom:(NSURL *)url
{
    __block NSError *err;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSString *responseString = [NSString stringWithContentsOfURL:url
                                                            encoding:NSASCIIStringEncoding
                                                               error:&err];

        if (err) {
            if (self.failureBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{

                    self.failureBlock(err);
                });
            }
        } else {
            for (DataResponseBlock obj in _operations) {
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

                    obj(responseString);
                });
            }
        }

    });

}


@end

That is the complete data request class.

We can use it like:

#import "ViewController.h"
#import "DataRequest.h"

@interface ViewController ()

@end

@implementation ViewController


- (IBAction)runButton:(id)sender {
    NSURL *url = [NSURL URLWithString:@"https://www.google.com/"];
    NSString *wordToCount = @"google";

    DataRequest *request = [[DataRequest alloc] init];

    NSMutableArray *operations = [NSMutableArray array];

    __block typeof(self) blockSelf = self;


    DataResponseBlock everyTenthLetter = ^(id response){
        if ([response isKindOfClass:[NSString class]]) {
            NSString *responseString = (NSString *)response;
            responseString = [blockSelf _flattendedString:responseString keepWords:NO];
            NSMutableArray *letterArray = [@[] mutableCopy];

            [responseString enumerateSubstringsInRange:NSMakeRange(0, [responseString length])
                                               options:(NSStringEnumerationByComposedCharacterSequences)
                                            usingBlock:^(NSString *substring,
                                                         NSRange substringRange,
                                                         NSRange enclosingRange,
                                                         BOOL *stop) {
                                                if (substringRange.location % 10 == 9) {
                                                    [letterArray addObject:substring];
                                                }
                                            }];
            dispatch_async(dispatch_get_main_queue(), ^{
                [textView2 setText:[letterArray componentsJoinedByString:@""]];
            });
        }
    };


    DataResponseBlock tenthLetter = ^(id response){
        NSString *responseString = (NSString *)response;
        responseString = [blockSelf _flattendedString:responseString keepWords:NO];
        if ([responseString length] > 10) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [textView1 setText:[responseString substringWithRange:NSMakeRange(9, 1)]];
            });
        }
    };

    DataResponseBlock occurance = ^(id response){
        NSString *responseString = (NSString *)response;
        NSArray *words = [blockSelf _wordsFromString:responseString];
        __block NSUInteger count =0;

        [words enumerateObjectsUsingBlock:^(NSString *word, NSUInteger idx, BOOL *stop) {
            if ([word compare:wordToCount options:NSCaseInsensitiveSearch]) {
                ++count;
            }
        }];

        dispatch_async(dispatch_get_main_queue(), ^{
            [textView3 setText:[NSString stringWithFormat:@"\"%@\" was found %d times in %d words", wordToCount, count, [words count]]];
        });



    };

    FailurBlock failure = ^(NSError *error){
        [[[UIAlertView alloc] initWithTitle:@"error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"dissmiss" otherButtonTitles: nil] show];

    };

    [operations addObject:[everyTenthLetter copy]];
    [operations addObject:[tenthLetter copy]];
    [operations addObject:[occurance copy]];


    request.operations = operations;
    request.failureBlock = failure;
    [request fetchFrom:url];

}


-(NSString *) _flattendedString:(NSString *)string keepWords:(BOOL)keepWords
{
    return (keepWords) ? [[self _wordsFromString:string] componentsJoinedByString:@" "]
    : [[self _wordsFromString:string] componentsJoinedByString:@""] ;
}


-(NSArray *)_wordsFromString:(NSString *)string
{
    NSRegularExpression *regEx = [[NSRegularExpression alloc] initWithPattern:@"[a-zA-Z]+" options:0 error:NULL];

    NSArray *matches = [regEx matchesInString:string
                                      options:0
                                        range:NSMakeRange(0, [string length]-1)];
    NSMutableArray *wordArray = [@[] mutableCopy];

    [matches enumerateObjectsUsingBlock:^(NSTextCheckingResult *match, NSUInteger idx, BOOL *stop) {
        [wordArray addObject:[string substringWithRange:[match range]]];
    }];
    return wordArray;

}
@end

Actually there is another improvement. And I would be not surprised, if the interviewer wanted to see that: Make the ViewController re-useable by adding a datasource.

Source code of this iteration.


The next step: Introducing a datasource to gain reusable view controllers — or at least avoid a Massive View Controller.

Traditionally in Cocoa datasources follow the same pattern as delegates. Here I want to do it a little differently.

I create a ViewController that has a datasource with a certain protocol:

@protocol DataSourceProtocol <NSObject>

-(NSArray *)operations;
-(NSURL *)url;
@end

operations will contain blocks with this signature:

typedef id(^DataResponseBlock)(id resposeObject, NSString **blockIdentifier);

This time the block will take a response object and returned a processed object. Each block can identify itself via a identifier, the ViewConroller will expect everyTenthLetter, tenthLetter and occurrence.

The DataSource:

@implementation DataSource

-(NSURL *)url
{
    return [NSURL URLWithString:@"https://www.google.com/"];
}

-(NSArray *)operations
{
    NSMutableArray *operations = [NSMutableArray array];

    __block typeof(self) blockSelf = self;


    DataResponseBlock everyTenthLetter = ^id(id response, NSString **blockIdentifier){
        if ([response isKindOfClass:[NSString class]]) {
            NSString *responseString = (NSString *)response;
            responseString = [blockSelf _flattendedString:responseString keepWords:NO];
            NSMutableArray *letterArray = [@[] mutableCopy];

            [responseString enumerateSubstringsInRange:NSMakeRange(0, [responseString length])
                                               options:(NSStringEnumerationByComposedCharacterSequences)
                                            usingBlock:^(NSString *substring,
                                                         NSRange substringRange,
                                                         NSRange enclosingRange,
                                                         BOOL *stop) {
                                                if (substringRange.location % 10 == 9) {
                                                    [letterArray addObject:substring];
                                                }
                                            }];
            *blockIdentifier = @"everyTenthLetter";
            return letterArray;
        }
        return nil;
    };


    DataResponseBlock tenthLetter = ^id(id response, NSString **blockIdentifier){
        *blockIdentifier = @"tenthLetter";

        NSString *responseString = (NSString *)response;
        responseString = [blockSelf _flattendedString:responseString keepWords:NO];
        if ([responseString length] > 10) {
                return [responseString substringWithRange:NSMakeRange(9, 1)];

        }
        return nil;
    };


    DataResponseBlock occurance = ^id (id response, NSString **blockIdentifier){
        *blockIdentifier = @"occurrence";

        NSString *responseString = (NSString *)response;
        NSArray *words = [blockSelf _wordsFromString:responseString];
        __block NSUInteger count =0;
        NSString *wordToCount = @"google";

        [words enumerateObjectsUsingBlock:^(NSString *word, NSUInteger idx, BOOL *stop) {
            if ([word compare:wordToCount options:NSCaseInsensitiveSearch]) {
                ++count;
            }
        }];

            return [NSString stringWithFormat:@"\"%@\" was found %d times in %d words", wordToCount, count, [words count]];



    };

    [operations addObject:[everyTenthLetter copy]];
    [operations addObject:[tenthLetter copy]];
    [operations addObject:[occurance copy]];

    return operations;
}
@end

The DataRequest now uses a delegate protocol to inform the ViewController about new processed data.

@protocol DataRequestDelegate <NSObject>

-(void)result:(id)result forBlockWithIdenfier:(NSString *)identifier;

@end


typedef id(^DataResponseBlock)(id resposeObject, NSString **blockIdentifier);
typedef void(^FailurBlock)(NSError *error);

@interface DataRequest : NSObject
@property (nonatomic, strong) NSArray *operations;
@property (nonatomic, copy) FailurBlock failureBlock;
@property (nonatomic, weak) id<DataRequestDelegate> delegate;
- (void)fetchFrom:(NSURL *)url;

@end

The ViewController conforms to this protocol:

-(void)result:(id)result forBlockWithIdenfier:(NSString *)identifier
{
    if ([identifier isEqualToString:@"everyTenthLetter"]) {
        [textView2 setText:[result componentsJoinedByString:@""]];
    } else if ([identifier isEqualToString:@"tenthLetter"]) {
        [textView1 setText:result];
    } else if ([identifier isEqualToString:@"occurrence"]) {
        [textView3 setText:result];
    }

}

The DataRequest looks like:

#import "DataRequest.h"

@implementation DataRequest


- (void)fetchFrom:(NSURL *)url
{
    __block NSError *err;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSString *responseString = [NSString stringWithContentsOfURL:url
                                                            encoding:NSASCIIStringEncoding
                                                               error:&err];

        if (err) {
            if (self.failureBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{

                    self.failureBlock(err);
                });
            }
        } else {
            for (DataResponseBlock obj in _operations) {
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    NSString *iden;
                    id result = obj(responseString, &iden);
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [self.delegate result:result forBlockWithIdenfier:iden];
                    });
                });
            }
        }

    });

}


@end

Now I have a easy to configure view controller— all I need to do is to write a datasource. With inheritance this could actually mean that I just need to overwrite -url (or make it a property and set it from somewhere) to deal with very different sites. The ViewController and the DataRequest won't need to be changed.

GitHub

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.