Remote fetch data inside model object in c object using AFNetworking
Throughout my iOS application I use this approach to respect MVC, I want to make sure my implementation is correct and respect the best practices and MVC design pattern:
AFNetworking singleton acting as API for network calls:
MyAPI.h:
#import "AFHTTPSessionManager.h"
#import "AFNetworking.h"
@interface MyAPI : AFHTTPSessionManager
+(MyAPI *)sharedInstance;
@end
MyAPI.m:
#pragma mark - Singleton
+(MyAPI*)sharedInstance
{
static MyAPI *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[MyAPI alloc] initWithBaseURL:[NSURL URLWithString:kROOT_URL]];
});
return sharedInstance;
}
Model User that uses a singleton to fecth user data (is it okay how is the implementation?):
user.h
@interface User : NSObject
@property (strong,nonatomic) NSString *userId;
@property (strong,nonatomic) NSString *email;
@property (strong,nonatomic) NSString *password;
-(id) initWithDictionary: (NSDictionary *) dictionay;
+(BOOL) isConnected;
+(void) disconnect;
+(NSString *) idOfConnectedUser;
+(User *) connectedUser;
+(void) loginWith : (NSString *) email andPassword :(NSString *) password complete:(void(^)(id result, NSError *error))block;
+(void) searchUsersFrom : (NSString *) countryCode withName :(NSString *) name andLevel:(NSString *) levelCode complete: (void(^)(id result, NSError *error)) block;
+(void) signup:(void(^)(id result, NSError *error)) block;
+(void) getUserFriends:(void(^)(id result, NSError *error)) block;
@end
User.m
[......]
+(void) loginWith : (NSString *) email andPassword :(NSString *) password complete: (void(^)(id result, NSError *error)) block
{
__block NSString * result ;
NSDictionary *params = @{@"email": email, @"password": password};
[[MyAPI sharedInstance] POST:@"auth/" parameters:params success:^(NSURLSessionDataTask *task, id responseObject)
{
if([responseObject objectForKey:@"id"])
{
[[NSUserDefaults standardUserDefaults] setObject:(NSDictionary*) responseObject forKey:USER_KEY];
[[NSUserDefaults standardUserDefaults] synchronize];
result = [responseObject objectForKey:@"id"];
}
else
{
result = nil ;
}
if (block) block(result, nil);
} failure:^(NSURLSessionDataTask *task, NSError *error)
{
if (block) block(nil, error);
}];
}
[.....]
LoginController.m:
-(void)loginButtonAction:(UIButton *)sender
{
[......]
[ User loginWith:text andPassword:text complete:^(id result, NSError *error)
{
if (result)
{
[APPDELEGATE start];
}
else
{
// ERROR
}
}];
}
Is this the way my implementation is MCV specific and following best practices and how can I improve it if not?
source to share
Singletons . You might want to avoid using singlets, this will help you design your APIs and make your code more testable. Also, in case User
, imagine you want to support a changing user (logout / guest user / etc). With the current approach, you will be limited to submitting NSNotification
, because everyone who uses connectedUser
it cannot know that the base link has been changed.
ActiveRecord : What you did with a model User
capable of network communication is somewhat similar to an active recording approach , which may not scale as well as modeling becomes more complex and the number of actions it can perform increases. Consider splitting these into a pure model and services that actually do the networking (or whatever else you might need in the future).
Model serialization : Consider encapsulating logic for serializing model and network serialization into a separate class (for example LoginResponse
, which points to a framework among other things User
) such as Mantle make this much easier.
MVC : From my experience with iOS, MVC may not be the best approach for all but simple applications. With MVC, the trend is to put all the logic in yours ViewController
, making it very large and difficult to maintain. Consider other patterns like MVVM
In general I understand that it is difficult to master all new technologies right away, but you can definitely start by saying that each class does only one thing and only one thing: the model is not online or stalling on disk, the API Client does not deserialize each response or saves data to NSUserDefaults
, the view controller does nothing but listen for user events (button presses, etc.). That alone will make your code much easier to reason and follow if a new developer is introduced to your codebase.
Hope it helps!
source to share
I can't say anything about your MVC (Model-view-controller) correctly?
I just want to add something useful to avoid unwanted crashes.
First is
[[MyAPI sharedInstance] POST:@"auth/" parameters:params success:^(NSURLSessionDataTask *task, id responseObject)
{
if([responseObject objectForKey:@"id"])
{
[[NSUserDefaults standardUserDefaults] setObject:(NSDictionary*) responseObject forKey:USER_KEY];
[[NSUserDefaults standardUserDefaults] synchronize];
result = [responseObject objectForKey:@"id"];
}
else
{
result = nil ;
}
}];
there are always so many reasons to mention what reponseObject
could be nil
, and therefore the object does not have a key @"id"
and will lead to an error (crash in the worst case). I have this code, I don't know if it can be considered best practice, but here it is:
if ([responseObject isKindOfClass:[NSArray class]])
{
NSLog(@"Log: Response is of class NSArray");
}
else if ([responseObject isKindOfClass:[NSDictionary class]])
{
NSLog(@"Log: Response is of class NSDictionary");
}
else
{
NSLog(@"Log: Kind of class is not supported");
}
This example restricts the class of another class, especially [NSNull class]
Second in line:
NSDictionary *params = @{@"email": email, @"password": password};
after checking email
and password
before assigning NSDictionary
, setting nil to NSDictionary will fail.
Third line:
if (block) block(result, nil);
block
returns void
from your implementation. It works? Sorry for the question, I haven't tried if-statement
with a block like this ..
complete: (void(^)(id result, NSError *error)) block
void
is this the return value of your block, or am I wrong .. hmm ..
if (block)
only checks if the block exists block
, so try checking it (we are sure it exists).
you might want to check result
instead ...
if (result != nil) block(result, nil);
is the correct statement
the reason is this:
if (result != nil) // will return NONE nil value only
{
block(result, nil);
}
// else will not set things to the block
//or maybe just
block(result, nil); // which will allow the 'block(nil, nil);' and under your implementation
[ User loginWith:text andPassword:text complete:^(id result, NSError *error)
{
if (result)
{
[APPDELEGATE start];
}
else if (result == nil & error == nil)
{
// NO objectForKey @"id"
}
else
{
// ERROR
}
}];
but under failure:^(NSURLSessionDataTask *task, NSError *error)
justblock(nil, error);
source to share