我们的客户端网络框架至少要解决三个问题:实现通信协议、账户系统、简化服务端接口调用。
实现通信协议 根据与服务端制定的通信协议,实现请求的组装,序列化,发送,以及响应的接收和解析等。
账户系统 简而言之就是实现注册、登陆、注销等功能,并维护登陆状态等。
简化服务端接口调用 客户代码只需要提供业务参数和回调函数就可以实现与服务器通信,网络框架负责封装掉其余所有细节。
我想对架构比较敏感的读者会立刻有这样的共鸣,首先上述的账户系统显然是一个独立的模块,最好单独设计实现。另一方面,账户系统的功能又是以服务端接口调用为基础的,在形式上登陆操作也是调用服务端接口,那么把登陆相关操作与其他服务端接口调用实现于一处就是自然的。如果再作一些考虑,我们还会想到的一个问题是,网络框架暴露给客户代码的接口应当尽可能单一,如果我们用一个类维护账户系统,用另一个类做服务端业务接口调用,会嫌不够简洁。达成这几点共识之后,我们就可以继续探讨一些设计细节了。先看下面的代码。
//SFClient.h
@interface SFClient
@property (nonatomic,readonly) NSString* account;
@property (nonatomic,readonly) NSString* password;
@property (nonatomic,readonly) BOOL isLoggedIn;
@property (nonatomic,readonly) BOOL pendingLogin;
@property (nonatomic,readonly) NSString* sessionId;
-(NSURLSessionTask*)loginWithAccount:(NSString*)account password:(NSString*)password;
-(NSURLSessionTask*)logout;
-(NSURLSessionTask*)someNetworkingTaskWithCompletionHandler:(SFNetworkingTaskCompletionHandler)completionHandler;
//...
@end
有的同行习惯于为每一个后端接口单独开一个类,这当然也不失为一种设计风格,笔者也曾尝试过,个人感觉嫌繁。
这里的SFClient类作为账户系统,又兼具服务端业务接口调用功能,实现了使接口尽可能简洁的设计目标,却违背了账户系统应当单独实现的架构设计直觉。
如何解决这一矛盾呢?可以采用dynamic proxy设计模式。
定义一个protocol
假设叫SFBackendInterfaces
,和一个实现类假设叫SFBackendInterfacesImpl
。让SFClient
和SFBackendInterfacesImpl
都实现这个协议。
@protocol SFBackendInterfaces<NSObject>
-(NSURLSessionTask*)loginWithAccount:(NSString*)account password:(NSString*)password completionHandler:(SFNetworkingTaskCompletionHandler);
-(NSURLSessionTask*)someNetworkingTaskWithCompletionHandler:(SFNetworkingTaskCompletionHandler)completionHandler;
@end
@interface SFClient:NSObject<SFBackendInterfaces>
@interface SFBackendInterfacesImpl:NSObject<SFBackendInterfaces>
这样做的目的是什么呢,就是让SFClient类继续提供服务端接口调用功能,同时把这些接口调用的实现代码交给SFBackendInterfacesImpl。这样就既满足网络框架接口简洁的需求,又保持了SFClient类作为账户系统的纯净,-(NSURLSessionTask*)someNetworkingTaskWithCompletionHandler:(SFNetworkingTaskCompletionHandler)completionHandler;
这行代码可以从SFClient的interface中拿掉了,并且相关代码也不需要出现在它的implementation文件里了。我们来看implementation。
@implementation SFClient
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
if([self.backendInterfacesImpl respondsToSelector:anInvocation.selector]){
[anInvocation invokeWithTarget:self.backendInterfacesImpl];
}else{
[super forwardInvocation:anInvocation];
}
}
-(void)loginWithAccount:(NSString*)account password:(NSString*)password
{
NSURLSessionTask* task=[self loginWithAccount:account password:(NSString*)password completionHandler:^(SFResponse* response){
[[NSNotificationCenter defaultCenter] postNotificationNamed:SFLoginCompletionNotification object:response];
_pendingLogin=NO;
if(response.status==SFResponseStatusSuccess){
_loggedIn=YES;
}
}];
[task resume];
_pendingLogin=YES;
}
@end
@implementation SFBackendInterfacesImpl
-(NSURLSessionTask*)loginWithAccount:(NSString*)account password:(NSString*)password completionHandler:(SFNetworkingTaskCompletionHandler)
{
//...
}
-(NSURLSessionTask*)someNetworkingTaskWithCompletionHandler:(SFNetworkingTaskCompletionHandler)completionHandler
{
//...
}
@end
这样客户代码就可以通过SFClient这一单一接口使用网络框架了。
[[SFClient sharedClient] loginWithAccount:xxxx password:xxxx];
//...
NSURLSessionTask* task=[[SFClient sharedClient] someNetworkingTaskWithPara::param completionHandler:^(SFResponse* response){
//...
}];
[task resume];
而在框架内部实现上,账户系统和业务接口调用的实现仍然是分离的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。