【问题标题】:AFNetworking AFHTTPClient Subclassing, Singleton Client and DelegatesAFNetworking AFHTTPClient 子类化、单例客户端和委托
【发布时间】:2013-09-12 20:08:46
【问题描述】:

我正在为一个 iOS 项目使用 AFNetworking 编写一个 REST API 层。我对到目前为止所写的内容有一些疑问,所以我转向 stackoverflow 寻求一些指导/答案。

以下是我要达到的目标的指导方针:

  • 一个基类 (DRAPI : AFHTTPClient),它将初始化一个单例客户端,正如 AFHTTPClient 的 cocoadocs 所推荐的那样。
  • DRAPI 的“基础”委托:DRAPIDelegate,包含以统一格式显示错误的方法。
  • 处理我的 REST API 的某些路由的 DRAPI 子类。例如,对用户的 CRUD 操作是 DRUserAPI 的责任,DRUserAPI 是 DRAPI 的子类。
  • DRAPI 的每个子类都有自己的委托。对于 DRUserAPI,有 DRUserAPIDelegate,它扩展了 DRAPIDelegate。

下面是一个简单的例子,说明到目前为止的编写方式:

DRAPI.h

@interface DRAPI : AFHTTPClient

- (void) apiGetCallWithRoute:(NSString*)route
                  parameters:(NSDictionary*)parameters
                   onSuccess:(void(^)(id))successBlock
                     onError:(void(^)(NSArray* errors))errorBlock;

@end

@protocol DRAPIDelegate <NSObject>

-(void) DRAPIErrorFromServer:(NSArray*)errors;

@end

DRAPI.m

@implementation DRAPI

+(DRAPI*) sharedClient
{
  static DRAPI *aSharedClient = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&once_token, ^{
    _sharedClient = [DRAPI alloc] initWithBaseURL:[NSURL URLWithString:@"http://127.0.0.1:3000/api"];
  });
  return aSharedClient;
}

-(id) initWithBaseURL:(NSURL *)url
{
  self = [super initWithBaseURL:url];
  if (self) {
     // configuration goes here... skipping because it is not important.
  }
  return self;
}

#pragma mark - Helper methods for Server Calls

- (void) apiGetCallWithRoute:(NSString*)route
                  parameters:(NSDictionary*)parameters
                   onSuccess:(void(^)(id))successBlock
                     onError:(void(^)(NSArray* errors))errorBlock 
{
  [[DRAPI sharedClient] getPath:route
                    parameters:addAuthenticationParametersTo(parameters)
                       success:^(AFHTTPRequestOperation *operation, id responseObject) {
                         successBlock(responseObject);
                       }
                       failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                         errorBlock( processError() );
                       }];
}

@end

DRUserAPI.h

@interface DRUserAPI: DRAPI

@property (weak, nonatomic) id<DRUserAPIDelegate>delegate;

+(DRUserAPI*) APIWithDelegate:(id<DRUserAPIDelegate>)delegate;

-(void) createUser:(NSString*)username password:(NSString*)password;

// ... more methods would be declared here...

@end

@protocol DRUserAPIDelegate <NSObject, DRAPIDelegate>

-(void) DRUserAPIOnUserCreated:(MyUserModel*)newUser;

// more delegate methods would be here...

@end

DRUserAPI.m

@implementation DRUserAPI

@synthesize delegate;

+(DRUserAPI*) APIWithDelegate:(id<DRUserAPIDelegate>)delegate 
{
  DRUserAPI * client = [DRUserAPI new];
  client.delegate = delegate;
  return client;
}

-(void) createUser:(NSString*)username password:(NSString*)password
{
  [self apiGetCallWithRoute:@"users/create"
                 parameters:@{@"username" : username, @"password": password}
                  onSuccess:^(id response) {
                    NSDictionary *parsedJSON = response;
                    [delegate DRUserAPIOnUserCreated:[MyUserModel newModelFromDictionary:parsedJSON];
                  }
                  onError:^(NSArray *errors) {
                    [delegate DRAPIErrorFromServer:errors];
                  }];
}

@end

一位同事提醒我,代表和单身人士不能混为一谈。不过,我仍然想管理代表。我认为好的解决方案是将委托的单例实例传递给我在 API 子类中调用的方法。

这是个好主意吗?

谢谢!

【问题讨论】:

  • 如果有回调块,为什么还需要委托?
  • 在这一点上,我使用委托而不是回调块并没有什么特别的原因。使用回调块有什么好处?
  • 另外,一位同事提到回调块不像委托那样可读。就个人而言,我没有任何偏好,但他是我课程的主要用户,所以如果可能的话,我想适应他。
  • 真的吗?当你必须设计 36 个不同的委托来与这个 API 交互时,他抱怨可读性?
  • 一个 REST API 没有 users/create 端点,你只需 POST 到 users/ 来创建一个,PUT 来更新一个,等等。我建议使用回调块而不是委托方法因为这种方法避免了可能的竞争条件。

标签: objective-c delegates afnetworking afhttpclient


【解决方案1】:

我更喜欢基于组合而不是子类化的实现,即使 AFNetworking 文档建议子类化 AFHTTPClient。

我会在 DRAPI 中注入 AFHTTPClient,在 DRUserAPI 中注入这个,使它们都成为 NSObject 的简单子类。扁平化设计在 IMO 上更简洁,并且可以更轻松地对类进行单元测试。

您可以创建一个负责创建整个对象图的注入器类,而不是单例,只在您的应用程序委托中调用它。

为此,您应该使用基于块的 API 而不是委托,因为您将只有一个 DRAPI 实例,并且您不想在对其进行任何调用之前设置其委托(您可以在其中注入另一个类,例如 DRUserAPI DRAPI 实例)。

【讨论】:

  • 注入似乎是一种有趣的方法。不过,我必须研究在 Objective-C 中注入的最佳实践。
【解决方案2】:

它并不完美,但它确实有效。为什么这么多代表?看来您正在进入单例的无限循环。我觉得你应该停下来……

【讨论】:

  • 我认为 DSAPI 的每个子类都应该有自己的委托类。对我来说,在我的应用程序中拥有一个包含所有回调的整体委托似乎是个坏主意。 IMO 将其除以关注点似乎是个好主意。
  • 我究竟是如何进入单例的无限循环的?我在 DSAPI#sharedClient 中的代码应该为客户端返回一个单例实例,对吧?
  • 我正在评论您的思考过程。这就是我曾经发生过的事情,我从一个(用于像你这样的网络)开始,然后最终有 5 个用于简单的事情。所以不要过度使用单身人士。设计模式(如 Singleton)很好,但并不适用于所有解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-16
  • 1970-01-01
  • 2020-12-07
  • 1970-01-01
  • 1970-01-01
  • 2014-01-18
相关资源
最近更新 更多