使用AFNetworking在Objective-C中的模型对象内进行远程数据获取

3
在我所有的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
         }
       }];

   }

那么我的实现是否尊重MVC并遵循最佳实践,如果不是,我该如何改进?
2个回答

2
单例模式:你可能希望避免使用单例模式,这将有助于改善API设计并使代码更易于测试。此外,在User的情况下,想象一下您将要支持更改用户(注销/访客用户等)。采用当前方法,您将仅限于发送NSNotification,因为使用connectedUser的所有人都无法知道底层引用已更改。 ActiveRecord:您对可执行网络操作的模型User所做的事情与活动记录方法有些相似,当您的模型变得更加复杂且可以执行的操作数量增加时,这种方法可能不会很好扩展。考虑将它们分离成纯模型和实际执行网络操作的服务(或将来需要的其他任何内容)。 模型序列化:考虑将模型和网络响应序列化逻辑封装到一个单独的类中(例如LoginResponse,其中包括指向User的其他内容),像Mantle这样的框架可以使它更容易。 MVC:从我的iOS经验来看,MVC可能不是除了简单应用程序以外最优的方法。使用MVC的趋势是将所有逻辑放入您的ViewController中,使其变得非常庞大且难以维护。考虑其他模式,如MVVM 总之,我理解一次学习所有新技术很难,但您肯定可以通过确保每个类只执行一件事情来开始:模型不执行网络操作或持久化到磁盘,API客户端不对每个响应进行反序列化或保存数据到NSUserDefaults,视图控制器除了侦听用户事件(按钮点击等)不做任何事情。这本身将使您的代码更易于理解,并且如果引入新开发人员到您的代码库中,则更易于跟踪。
希望有所帮助!

1

关于你的MVC(Model–view–controller)我没有什么要说的,对吧?

我只想补充一些有用的方法来避免不必要的崩溃...

首先是在

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

这个示例限制了其他类型的类,特别是[NSNull class] 第二行:
NSDictionary *params = @{@"email": email, @"password": password};

在将emailpassword分配给NSDictionary之前,请先进行检查,如果将nil赋值给NSDictionary将导致崩溃。

第三行是:

if (block) block(result, nil);

block从您的实现中返回void。这个有用吗?抱歉问一下,我还没有尝试过像这样的块式if语句..

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


只是一个小提示,如果responseObjectnil,那么if([responseObject objectForKey:@"id"])将会评估为NO,所以如果我正确理解了你的担忧,就不会崩溃。此外,在开发过程中检查数据类型是您可能会做的事情,但在API建立后的生产环境中不需要这样做:在我看来,这太过于防御性编码了。 - Sash Zats
1
而原始代码在某种意义上是正确的,因为您必须检查 block != nil(可以缩写为 if (block)),并且只有在确定有有效指针后才调用它。 - Sash Zats
@SashZats,我明白了。但他正在寻求最佳实践,难道在生产之前检查一切不是最好的吗?而且没有“过多的防御性编码”,这就是你所说的错误处理,预见可能出现的错误是一件坏事吗?“系统可靠性”和“几乎没有”漏洞的系统是好的,对吧? - 0yeoj
网络连接经常会出现故障,因此这是我需要防范的事情。现在想象一下,在使用 Facebook API v X.X 时,突然更改了返回的数据类型。这在生产中并不经常发生。至于结果,除了成功时的 nil 情况外,我没有看到任何可疑的东西,我可能会添加一个手动创建的 NSError(例如,响应格式错误...)。 - Sash Zats
1
别担心,没事的,我喜欢这种对话方式,这样的交流可以让我们在专业领域更加有见地,不是吗?兄弟,干杯!(击掌):D - 0yeoj
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接