问题
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.
回答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!
来源:https://stackoverflow.com/questions/23801085/ios-communication-between-api-object-and-controllers