【问题标题】:How to properly implement a Services class into my iOS application?如何在我的 iOS 应用程序中正确实现服务类?
【发布时间】:2012-06-15 23:57:37
【问题描述】:

我目前的头疼问题:实现一个模型类,专门用于对我的 rails 应用程序的服务调用。

这是场景:

  • 我有一个名为 Service 的类,它是 NSObject 的子类。
  • 实现文件定义了一些方法……让我们看看 doSignUp
  • 我正在使用 AFNetworking 与 api 进行通信。
  • 从我的 SignUpViewController,我创建了一个我的实例 Service 类并调用 doSignUp
  • 该方法按预期工作,我收到了来自服务器的正确响应。

现在是我不完全理解的部分:

  • AFNetworking 在其服务调用中使用块。
  • success 块中,我调用了一个名为 handleSignUp 的辅助方法(也在 Service 类中)。这个方法本质上是解析 JSON,然后我创建了一个新的 User(NSObject 子类)。 handSignUp 方法然后返回 User 对象。

此时我有一个新的 User 对象,我需要将该对象发送回我的 SignUpViewController...我该怎么做?

  • 我是否应该尝试将该对象添加到 AppDelegate 并访问它 来自 SignUpViewController?该解决方案可以访问 各种全局属性,但 SignUpViewController 何时 知道什么时候可以访问吗?
  • 我是否应该尝试添加对 SignUpViewController 的引用 服务 类?这似乎适得其反......我可能会 将方法 doSignUphandSignUp 添加到 SignUpViewController。在我看来,我的 Service 类不应该知道任何其他视图控制器。

下面是我的代码示例:

服务.h

//Service.h
#import <UIKit/UIKit.h>
#import "AFNetworking.h"
@interface Services : NSObject
- (void) doSignUp:(NSMutableDictionary*)params;
@end

服务.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 对象可用?

感谢您的宝贵时间, 安德烈

【问题讨论】:

    标签: iphone ios web-services architecture afnetworking


    【解决方案1】:

    据我所知,您没有意识到这个过程的异步性质:实际上,由于网络或 IO 操作需要很长时间才能完成,我们倾向于使用异步访问,我的意思是:调用者(视图控制器)调用通过服务在 Web API 上发送消息,然后停止等待响应。实际上,网络处理可以在不同的进程或线程或处理器的核心上完成。

    那么,当操作完成时,调用者如何被服务通知? 为此,我们必须使用委托绑定两个对象: 我们创建一个协议(它只是一个对象将响应的消息的声明)并在我们的视图控制器中实现它,这样任何使用它的人都会确定哪些消息可用。

    然后我们将在我们的服务中声明一个类型为 id 的委托,一旦操作完成就会被调用。

    这几乎就像在服务类中导入您的 ViewController.h 并为服务提供指向视图控制器的指针,但以正确的方式完成(没有循环引用并尊重 SOLID 原则)

    现在一些代码:

    //service.h
    @protocol RemoteApiProtocol <NSObject>
    @required
    -(void) UserLoggingIn;
    -(void) UserLoggedIn:(User*)user;
    -(void) UserLoginError:(NSError *)error;
    @end
    

    该协议就像一个小接口,它是两个类/对象之间的契约。 然后在您的服务中,您可以为协议声明字段和属性:

    //Service.h
    #import <UIKit/UIKit.h>
    #import "AFNetworking.h"
    @interface Services : NSObject{
          __weak id<RemoteApiProtocol> delegate;
    }
    
    @property (nonatomic, assign) id<RemoteApiProtocol> delegate;
    
    - (void) doSignUp:(NSMutableDictionary*)params;
    
    @end
    

    此时,您在视图控制器中实现协议,集成您刚刚构建的合约。这样,服务器就会知道委托将始终能够响应协议消息。

    //SignUpViewController.h
    #import "Service.h"
     @interface SignUpViewController : UIViewController <RemoteApiProtocol> {
         Service * service;
     }
    
     @property (nonatomic, strong) Service * service;
    

    实现协议后,您可以将视图控制器分配为服务器的委托,这样服务器在调用 api 后将能够使用调用结果调用委托。为此,您需要实现将由服务调用的协议消息:

    //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
    

    【讨论】:

    • 这种方法效果很好,但是,@mattt 让mentioned 使用块,因为它更容易。两种解决方案都很好,但我很想知道建议的解决方案的优缺点可能是什么。再次感谢!
    • 我没有提到块,因为它们更容易使用,但不容易理解。我在这里看到的是需要清楚地理解(异步)过程,而不是更简洁的代码。说到代表就像描述一种人际互动,有人向其他人提出问题,然后等待回应。描述块、闭包和 lambda 函数并不容易。
    • 知道了。我决定采用您建议的方法,您是正确的,更容易理解该过程。干杯
    【解决方案2】:

    您可能想查看Delegation 和/或Data Source。查看我在这篇文章中接受的答案以获取示例,并参考有关该主题的 Apple Docs。

    Objective C View to Controller Communication

    希望这会有所帮助。

    【讨论】:

      【解决方案3】:

      将回调块参数添加到doSignUp(可能应该重命名为signUpWithParams:success:failure:),以便控制器可以响应注册成功或失败,并具有任何一种情况的上下文以向用户。

      【讨论】:

      • 你会建议这种方法而不是创建@protocol。如果是,为什么?
      • 等等,什么?你是指代言人吗?不,一定要使用积木——容易得多。
      • 是的,我的意思是创建一个代表。因此,如果我理解正确,我的 doSignUp (或 signUpWithParams)方法将包含回调块参数 AND 包括 AFJSONRequestOperation 块...其中还包含成功:失败参数。有这样的例子吗?
      【解决方案4】:

      委托方法肯定行得通。您基本上可以将委托添加到服务类,并在调用 doSignup 之前将委托设置为等于视图控制器。

      但是,我会使用块来实现它。向 doSignUp 方法添加一个参数,该参数是一个完成块,在 SignUpViewController 中声明。有关如何将块作为参数传递的示例,请查看来自 AFNetworking 的源代码。

      【讨论】:

        猜你喜欢
        • 2017-03-20
        • 2019-08-11
        • 1970-01-01
        • 2012-09-21
        • 1970-01-01
        • 1970-01-01
        • 2012-05-17
        • 2019-06-06
        • 2012-10-22
        相关资源
        最近更新 更多