Latest 1.5.0
Homepage https://github.com/bawn/LCNetwork
License MIT
Platforms ios 7.0, requires ARC
Dependencies AFNetworking
Frameworks CFNetwork
Authors

License MIT
Pod version
Platform info
Support

基于 AFNetworking 的封装,参考了YTKNetwork的实现方式,
接口类采用 @protocol 约束,接口类的创建和使用更清晰。已适配 AFNetworking 3.x

若遇到 Demo 闪退问题,请删除 APP 重新运行,另外感谢zdoz提供免费的测试接口。

功能

  1. 支持blockdelegate的回调方式
  2. 支持设置主、副两个服务器地址
  3. ~支持response缓存,基于TMCache~
  4. 支持统一的argument加工
  5. 支持统一的response加工
  6. 支持多个请求同时发送,并统一设置它们的回调
  7. 支持方便地设置有相互依赖的网络请求的发送
  8. 支持以类似于插件的形式显示HUD
  9. 支持获取请求的实时进度

最终在 ViewController 中调用一个接口请求的例子如下

Api2 *api2 = [[Api2 alloc] init];
api2.requestArgument = @{
                         @"lat" : @"34.345",
                         @"lng" : @"113.678"
                         };
[api2 startWithCompletionBlockWithSuccess:^(__kindof LCBaseRequest *request) {
    self.weather2.text = api2.responseJSONObject[@"Weather"];
} failure:NULL];

集成

CocoaPods:

pod 'LCNetwork'

使用

统一配置

LCNetworkConfig *config = [LCNetworkConfig sharedInstance];
config.mainBaseUrl = @"http://api.zdoz.net/";// 设置主服务器地址
config.viceBaseUrl = @"https://api.zdoz.net/";// 设置副服务器地址

创建接口调用类

每个请求都需要一个对应的类去执行,这样的好处是接口所需要的信息都集成到了这个API类的内部,不在暴露在Controller层。

创建一个API类需要继承LCBaseRequest类,并且遵守LCAPIRequest协议,下面是最基本的API类的创建。

Api1.h

#import <LCNetwork/LCBaseRequest.h>

@interface Api1 : LCBaseRequest<LCAPIRequest>

@end

Api1.m

#import "Api1.h"

@implementation Api1

// 接口地址
- (NSString *)apiMethodName{
    return @"getweather.aspx";
}

// 请求方式
- (LCRequestMethod)requestMethod{
    return LCRequestMethodGet;
}

@end

- (NSString *)apiMethodName- (LCRequestMethod)requestMethod 是 @required 方法,所以必须实现,这在一定程度上降低了因漏写方法而crash的概率。

参数设置

请求的参数可以在外部设置,例如:

Api2 *api2 = [[Api2 alloc] init];
api2.requestArgument = @{
                          @"lat" : @"34.345",
                          @"lng" : @"113.678"
                        };

如果不想把参数的 key 值暴露在外部,也可以在 API 类中自定义初始化方法,例如:

Api2.h

@interface Api2 : LCBaseRequest<LCAPIRequest>

- (instancetype)initWith:(NSString *)lat lng:(NSString *)lng;

@end

Api2.m

#import "Api2.h"

@implementation Api2

- (instancetype)initWith:(NSString *)lat lng:(NSString *)lng{
    self = [super init];
    if (self) {
                self.requestArgument = @{
                                 @"lat" : lat,
                                 @"lng" : lng
                                 };
    }
    return self;
}

直接在初始化方法中使用 self.requestArgument = @{@"lat" : lat, lng" : lng} 其实不妥,原因请参考 唐巧jymn_chen,如果想完全规避这样的问题,请参考demo中的实现

统一处理argumentresponse

这里需要用到另外一个协议 <LCProcessProtocol>,比如我们的每个请求需要添加一个关于版本的参数:

LCProcessFilter.h

#import "LCNetworkConfig.h"

@interface LCProcessFilter : NSObject <LCProcessProtocol>

@end

LCProcessFilter.m

#import "LCBaseRequest.h"

@implementation LCProcessFilter

- (NSDictionary *) processArgumentWithRequest:(NSDictionary *)argument{
     NSMutableDictionary *newParameters = [[NSMutableDictionary alloc] initWithDictionary:argument];
    [newParameters setObject:@"1.0.0" forKey:@"version"];
    return newParameters;
}

或者是处理类似于下面的 json 数据,服务端返回的数据结构都会带resultok 的 key

{
  "result": {
      "_id": "564a931dbbb03c7002a2c0f3",
      "name": "clover",
      "count": 0
    },

  "ok": true,
  "message" : "成功"
}

那么就需要实现 - (id) processResponseWithRequest:(id)response 方法

- (id) processResponseWithRequest:(id)response{
    if ([response[@"ok"] boolValue]) {
        return response[@"result"];
    }
    else{
        NSDictionary *userInfo = @{NSLocalizedDescriptionKey: response[@"message"]};
        return [NSError errorWithDomain:ErrorDomain code:0 userInfo:userInfo];
    }
}

也就是说当使用 api1.responseJSONObject 获取数据时,返回的直接是 result 对应的值,或者是错误信息。

LCProcessProtocol 协议里面还有另外一个方法用来标记接口的请求是否成功,例如以下返回数据代表成功,因为 ok 的值是 true

{
  "result": {
      "_id": "564a931dbbb03c7002a2c0f3",
      "name": "clover",
      "count": 0
    },

  "ok": true,
  "message" : "成功"
}

当然并不是所有的接口用 ok 的字段来代表成功与否,所以在这个方法就是用来自定义判断条件:

- (BOOL)isSuccess:(id)response{
    return [response[@"ok"] boolValue];
}

然后 api1.isSuccess 的返回值能用来判断接口的成功与否。

LCProcessFilter *filter = [[LCProcessFilter alloc] init];
config.processRule = filter;

当然,如果你某个接口的 response 你不想做统一的处理,可以在请求子类中实现

- (BOOL)ignoreUnifiedResponseProcess{
    return YES;
}

这样返回的 responseJSONObject 就是原始数据

multipart/form-data

通常我们会用到上传图片或者其他文件就需要用到 multipart/form-data,同样的只需要实现- (AFConstructingBlock)constructingBodyBlock;协议方法即可,比如

- (AFConstructingBlock)constructingBodyBlock {
    return ^(id<AFMultipartFormData> formData) {
        NSData *data = UIImageJPEGRepresentation([UIImage imageNamed:@"currentPageDot"], 0.9);
        NSString *name = @"image";
        NSString *formKey = @"image";
        NSString *type = @"image/jpeg";
        [formData appendPartWithFileData:data name:formKey fileName:name mimeType:type];
    };
}

对于多图上传可能还会需要知道进度情况,LCNetwork 在 1.1.0 版本之后提供了监听进度的方法,只需要调用

 (void)startWithBlockProgress:(void (^)(NSProgress *progress))progress
                       success:(void (^)(id request))success
                       failure:(void (^)(id request))failure;

或者 - (void)requestProgress:(NSProgress *)progress 的协议方法,下面是一个具体例子:

MultiImageUploadApi *multiImageUploadApi = [[MultiImageUploadApi alloc] init];
    multiImageUploadApi.images = @[[UIImage imageNamed:@"test"], [UIImage imageNamed:@"test1"]];
    [multiImageUploadApi startWithBlockProgress:^(NSProgress *progress) {
        NSLog(@"%f", progress.fractionCompleted);
    } success:^(id request) {

    } failure:NULL];

response 再加工

当类似于

{
  "result": {
      "_id": "564a931dbbb03c7002a2c0f3",
      "name": "clover",
      "count": 10
    },

  "ok": true,
  "message" : "成功"
}

这样的数据,如果已经对 response 做了统一的加工,比如成功后统一返回的数据是 result 中的数据,那么返回的也是一个 NSDictionary,可能无法满足需求,这时候再把数据交给 LCBaseRequest 子类处理再合适不过了。比如需要直接获取count值,那么只需要实现 - (id)responseProcess:(id)responseObject; 协议方法,具体如下

- (id)responseProcess:(id)responseObject{
    return responseObject[@"count"];
}

注意,不应该调用self.responseJSONObject作为处理数据,请使用responseObject来处理,当实现这个协议方法后,使用 api1.responseJSONObject 获取数据时,返回将是 count 的值。

添加 header

只需要实现- (NSDictionary *)requestHeaderValue方法即可,比如

- (NSDictionary *)requestHeaderValue{
    return @{@"Accept" : @"application/json"};
}

或者添加多个header

- (NSDictionary *)requestHeaderValue{
    return @{@"Accept" : @"application/json", @"Accept" : @"application/json; charset=utf-8" : @"Content-Type"};
}

关于如何使用 LCQueueRequest

当每个请求发起的时间不固定,但是又要监听所有这些请求都已经完成的场景下使用。

举个例子:比如用户需要上传多张图片,每上传完一张图片服务器会返回一个图片地址,最后点击“完成”按钮后把这些图片地址提交给服务器。因为每次用户上传图片的发起时间都不是固定的,有可能在点击“完成”按钮时有些图片还没上传完成,所以这时候就需要监听这些上传的请求是否全部都已经完成

初始化 LCQueueRequest,这个类也支持添加HUD(Loading视图)

    self.queueRequest = [[LCQueueRequest alloc] init];
    HQRequestAccessory *accessory = [[HQRequestAccessory alloc] initWithViewController:self];
    [self.queueRequest addAccessory:accessory];

每次上传图片的时候的调用的方法,使用- (void)addRequest:(LCBaseRequest *)request添加在并发队列中

- (void)uploadImage:(UIImage *)image imageInfo:(HQImageInfo *)imageInfo{
    HQSingleImageUploadApi *uploadApi = [[HQSingleImageUploadApi alloc] init];
    NSData *imageData = UIImageJPEGRepresentation(image, 0.6);
    uploadApi.uploadImageData = imageData;

    [uploadApi startWithBlockSuccess:^(__kindof LCBaseRequest *request) {
        //
    } failure:NULL];

    [self.queueRequest addRequest:uploadApi];
}

最后点击“完成”按钮,这时候会等待队列中的所有请求都完成后再执行,当然这时候HUD(Loading视图)也会正确显示

- (IBAction)doneButtonAction:(id)sender{
    [self.queueRequest allComplete:^{
        //
    }];
}

关于HUD

如何显示 "正在加载"的 HUD,请参考Demo中的 LCRequestAccessory

1.1.9 版本新增了,是否执行插件的功能,用于隐藏和显示HUD。比如第一次进入页面时调用以下代码请求数据并显示HUD

    self.userLikeApi = [[HQUserLikesApi alloc] init];
    HQRequestAccessory *requestAccessory = [[HQRequestAccessory alloc] initWithShowVC:self];
    [self.userLikeApi addAccessory:requestAccessory];
    @weakify(self);
    [self.userLikeApi startWithBlockSuccess:^(__kindof LCBaseRequest *request) {
       //
    } failure:NULL];

如果但是如果这时候有上拉加载更多数据功能时,一般情况下都不需要显示HUD,所以

- (void)loadMoreData{
    self.userLikeApi.invalidAccessory = YES;
    [self.userLikeApi startWithBlockSuccess:^(HQUserLikesApi *request) {
    //
    } failure:NULL];
}

设置invalidAccessory属性为YES即可

其他

Query

某些 POST 请求希望参数不放在 body(或者说是payload)里面,需要用Query来附带信息,那么就需要用到 queryArgument 属性,比如

unsubscribeChannelApi.queryArgument = @{@"token" : @"token1"};

那么token=token1就会拼接到 URL 的后面

原始数据

1.1.3版本提供了一个返回原始数据的属性rawJSONObject,用于需要获得原始数据但response又要加工的情况下

TODO

  • [x] response 加工可选功能,比如有些接口返回需要特殊处理,这时候就需要忽略统一的加工方式
  • [x] ~替换 Cache 库,由于 TMCache 不在维护~
  • [x] 适配 AFNetworking 3.0

Requirements

  • iOS 7 or higher
  • ARC

更新日志

Releases

FAQ

当请求失败时,如何获取错误信息中的json数据

[self.userLikeApi startWithBlockSuccess:^(__kindof LCBaseRequest *request) {
      //
    } failure:^(__kindof LCBaseRequest *request, NSError *error) {
        NSString* errResponse = [[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding];
NSLog(@"%@",errResponse);
    }];

License

MIT

Latest podspec

{
    "name": "LCNetwork",
    "version": "1.5.0",
    "summary": "u57fau4e8eAFNetworkingu7684u7f51u7edcu5e93u5c01u88c5",
    "homepage": "https://github.com/bawn/LCNetwork",
    "license": "MIT",
    "authors": {
        "bawn": "[email protected]"
    },
    "source": {
        "git": "https://github.com/bawn/LCNetwork.git",
        "tag": "1.5.0"
    },
    "platforms": {
        "ios": "7.0"
    },
    "requires_arc": true,
    "source_files": "LCNetwork/*.{h,m}",
    "dependencies": {
        "AFNetworking": []
    },
    "frameworks": "CFNetwork"
}

Pin It on Pinterest

Share This