2

I am curious about 2 things:

1 whats the efficient and easy-to-scale way to design communication between object that communicates with API and viewcontrollers

2 how to design the communicating object itself (how to design methods to be scalable,..)

(My approach mention below is messy, I know, but the deadlines were crazy and until now I didn't have time to think about it really.)

Let me introduce the task I was dealing with:

I had to write 2-3 apps depending on communication with API. There was about 10-15 different methods that the API responded to (send via http POST, result in JSON). Of course the communication had to be asynchronous.

My approach:

So the object communicationg with API (apiComm for short) was shared by all the UIViewControllers. apiComm had 10-15 methods, each one for the one that API is capable to process; there was big variability between individual request contents.. => question 2

When the apiComm recieved data from API, it posted notification on [NSNotificationCenter defaultCenter]. This means, that every UIViewController that wanted to use apiComm had to register self for notifications and implement method dealing with incoming notification. This methods grew nasty as some UIViewController had to process more API requests,... => question 1

I would like to know if there is a universal pattern to use when desining these problems.. I will be greatful for any comments about any part of this problem.

janh
  • 232
  • 2
  • 9
  • Check out this blog post for creating a nice structure to handle web services. I've used it in a couple of large projects now and it has been **very** good: http://commandshift.co.uk/blog/2014/01/02/nice-web-services/ – liamnichols May 22 '14 at 08:10
  • Regarding "how to notify" - that's really a very general iOS programming decision - there are many ways! Often KVO works great; often notification centre. The most likely thing is a state machine; your app will already have a "state" singleton. Just call to it [STATE newFeedDataIsReady], one call. and then IN STATE, you can decide what to do with that, based on the app. if you have not tried KVO, experiment with it. – Fattie May 22 '14 at 09:42
  • Yes. Thank you, @liamnichols - very nice blog post and project. – janh May 23 '14 at 06:20
  • And thank you, @JoeBlow - I agree, KVO would be my choice nr 1 now, unfortunatelly I just learned about it yesterday. – janh May 23 '14 at 06:20

1 Answers1

1

For me the only real answers or direction I can give to this tricky problem is:

  • by all means use some sort of abstract class -like pattern as @liamnichols points out

  • if you are reading this new to iOS, it's absolutely essential to use the "after..." block pattern (examples in the code below)

  • on that point, here's an absolutely critical point in iOS/objective-C https://stackoverflow.com/a/20760583/294884 .. how to make a block a property

  • purely IMO, i've never found a big project where the "15 items" can, in fact, be really truly rationalised. it just has not happened yet. so the best for us it to carefully - at least - package it so that (one way or another) you call the "15 items" something like this .. CLOUD.NOTES .. CLOUD.PROFILE .. CLOUD.SCORES .. and so on from the rest of your code.

  • use a singleton(s) of course for the networking systems

  • it's critical to get with both KVO and NSNotifications for networking systems

  • it's very important to note that it is so absurdly easy to handle JSON in the iOS universe, these days, that (fortunately) having "just 15 separate files" {one way or another} is really not a bad thing and it would be easy to see it as, really, about the most definitive rationalisation you can make.

So, just some mixed thoughts. One final point - everything's just moving to parse.com so this all becomes moot, fortunately :)

It's almost a case of "I'm working with a client who hans't yet moved to a bAAs ('no, really!') so how do I keep networking code tidy ...!" Heh.


Couldn't be easier, just write a nice singleton.

Drop it in any app that needs it.

It's unbelievably easy to handle JSON in iOS these days. So, what I describe is often trivial .. little more than a few dozen lines of code.

Your "cloud" files will contain routines this simple ... this singleton would be called "BLANKS" or similar... it gets some "blank" user file types from the server, let's say.

-(void)loadIfNeededThen:(void(^)(void))after
    {
    if ( self.rawStubsFromCloud != nil )
        {
        NSLog(@"good new, blanks are already loaded!!");
        after();
        return;
        }

    [APP huddie]; // (use MBProgressHUD for all spinners)
    APP.hud.labelText = @"Loading file blanks from cloud...";

    dispatch_after_secs_on_main(0.1 ,
            ^{
            [self _refreshThen:
                ^{
                [APP.hud hide:YES];

                NSLog(@"loaded the blanks fine:\n%@\n",
                    [self supplyDisplayStyleString] );

                after();
                }];
            }
        );
    }
-(void)_refresh
    {
    [self _refreshThen:nil];
    }

#define uBlanks [NSURL URLWithString:@"http://blah.com/json/blanks"]
-(void)_refreshThen:(void(^)(void))after
    {
    dispatch_async(dispatch_get_main_queue(),
        ^{
        self.rawBlanksFromCloud = [NSData dataWithContentsOfURL:uBlanks];

        [self _doShowAllOnLog];

        after();
        });
    }

It's worth realising that, everything is moving to Parse.com and other bAAs. There's no other realistic future, there's not going to be much "server side" after the end of this year.

So in practice, these simple singletons become even simpler - they're just the few lines of code to hook up to Parse. Enjoy!

So TBC the above code sample is from an "ye olde web server" situation.

Here's an example of getting "user files" .. (note the handy routine postStringUser for assembling damned url calls .. I've never found a really neat way to do that!)...

-(NSString *)postStringUser:(NSString *)user pass:(NSString *)pass
{
NSString *username = user;
NSString *password = pass;

NSMutableString *r = [NSMutableString stringWithString:@""];

[r appendString:@"command=commandExampleGetFile"];
[r appendString:@"&"];

[r appendString:@"name=blah"];
[r appendString:@"&"];

[r appendString:@"user="];
[r appendString: [username stringByUrlEncoding] ];
[r appendString:@"&"];

[r appendString:@"password="];
[r appendString: [password stringByUrlEncoding] ];

return r;
}

#define yourUrl [NSURL URLWithString:@"http://blah.com/json/blah"]

-(void)fetchTheUsersFiles:(NSString *)user pass:(NSString *)pass then:(void(^)(void))after
{
NSString *postString = [self postStringUser:user pass:pass];
NSLog(@"postString is %@ ", postString );

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:yourUrl];
request.HTTPMethod = @"POST";
request.HTTPBody = [ postString dataUsingEncoding:NSUTF8StringEncoding];
[request addValue:@"application/x-www-form-urlencoded" forHTTPHeaderField: @"Content-Type"];

[APP huddie]; // use MBProgress everywhere and always at all times in all apps always
APP.hud.labelText = @"Connecting to the cloud...";

// 1 get the data
// 2 make it a jdic
// 3 make it an array of the "files"

[NSURLConnection
  sendAsynchronousRequest: request
  queue: [NSOperationQueue mainQueue]
  completionHandler:^(NSURLResponse *r, NSData *data, NSError *error)
    {
    [APP.hud hide:YES];
    NSLog(@"Done... %@", r);

    self.rawGetFilesFromCloud = data;

    NSError* err;
    NSDictionary* jdic = [NSJSONSerialization
      JSONObjectWithData:self.rawGetFilesFromCloud
      options:kNilOptions
      error:&err];

      //dev only
      NSLog(@"Here's the whole damned jdic, for GetFiles\n%@", jdic);

    if ( ! jdic )
      {
      [APP simpleOK:@"Wrong username or pass? Or no files found."];
      }
    else
      {
      // the user has "logged in" so something like
      STATE.currentUsername = user;
      STATE.currentPassword = pass;
      // naturally you have a STATE singleton, every app needs one

      self.rawArrayFromCloud = [jdic objectForKey:@"data"];

      NSInteger kUserFiles = self.rawArrayFromCloud.count;
      NSString *sayString = [NSString stringWithFormat:
        @"We found %lu files of yours on the damned cloud.", kUserFiles];

      /*
      and for example...
      STATE.fileZero = self.rawArrayFromCloud[0];
      or for example...
      NSDictionary *oneStubDict = self.rawArrayFromCloud[17];
      NSString *subjectName = oneStubDict[@"subjectName"];
      NSString *mainBody = oneStubDict[@"mainBody"];
      NSString *authorField = oneStubDict[@"authorField"];
      */

      [APP simpleOK: sayString
          then:^{ [STATE showFileInterface]; } ];
      }

    if (after) after();
    }];

}

Note the critical code is little more than ...

NSMutableURLRequest *request = ...

[NSURLConnection sendAsynchronousRequest: request ...

NSDictionary* jdic = [NSJSONSerialization JSONObjectWithData:result ...

NSArray *theFiles = [jdic objectForKey:@"theFiles"];

NSString *aField = theFiles[13]["coverInfo"]["aField"];

Hope it helps!

Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719
  • Thanks, @JoeBlow, for your answer. I am not sure if I understand it right. My "shared object apiComm" was a singleton. My problem wasnt constructing the POST request, but to structure the set of different requests efficiently. At the end of the app development, there was more than 10 methods like your _-(NSString *)postStringUser:(NSString *)user pass:(NSString *)pass_ (CRUD user and CRUD his events, etc) and I can't imagine doing so with a bigger app. But thank you, I like the creation of BLANKS. – janh May 23 '14 at 07:03
  • Hmm, I'm not totally sure what you're asking -- if you glance at the method "postStringUser:" ........ are you saying "You're sick of having 15 of those to deal with" .. is that the situation? – Fattie May 23 '14 at 07:14
  • I would like to upvote, but I dont have enough reputation yet.. And yes, thats the situation. I was wondering if there is some pattern that one can follow when creating app with really rich communication with API. – janh May 23 '14 at 07:56
  • Gotchya - let me think about this! I prefer to have many small code files; so I've always simply used (in the example) 15 different code files (of one form or another) for what you describe. Everything that I can possibly rationalise to one place - on a given project - I do. I have a feeling there's no decisive way to always rationalise out what you are asking - I've got a feeling it's more or less a case of "every project is different". I find every one of the "calls" (the 15 of them) tends to be utterly different in terms of what happens and what it does; who it affects the MVC, so ... – Fattie May 23 '14 at 08:01
  • .. unfortunately for our jobs, the only solution to precisely what you ask is, it pretty much comes down to one "folder" or class (whatever the form of the project is, whatever platform) and centralise those 15 methods to there in a tidy way. (It might be that you ultimately call them from other code like this .. Cloud.Files.. Cloud.Scores.. and so on 15 times; depending on the platform and language in question.) And again just factor out as much as humanly possible. Not much of an answer... – Fattie May 23 '14 at 08:03
  • Exactly as @iamnichols describes it's nice to use an interface (some sort of abstract class in whatever language). But - IMO anyway - I find the "15 items" are just too different, too all-over-the-place, to really rationalise. One small point for any beginners reading in the future, notice my first code example the "(void(^)(void))after" block handling. I'd say it's basically 100% essential to use that pattern, always, in networking code. Indeed this may help .. http://stackoverflow.com/a/20760583/294884 Sorry this is all a "I don't have a good answer" answer Jan! :) – Fattie May 23 '14 at 08:08