WebApiClient 1.2.2

WebApiClient 1.2.2

测试已测试
Lang语言 Obj-CObjective C
许可证 Apache-2.0
发布最后发布2018年4月

Matt MagoffinDan Flynnwmjesstaylor 维护。



WebApiClient

WebApiClient 是一个应用框架,旨在以标准化的方式执行 RESTful 网络服务请求。它通过为每个网络服务端点配置命名路由,从而使您的应用程序专注于消费逻辑网络服务 API,而不是 HTTP 实现细节。

项目分为模块,从核心模块开始,然后分支到各种支持模块,这些模块添加了如 对象映射缓存UI 支持 等功能。

模块:核心

“核心”模块提供了一个基于路由的 HTTP 客户端框架,支持将请求和响应在本地对象和序列化形式之间进行转换,如 JSON。此模块仅提供基于协议的 API 和一些支持 API 的骨架类,但本身不提供实际的全实现,因此可以使用不同需要的 HTTP 后端。`AFNetworking 模块` 提供了 API 的完整实现。

WebApiClient 协议定义了应用程序使用的 HTTP 客户端主入口点。API 故意简单,基于异步块回调。

- (void)requestAPI:(NSString *)name
 withPathVariables:(id)pathVariables
        parameters:(id)parameters
              data:(id<WebApiResource>)data
		  finished:(void (^)(id<WebApiResponse> response, NSError *error))callback;

此 API 的一个示例调用可能如下所示

// make a GET request to /documents/123
[client requestAPI:@"doc" withPathVariables:@{@"uniqueId" : @123 } parameters:nil data:nil
          finished:^(id<WebApiResponse> response, NSError *error) {
	if ( !error ) {
		MyDocument *doc = response.responseObject;
	} else if ( response.statusCode == 422 ) {
		// handle 422 (validation) errors here...
	}
}];

后台回调支持

默认情况下,回调块在主线程(队列)上调用。如果您希望回调在特定队列上,可以使用接受分派队列作为参数的另一种方法。在这种情况下,将使用传入的队列来执行回调

- (void)requestAPI:(NSString *)name
 withPathVariables:(id)pathVariables
        parameters:(id)parameters
              data:(id<WebApiResource>)data
             queue:(dispatch_queue_t)callbackQueue
          progress:(nullable WebApiClientRequestProgressBlock)progressCallback
		  finished:(void (^)(id<WebApiResponse> response, NSError *error))callback;

进度回调支持

上一部分中展示的接受显式回调块的相同方法还接受一个可选的 WebApiClientRequestProgressBlock,其定义为以下内容

typedef void (^WebApiClientRequestProgressBlock)(NSString *routeName,
                                                 NSProgress * _Nullable uploadProgress,
                                                 NSProgress * _Nullable downloadProgress);

通过将此类型块传递给 progress 参数,您可以监控 HTTP 请求的上传和下载进度。此外,还可以使用 WebApiClientRequestDidProgressNotificationWebApiClientResponseDidProgressNotification 通知来监听进度更新。用户信息键 WebApiClientProgressNotificationKey 将包含相关的 NSProgress 对象。

同步请求支持

有时执行一个阻塞的、同步的 HTTP 资源请求可能很有用。WebApiClient 也支持这一点

- (id<WebApiResponse>)blockingRequestAPI:(NSString *)name
                       withPathVariables:(id)pathVariables
                              parameters:(id)parameters
                                    data:(id<WebApiResource>)data
                             maximumWait:(NSTimeInterval)maximumWait
                                   error:(NSError **)error;

调用此方法将阻塞调用线程,直到响应可用或 maximumWait 秒已过。

路由

WebApiRoute 协议定义了一个单个 API 端点定义,分配一个唯一名称。路由通常在应用程序启动时配置。每个路由定义了一些标准属性,例如 HTTP 的 method 和 URL 的 path。为了方便,路由支持通过 Objective-C 的键子索引支持进行任意属性访问,因此以下是可能的

id<WebApiRoute> myRoute = ...;

// access the path property
NSString *path1 = myRoute.path;

// access the path property using keyed subscript notation
NSString *path2 = myRoute[@"path"];

// access some arbitrary property not defined in WebApiRoute specifically
id something = myRoute[@"extendedProperty"];

为了进一步提高便利性,WebApiRoute 为 NSDictionaryNSMutableDictionary 提供了 扩展,以便它们分别符合 WebApiRouteMutableWebApiRoute。这意味着您可以直接使用字典作为路由,如下所示

// define a route
id<WebApiRoute> myRoute = @{ @"name" : "login", @"path" : @"user/login", @"method" : @"POST" };

// create a mutable copy and extend
id<MutableWebApiRoute> mutableRoute = [myRoute mutableCopy];
mutableRoute[@"extendedProperty"] = @"special";

对象映射

WebApiDataMapper协议定义了一个API,用于将原生对象编码到HTTP请求中,以及将HTTP响应映射到原生对象中。可以通过配置dataMapper属性来配置路由,以支持这一功能。该API也非常简单。

@protocol WebApiDataMapper <NSObject>

// Map a source data object into some domain object.
- (id)performMappingWithSourceObject:(id)sourceObject route:(id<WebApiRoute>)route error:(NSError *__autoreleasing *)error;

// Encode a domain object into an encoded form, such as @c NSDictionary or @c NSData.
- (id)performEncodingWithObject:(id)domainObject route:(id<WebApiRoute>)route error:(NSError *__autoreleasing *)error;

@end

模块:AFNetworking

AFNetworking模块提供了基于AFNetworking和NSURLSessionWebApiClient API的完整实现。

路由配置

可以通过registerRoute:forName:方法在代码中配置路由,但更方便的是通过BREnvironment来配置。默认情况下,将会检查webservice.api键,它可以为应用程序注册的所有路由表示一个字典。例如,以下JSON将会注册三个路由:login、register和absolute。

{
  "App_webservice_protocol" : "https",
  "App_webservice_host" : "example.com",
  "App_webservice_port" : 443,
  "webservice" : {
    "api" : {
      "register" : {
        "method" : "POST",
        "path" : "user/register",
      },
      "login" : {
        "method" : "POST",
        "path" : "user/login",
      },
      "absolute" : {
        "method" : "GET",
        "path" : "https://example.com/something"
      }
    }
  }
}

您会注意到register和login路由有相对路径。所有web服务URL都相对于可配置的baseApiURL属性构建,默认情况下是通过之前示例JSON中显示的各种App_webservice_* BREnvironment键配置的。

GZip压缩支持

支持在路由上启用gzip属性。当设置为true时,任何数据都会被压缩,并添加一个请求HTTP头Content-Encoding: gzip

为了支持压缩的响应数据(强烈推荐!),您只需配置一个Accept-Encoding: gzip HTTP头,通过requestHeaders属性在路由上或通过在AFNetworkingWebApiClient上可用的globalHTTPRequestHeaders属性来进行。

以下是一个配置请求和响应压缩的示例路由。

{
  "webservice" : {
    "api" : {
      "trim" : {
        "method" : "POST",
        "path" : "upload/jumbo",
        "gzip" : true,
        "requestHeaders" : {
          "Accept-Encoding" : "gzip"
        }
      }
    }
  }
}

上传原始数据

原始数据可以直接上传到HTTP请求体中。这适用于需要上传图片或其他类型的数据的情况,因为URL中包含了足够的信息来识别内容。要执行原始数据上传,请将WebApiResource实例传递到WebApiClient API中的data参数。WebApiClient提供了两种WebApiResource实现:用于内存数据的DataWebApiResource和用于文件数据的FileWebApiResource。您传递的WebApiResource实例将直接发送到请求体中,并包含适当的Content-TypeContent-MD5 HTTP头信息。这意味着parameters对象将被忽略。如果需要同时上传参数和文件,请使用下一节中描述的multipart/form-data上传方法。

使用multipart/form-data方式上传数据

除了直接上传原始数据外,还可以通过将一个WebApiResource实例传递到WebApiClient API中的data参数来实现multipart/form-data附件编码上传,并将路由配置为具有form类型的序列化。

{
  "webservice" : {
    "api" : {
      "trim" : {
        "method" : "POST",
        "path" : "upload/image",
        "serializationName" : "form"
      }
    }
  }
}

如果提供了WebApiClient API的parameters对象,它也将包含在请求中,并按照请求体的其他部分序列化。

下载原始数据

您可以通过添加一个具有真值的saveAsResource属性,将响应数据保存到文件中,而不是默认将响应加载到RAM中。

{
  "webservice" : {
    "api" : {
      "download" : {
        "method" : "GET",
        "path" : "download/image",
        "saveAsResource" : true
      }
    }
  }
}

然后,在WebApiResponse中返回的responseObject将是一个WebApiResource,您可以将其移动到所需的位置,例如

[client requestAPI:@"download" withPathVariables:nil parameters:nil data:nil
             queue:dispatch_get_main_queue()
          progress:nil
          finished:^(id<WebApiResponse> response, NSError *error) {
    if ( !error ) {
        id<WebApiResource> resource = response.responseObject;
        NSURL *dest = [NSURL fileURLWithPath:@"/some/path"];
        [[NSFileManager defaultManager] moveItemAtURL:[resource URLValue] toURL:dest error:nil];
    }
}];

模块:缓存

缓存模块通过提供可以使用PINCache缓存结果对象的PINCacheWebApiClient代理,为WebApiClient API提供响应缓存支持。通过在路由上配置cacheTTL属性来启用缓存支持,例如

{
  "webservice" : {
    "api" : {
      "info" : {
        "method" : "GET",
        "path" : "infrequentlyupdated/info",
        "cacheTTL" : 3600
      }
    }
  }
}

有关路由缓存的更多详细信息,请参阅CachingWebApiRoute协议。

会使得其他路由的缓存数据失效的路由

您还可以配置路由,使得它失效任何针对 其他 路由的缓存数据。一个很好的例子是,当您定义一个返回对象列表的 列表 路由,还有一个添加对象到该列表的 添加 路由时。我们可以使后者路由失效前者路由的缓存数据,如下所示

{
  "webservice" : {
    "api" : {
      "list" : {
        "method" : "GET",
        "path" : "stuff/list",
        "cacheTTL" : 3600
      },
      "add" : {
        "method" : "PUT",
        "path" : "stuff/:thingId",
        "invalidatesCachedRouteNames" : [ "list" ]
      }
    }
  }
}

invalidatesCachedRouteNames 被配置为一个数组,当这些路由成功调用时,应失效的路线名称。

忽略路由URL查询参数

默认情况下,在计算每个路由的缓存密钥时,会包括URL查询参数。有时忽略查询参数可能会有用。例如,Amazon S3资源的预签名URL包含每次请求相同的资源时都会改变的授权查询参数。通过使用cacheIgnoreQueryParameters = YES配置该路由,那么对该路由的查询参数将不包含在请求的缓存键中。

{
  "webservice" : {
    "api" : {
      "info" : {
        "method" : "GET",
        "path" : "https://s3.amazon.com/info/foo.txt",
        "cacheTTL" : 3600,
        "cacheIgnoreQueryParameters" : true
      }
    }
  }
}

支持多用户的缓存组

PINCacheWebApiClient支持一个可以在运行时改变的keyDiscriminator属性,可以将所有缓存路由数据隔离到组中。这个功能的主要使用场景是支持多用户应用,其中路由URL不包含识别用户的参数,因此当不同用户登录时,他们不会看到其他用户的缓存数据。您可以将活动用户的唯一标识符分配给keyDiscriminator属性,然后所有缓存数据都变为了特定用户。当用户注销并登录不同用户时,更改keyDiscriminator为新的用户标识符。

检查缓存数据

CachingWebApiClient API增加了一个方法,可以用来测试缓存数据是否可用。

- (void)requestCachedAPI:(NSString *)name
       withPathVariables:(nullable id)pathVariables
              parameters:(nullable id)parameters
                   queue:(dispatch_queue_t)callbackQueue
                finished:(void (^)(id<WebApiResponse> _Nullable response, NSError * _Nullable error))callback;

只有当数据已经在缓存中时,回调才会传递回响应。有时知道一些内容已经被下载是有用的!

模块:RestKit

RestKit 模块为基于 RestKitWebApiClient API 提供了 对象映射 实现方式。该模块能够将本地对象转换为 JSON 格式,反之亦然。这个模块仅使用 RestKit/ObjectMapping 模块,因此不会与 AFNetworking 2 冲突。实际上,WebApiClient 的创建部分动机是能够使用 AFNetworking 2 和 RestKit 的对象映射支持,因为 RestKit 的网络层是基于 AFNetworking 1 的。在某种程度上,WebApiClient API 提供的功能与完整 RestKit 项目提供的功能相似。

映射配置

RestKitWebApiDataMapper 类支持一个共享的单例模式,应用程序可以在启动时配置任何必需的 RKObjectMapping 对象。您可以这样配置它

RestKitWebApiDataMapper *dataMapper = [RestKitWebApiDataMapper sharedDataMapper];

// get RestKit mapper for user objects
RKObjectMapper *userObjectMapper = ...;

// register user mapper for requests and responses
[dataMapper registerRequestObjectMapping:[userObjectMapper inverseMapping] forRouteName:@"login"];
[dataMapper registerResponseObjectMapping:userObjectMapper forRouteName:@"login"];

基于块的编码和映射

RestKitWebApiDataMapper 类还支持基于块的映射钩子,既可以用于请求编码,也可以用于响应映射。这些块执行 已配置的 RKObjectMapper 对请求或响应数据进行处理之后。

此支持的一个有用示例是连接隐含由数据指示的父子关系属性。想象一个具有子 Person 对象数组的 Person 类,每个子对象都有一个指向其父 Person 实例的 parent 属性

@interface Person : NSObject

@property (strong) NSString *name;
@property (strong) NSArray<Person *> *children;
@property (weak) Person *parent;

@end

服务器返回的 JSON 如下所示

{
  "name" : "John Doe",
  "children" : [
    { "name" : "Johnny Doe" },
    { "name" : "Jane Doe" }
  ]
}

要使用新分配的 John Doe 对象填充每个子对象的 parent 属性,可以配置响应映射块如下

[dataMapper registerResponseMappingBlock:^(id sourceObject, id<WebApiRoute> route, NSError * __autoreleasing *error) {
	if ( [sourceObject isKindOfClass:[Person class]] ) {
		// populate the Child -> Parent relationship
		Person *parent = sourceObject;
		for ( Person *child in parent.children ) {
			child.parent = parent;
		}
	}
	return sourceObject;
} forRouteName:@"parent-child-tree"];

路由配置

要使用基于RestKit的对象映射与路由,你需要将路由的dataMapper属性配置为RestKitWebApiDataMapper,如下所示

{
  "webservice" : {
    "api" : {
      "login" : {
        "method" : "POST",
        "path" : "user/login",
        "dataMapper" : "RestKitWebApiDataMapper"
      }
    }
  }
}

有时请求或响应的JSON需要嵌套在某个顶级对象中。例如,想象一下,注册端点期望将用户对象以如下JSON格式发布

{
  "user" : { "email" : "[email protected]", "name" : "Joe" }
}

可以通过添加一个dataMapperRequestRootKeyPath属性(或用于映射响应的dataMapperResponseRootKeyPath)来实现,如下所示

{
  "webservice" : {
    "api" : {
      "login" : {
        "method" : "POST",
        "path" : "user/login",
        "dataMapper" : "RestKitWebApiDataMapper",
        "dataMapperRequestRootKeyPath" : "user"
      }
    }
  }
}

模块:UI

UI模块提供了一些UI实用工具,例如WebApiClientActivitySupport类,它可以监听路由请求,对于那些指定了具有布尔值的preventUserInteraction的路由,会抛出一个全屏的“请求时间过长”视窗,让应用程序的用户知道正在等待响应。例如,可以配置如下路由

{
  "webservice" : {
    "api" : {
      "login" : {
        "method" : "POST",
        "path" : "user/login",
        "preventUserInteraction" : true
      }
    }
  }
}