使用 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
之前先检查email
和password
,将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
,视图控制器除了监听用户事件(按钮点击等)外什么都不做。如果将新开发人员引入您的代码库,仅此一项就可以使您的代码更容易推理和遵循。
希望对您有所帮助!
在我所有的 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
之前先检查email
和password
,将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
,视图控制器除了监听用户事件(按钮点击等)外什么都不做。如果将新开发人员引入您的代码库,仅此一项就可以使您的代码更容易推理和遵循。
希望对您有所帮助!