如何在我的iOS应用程序中正确实现Services类?
我当前的头脑刮板:专门为我的rails应用程序的服务调用实现一个模型类。
这是场景:
- 我有一个名为Service的类,它是NSObject的子类。
- 实现文件定义了一些方法…让我们来看看doSignUp 。
- 我正在使用AFNetworking与api进行通信。
- 从我的SignUpViewController ,我创建了一个Service类的实例并调用doSignUp
- 该方法按预期工作,我从服务器收到适当的响应。
现在来了我不完全明白的部分:
- AFNetworking利用块进行服务调用。
- 在成功块内部,我调用了一个名为handleSignUp的辅助方法(也在Service类中)。 这个方法基本上解析了JSON,并从中创建了一个新的User (NSObject子类)。 然后, handSignUp方法返回User对象。
此时我有一个新的User对象,我需要将该对象发送回我的SignUpViewController …我该怎么做?
- 我应该尝试将该对象添加到AppDelegate并从SignUpViewController访问它吗? 此解决方案可以用于访问各种全局属性,但SignUpViewController何时可以知道何时访问它?
- 我应该尝试在Service类中添加对SignUpViewController的引用吗? 这似乎适得其反…我不妨将方法doSignUp和handSignUp添加到SignUpViewController 。 在我看来,我的Service类不应该知道任何其他viewControllers。
请参阅下面的代码示例:
Service.h
//Service.h #import #import "AFNetworking.h" @interface Services : NSObject - (void) doSignUp:(NSMutableDictionary*)params; @end
Service.m
// Service.m #import "Services.h" #import "Config.h" @implementation Services - (void) doSignUp:(NSMutableDictionary*)params { NSURL *url = [NSURL URLWithString:@"http://MYURL.COM"]; AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url]; NSURLRequest *request = [httpClient requestWithMethod:@"POST" path:@"signup.json" parameters:params]; AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) { [self handleSignUp:JSON]; } failure:nil]; [operation start]; } - (User*) handleSignUp:(NSMutableArray*)json { User * user = nil; if ([[json valueForKey:@"success"] isEqualToString:@"true"]) { // create user here ... } return user; }
SignUpViewController.h
#import "Service.h" @interface SignUpViewController : UIViewController { Service * service; } @property (nonatomic, strong) Service * service;
SignUpViewController.m
#import "SignUpViewController.h" @interface SignUpViewController () @end @implementation SignUpViewController @synthesize service = __service; - (IBAction) initSignUp:(id)sender { // create params... [self.service doSignUp:params]; }
再一次,所有这些代码都做了它应该做的事情……我只需要知道它应该如何进行通信。 如何警告SignUpViewController已调用handleSignUp并且新的User对象可用?
谢谢你的时间,安德烈
据我所知,你没有意识到这个过程的异步性质:实际上,由于网络或IO操作需要很长时间才能完成,我们倾向于选择异步访问,我的意思是:调用者(View Controller)调用消息通过服务的Web API,然后STOPS等待响应。 实际上,网络处理可以在不同的进程或线程或处理器的核心上完成。
那么,当操作完成时,如何通过服务通知调用者? 要做到这一点,我们必须使用Delegation绑定两个对象:我们创建一个协议(它只是一个对象将响应的消息声明),我们在View Controller中实现它,这样任何使用它的人都会知道确定有哪些消息可用。
然后我们将在我们的服务中声明一个类型为id的委托,一旦操作完成就会调用该委托。
这几乎就像在服务类中导入ViewController.h并为服务提供指向视图控制器的指针,但是以正确的方式完成(没有循环引用并遵守SOLID原则)
现在一些代码:
//service.h @protocol RemoteApiProtocol @required -(void) UserLoggingIn; -(void) UserLoggedIn:(User*)user; -(void) UserLoginError:(NSError *)error; @end
该协议就像一个小接口,它是两个类/对象之间的契约。 然后在您的服务中,您可以声明协议的字段和属性:
//Service.h #import #import "AFNetworking.h" @interface Services : NSObject{ __weak id delegate; } @property (nonatomic, assign) id delegate; - (void) doSignUp:(NSMutableDictionary*)params; @end
此时,您在视图控制器中实现协议,集成您刚刚构建的合同。 这样,服务器将知道委托将始终能够响应协议消息。
//SignUpViewController.h #import "Service.h" @interface SignUpViewController : UIViewController { Service * service; } @property (nonatomic, strong) Service * service;
在实现协议之后,您可以将视图控制器指定为服务器的委托,这样服务器在调用apis之后将能够使用调用结果调用委托。 为此,您需要实现服务将调用的协议消息:
//SignUpViewController.m #import "SignUpViewController.h" @implementation SignUpViewController @synthesize service = __service; - (IBAction) initSignUp:(id)sender { //create the service with params //... //assign self as the delegate self.service.delegate = self; [self.service doSignUp:params]; } #pragma mark - RemoteApiProtocol -(void) UserLoggingIn { //login started, //you can show a spinner or animation here } -(void) UserLoggedIn:(User*)user { //user logged in , do your stuff here } -(void) UserLoginError:(NSError *)error { NSLog(@"error: %@",error); } @end
最后,在成功和错误块中调用api之后,调用服务中的消息:
// Service.m #import "Services.h" #import "Config.h" @implementation Services - (void) doSignUp:(NSMutableDictionary*)params { NSURL *url = [NSURL URLWithString:@"http://MYURL.COM"]; AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url]; NSURLRequest *request = [httpClient requestWithMethod:@"POST" path:@"signup.json" parameters:params]; AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) { [self handleSignUp:JSON]; } failure:nil]; //Signaling the start of the signup operation to the delegate if(self.delegate){ [self.delegate UserLoggingIn]; } [operation start]; } - (User*) handleSignUp:(NSMutableArray*)json { User * user = nil; if ([[json valueForKey:@"success"] isEqualToString:@"true"]) { // create user here ... //call the delegate (view controller) passing the user if(self.delegate){ [self.delegate UserLoggedIn:user]; } }else{ //error handling if(self.delegate){ //NSError *error = ... //[self.delegate UserLoginError:error]; } } //return user; } @end
您可能希望查看Delegation
和/或Data Source
。 在本文中查看我接受的答案以获取示例,并参考有关该主题的Apple Docs。
目标C查看控制器通信
希望这可以帮助。
将回调块参数添加到doSignUp
(应该可以重命名为signUpWithParams:success:failure:
,以便控制器可以响应注册成功或失败,并使任一情况的上下文可用于向用户显示相关信息。
委派方法肯定会奏效。 您基本上可以将一个委托添加到Service类,并在调用doSignup之前将委托设置为等于视图控制器。
但是,我会使用块来实现它。 将参数添加到doSignUp方法,该方法是一个完成块,在SignUpViewController中声明。 有关如何将块作为参数传递的示例,请查看AFNetworking中的源。