使用 AFNetworking 在 objective c 中的模型对象内部获取远程数据

remote data fetching inside model object in objective c using AFNetworking

在我所有的 iOS 应用程序中,我都使用这种方法来尊重 MVC,我想确保我的实现是正确的并且尊重最佳实践和 MVC 设计模式:

AFNetworking 的单例充当 API 网络调用:

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

使用单例来获取用户数据的模型用户(作为实现好吗?):

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
         }
       }];

   }

那么我的实施是否尊重 MCV 并遵循最佳实践?如果没有,我该如何改进?

我对你的 MVC(模型-视图-控制器)没什么好说的,对吗?

我只想添加一些可能有用的方法来避免不必要的崩溃..

第一个在

[[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 ;
    }
}];

总是有可能因为很多原因而提到 reponseObject 可能是 nil 因此对象没有密钥 @"id" 并且会导致错误(最坏情况下崩溃案件)。我有这段代码,我不知道这是否可以被视为最佳实践,但它是:

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

这个例子限制了其他类型的 class 特别是 [NSNull class]

排在第二位:

NSDictionary *params = @{@"email": email, @"password": password};

通过在分配给NSDictionary之前先检查emailpassword,将nil设置为NSDictionary会导致崩溃。

第三行:

if (block) block(result, nil);

block returns void 来自您的实施。这管用吗?很抱歉问我还没有尝试 if-statement 这样的块..

complete: (void(^)(id result, NSError *error)) block

void 这里是你的块的返回值,否则我错了..嗯..

if (block) 只检查 block 块是否存在,所以不检查它(我们确信它存在)..

也许您想检查 result...

if (result != nil) block(result, nil);是正确的说法

背后的原因是:

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
     }
}];

而在 failure:^(NSURLSessionDataTask *task, NSError *error) 下只是 block(nil, error);

Singletons:您可能希望避免使用单例,它会帮助您改进 API 设计并使代码更易于测试。此外,在 User 的情况下,假设您希望支持更改用户 (logout/guest user/etc)。使用当前方法,您将仅限于发送 NSNotification,因为使用 connectedUser 的每个人都不知道底层引用已更改。

ActiveRecord: 您对能够执行网络的模型 User 所做的与 active record approach 有点相似,当您的模型变得更复杂并且它可以执行的操作数量增加时,它可能无法很好地扩展。考虑将它们分成纯模型和实际执行网络的服务(或您将来需要的任何其他内容)。

模型序列化: 考虑将模型和网络响应序列化逻辑封装到一个单独的 class(例如 LoginResponse,除此之外指向 User)框架,如 Mantle 使它变得容易得多。

MVC:根据我在 iOS 方面的经验,除了简单的应用程序之外,MVC 可能不是最佳方法。使用 MVC 的趋势是将所有逻辑放入 ViewController 中,使其变得非常庞大且难以维护。考虑其他模式,例如 MVVM

总而言之,我知道很难一次学习所有新技术,但您绝对可以从确保每个 class 执行一件事且仅执行一件事开始:模型不做网络或者持久化到磁盘,API 客户端不会反序列化每个响应或将数据保存到 NSUserDefaults,视图控制器除了监听用户事件(按钮点击等)外什么都不做。如果将新开发人员引入您的代码库,仅此一项就可以使您的代码更容易推理和遵循。

希望对您有所帮助!