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?

+3


source to share


2 answers


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!

+2


source


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);

+1


source







All Articles