本文主要是讲一下我们在请求网络的时候如何去配置相关的参数然后成功得到响应。那本文以请求百度的API中图像识别为例来说一下该如何去配置。
目的需求
我需要把一张包含二维码的图片通过百度的图像识别接口来判定是否包含二维码。
准备工作
1>装有Xcode的MAC电脑一台
2>准备一张二维码图片,这个随便找一张,或者去草料二维码制作一张也是OK的
3>百度云登录注册,并创建一个图像识别应用,拿到API Key和Secret Key
4>百度对接文档:图像审核组合接口
开始对接
1>创建工程项目,然后创建一个网络请求。那么此处我选择AFN,并且是手动导入框架。首先这个过程是要先通过API Key和Secret Key获取AccessToken,然后再通过AccessToken再将图片通过POST请求发送给百度鉴别,然后返回结果。
Snip20170812_28.png获取AccessToken的代码:
- (void)viewDidLoad {
[super viewDidLoad];
[self addBtn];
}
- (void)addBtn{
UIButton *btn = [[UIButton alloc]init];
btn.frame = CGRectMake(0, 300, 200, 50);
CGFloat btnCenterX = self.view.center.x;
CGFloat btnCenterY = btn.frame.origin.y + btn.frame.size.height * 0.5;
CGPoint btnCenter = CGPointMake(btnCenterX, btnCenterY);
btn.center = btnCenter;
btn.backgroundColor = [UIColor orangeColor];
[self.view addSubview:btn];
[btn setTitle:@"点我获取Token" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(didClickBtn) forControlEvents:UIControlEventTouchUpInside];
}
- (void)didClickBtn{
[self getAccessToken];
}
- (void)getAccessToken{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//一但用了这个返回的那个responseObject就是NSData,如果不用就是简单的
//manager.responseSerializer = [AFHTTPResponseSerializer serializer];
//manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html",@"image/jpeg",@"text/plain", nil];
NSDictionary *dict = @{
@"grant_type":@"client_credentials",
@"client_id":@"填写你的API Key",
@"client_secret":@"填写你的Secret Key"
};
[manager POST:@"https://aip.baidubce.com/oauth/2.0/token" parameters:dict progress:nil success:
^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
/*
if ([responseObject isKindOfClass:[NSData class]]) {
NSError *error;
id object = [NSJSONSerialization JSONObjectWithData:responseObject options:0 error:&error];
NSLog(@"%@",object);
}
if (![responseObject isKindOfClass:[NSDictionary class]]) {
NSLog(@"不是想要的结果,应该是哪里出错了,请注意检查!");
NSLog(@"responseObject = %@",responseObject);
return;
}
*/
NSDictionary *dict = (NSDictionary *)responseObject;
NSString *accessToken = dict[@"access_token"];
_accessToken = dict[@"access_token"];
NSLog(@"%@",accessToken);
//调用判定二维码的接口
[self getResultOfQRImageWithAFN1];
//[self getResultOfQRImageWithAFN2];
//[self getResultOfQRImageWithNSURLSession];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"请求失败--%@",error);
}];
}
PS :注意以下有一点要说一下
下面这句代码意思是得到的响应会序列化,何为序列化,序列化就是将其他的数据类型转成二进制,所以一旦我们加上了这句话,那么我们在接收响应的时候,就要反序列化,将二进制的NSData转成其他响应的数据类型
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
2>获取AccessToken之后就需要去请求图像识别接口了
Snip20170812_26.png Snip20170812_27.png Snip20170812_29.png首先请求是POST,我们再说下以下几个要点:
要点一:
参数中有URL参数,那么这个URL参数是直接拼接在URL后面的,这个要注意
要点二:
其中说到的Header表示的是我们传给百度接口的是内容Content-Type是Json格式的,并且是UTF-8编码格式的,所以这个要特别注意,很多请求失败可能是这些设置没有设置好才导致的application/json;charset=utf-8
要点三:
请求体应该怎么放,在AFN中如果是使用POST的URL方式的接口中的parameters这个参数其实就是请求体,并且这是id类型的,只要我们设置好请求的内容格式然后这个参数传入对应格式的内容即可
PS: 这里面就涉及到请求体,那怎么设置请求体呢在AFN中,有以下的三种方式:
方式一:
//这个是用Request方式
- (void)getResultOfQRImageWithAFN1{
//由于是POST,将一些凭证如AccessToken之类的拼接在一起,这种叫URL参数,同GET一样
NSString *urlString = @"https://aip.baidubce.com/api/v1/solution/direct/img_censor";
NSString *newURLString = [NSString stringWithFormat:@"%@?access_token=%@?",urlString,_accessToken];
NSURL *url = [NSURL URLWithString:newURLString];
//创建请求request
NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:30];
//设置请求方式为POST
request.HTTPMethod = @"POST";
//设置请求内容格式
[request setValue:@"application/json;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
//设置请求体参数
NSString *base64String = [self base64EncodeImageWithName:@"qrcode"];
NSArray *array = @[@"watermark"];
NSDictionary *params = @{@"image":base64String,
@"scenes": array};
//这是设置请求体,把参数放进请求体(这部分的参数也叫请求参数)
NSString *paramJsonStr = [KODJsonService dictionaryToJson:params];
NSLog(@"paramString = %@",paramJsonStr);
NSData *data = [paramJsonStr dataUsingEncoding:NSUTF8StringEncoding];
NSLog(@"data = %@",data);
request.HTTPBody = [paramJsonStr dataUsingEncoding:NSUTF8StringEncoding];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html",@"image/jpeg",@"text/plain", nil];
[[manager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
NSLog(@"请求成功---%@---%@",responseObject,[responseObject class]);
if (error) {
NSLog(@"%@",error);
return ;
}
NSDictionary *dict = (NSDictionary *)responseObject;
//以下结构没做放空处理,只为了尽快看到效果,请大家自行处理下
NSDictionary *resultDict = dict[@"result"];
if (!resultDict) {
NSLog(@"result没有值,很抱歉,估计错误了!");
return;
}
NSDictionary *newDict = resultDict[@"watermark"];
NSArray *resultArray = newDict[@"result"];
for (NSDictionary *dict in resultArray) {
if ([dict[@"type"] isEqualToString:@"QR code"]) {
NSLog(@"这绝壁有一张二维码!!!");
}
}
}] resume];
}
PS: 这种方式是用manager直接通过request的方式去请求而不是设置URL的方式,另外request是可以设置HTTPBody的,这样更加的清晰
方式二:
//这个是用URL方式的
- (void)getResultOfQRImageWithAFN2{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//这句话加了之后返回的responseObject就是JSONData了,如果不加那就是正常的JSON可以直接转成字典然后操作
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager.requestSerializer setValue:@"application/json;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html",@"image/jpeg",@"text/plain", nil];
NSString *urlString = @"https://aip.baidubce.com/api/v1/solution/direct/img_censor";
NSString *newURLString = [NSString stringWithFormat:@"%@?access_token=%@?",urlString,_accessToken];
NSString *base64String = [self base64EncodeImageWithName:@"qrcode"];
NSArray *array = @[@"watermark"];
NSDictionary *params = @{@"image":base64String,
@"scenes": array};
[manager POST:newURLString parameters:params progress:nil success:
^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"请求成功---%@---%@",responseObject,[responseObject class]);
NSLog(@"成功!!!");
NSError *error;
id object = [NSJSONSerialization JSONObjectWithData:responseObject options:0 error:&error];
if ([object isKindOfClass:[NSDictionary class]]) {
NSDictionary *response = (NSDictionary *)object;
NSLog(@"%@",response);
//以下结构没做放空处理
NSDictionary *resultDict = response[@"result"];
if (!resultDict) {
NSLog(@"result没有值,很抱歉,估计错误了!");
return;
}
NSDictionary *newDict = resultDict[@"watermark"];
NSArray *resultArray = newDict[@"result"];
for (NSDictionary *dict in resultArray) {
if ([dict[@"type"] isEqualToString:@"QR code"]) {
NSLog(@"这绝壁有一张二维码!!!");
}
}
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"请求失败--%@",error);
}];
}
PS: 大家要注意一下以下这两句代码的作用,第一句是设置请求上传内容是Json格式的,我试了以下如果单单设置第二句是会返回400Bad Request错误的,如果仅仅设置第一句是OK的
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager.requestSerializer setValue:@"application/json;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
我看了下AFN的内部实现其实已经设置好了,大家有兴趣可以去查看一下AFN的内部源码
+ (instancetype)serializer {
return [self serializerWithWritingOptions:(NSJSONWritingOptions)0];
}
+ (instancetype)serializerWithWritingOptions:(NSJSONWritingOptions)writingOptions
{
AFJSONRequestSerializer *serializer = [[self alloc] init];
serializer.writingOptions = writingOptions;
return serializer;
}
最终都会调用
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
return [super requestBySerializingRequest:request withParameters:parameters error:error];
}
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
if (parameters) {
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]];
}
return mutableRequest;
}
所以看到这里你会发现AFN封装了好大一圈,一会儿在文末贴出纯系统NSURLSession怎么实现
另外有两个AFHTTPRequestSerializer的子类,一个是AFJSONRequestSerializer,这个是会把参数编码成Json格式,然后设置Content-Type为application/json,但是并没有进行UTF-8编码,所以这个还是要注意一下自己编码
/**
`AFJSONRequestSerializer` is a subclass of `AFHTTPRequestSerializer` that encodes parameters as JSON using `NSJSONSerialization`, setting the `Content-Type` of the encoded request to `application/json`.
*/
@interface AFJSONRequestSerializer : AFHTTPRequestSerializer
另一个是AFPropertyListRequestSerializer,这个可以猜一下,其实就是XML,并且设置为application/x-plist
/**
`AFPropertyListRequestSerializer` is a subclass of `AFHTTPRequestSerializer` that encodes parameters as JSON using `NSPropertyListSerializer`, setting the `Content-Type` of the encoded request to `application/x-plist`.
*/
@interface AFPropertyListRequestSerializer : AFHTTPRequestSerializer
方式三:
- (void)getResultOfQRImageWithAFN3{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//这句话加了之后返回的responseObject就是JSONData了,如果不加那就是正常的JSON可以直接转成字典然后操作
//manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager.requestSerializer setValue:@"application/json;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html",@"image/jpeg",@"text/plain", nil];
NSString *urlString = @"https://aip.baidubce.com/api/v1/solution/direct/img_censor";
NSString *newURLString = [NSString stringWithFormat:@"%@?access_token=%@?",urlString,_accessToken];
NSString *base64String = [self base64EncodeImageWithName:@"qrcode"];
NSArray *array = @[@"watermark"];
NSDictionary *params = @{@"image":base64String,
@"scenes": array};
NSMutableURLRequest *request = [manager.requestSerializer requestWithMethod:@"POST" URLString:newURLString parameters:params error:nil];
[[manager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
NSLog(@"%@",error);
NSLog(@"%@",responseObject);
}] resume];
}
对比原生NSURLSession的做法
-(void)getResultOfQRImageWithNSURLSession
{
NSString *urlString = @"https://aip.baidubce.com/api/v1/solution/direct/img_censor";
NSString *newURLString = [NSString stringWithFormat:@"%@?access_token=%@?",urlString,_accessToken];
NSURL *url = [NSURL URLWithString:newURLString];
NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:30];
request.HTTPMethod = @"POST";
[request setValue:@"application/json;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
NSString *base64String = [self base64EncodeImageWithName:@"qrcode"];
NSArray *array = @[@"watermark"];
NSDictionary *params = @{@"image":base64String,
@"scenes": array};
NSString *paramJsonStr = [KODJsonService dictionaryToJson:params];
NSLog(@"paramString = %@",paramJsonStr);
NSData *data = [paramJsonStr dataUsingEncoding:NSUTF8StringEncoding];
NSLog(@"data = %@",data);
request.HTTPBody = [paramJsonStr dataUsingEncoding:NSUTF8StringEncoding];
// 3.获得会话对象
NSURLSession *session = [NSURLSession sharedSession];
// 4.根据会话对象,创建一个Task任务
NSURLSessionDataTask *sessionDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//判断statusCode
NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;
if (!(res.statusCode == 200 || error)) {
NSLog(@"失败!!!");
return;
}
NSLog(@"从服务器获取到数据");
if (error) {
NSLog(@"error:%@",error.description);
return ;
}
NSLog(@"%@",data);
NSError *newError;
id object = [NSJSONSerialization JSONObjectWithData:data options:0 error:&newError];
if ([object isKindOfClass:[NSDictionary class]]) {
NSDictionary *response = (NSDictionary *)object;
NSLog(@"%@",response);
//这里记得把这个值取出来,很深,哈哈哈...
}
}];
//5.最后一步,执行任务,(一定调用这句话)
[sessionDataTask resume];
}
附件
其中用到了一个私有方法:
- (NSString *)base64EncodeImageWithName:(NSString *)name{
UIImage *image = [UIImage imageNamed:name];
NSData *data = UIImagePNGRepresentation(image);
NSData *base64Data = [data base64EncodedDataWithOptions:0];
NSString *baseString = [[NSString alloc]initWithData:base64Data encoding:NSUTF8StringEncoding];
return baseString;
}
//这是我的一个工具类里面的方法,大家可以改成对象方法直接替换调用即可
+ (NSString*)dictionaryToJson:(NSDictionary *)dic
{
if (dic.allKeys.count == 0){
#ifdef DSDUBUG
NSLog(@"%@---%s",self.class,__FUNCTION__);
NSLog(@"您传入的字典为空,无法转换,请确保字典不为空!!!");
#endif
return nil;
}
NSError *parseError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:&parseError];
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
图片:
qrcode.png文末总结
其实今天就讲了一个很重要的问题,设置请求体,并且设置请求的内容格式的问题。另外一个是我们其实可以对系统的方法NSURLSession进行封装,系统的已经很好了,其实有时间的话我们可以针对于我们的业务自己封装会更好,因为AFN封装了一层又一层太绕了...
AFN中的如果是GET请求,用URL方式的那个接口中的parameters参数其实就是我们需要拼接在URL后面的参数,所以这个通常只需要转一下UTF-8就好
AFN中的如果是POST请求,用URL方式的那个接口中的parameters参数其实就是我们需要上传给后台的参数,因为要上传所以需要设置一下内容格式,一般上传是上传Json,也有上传Form表单数据的比如图片等等;另外这个URL参数就需要跟URL做一下拼接然后整体放置在以下接口中的newURLString:
[manager POST:newURLString parameters:params progress:nil success:
^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//code here...
}];
以上!!!
网友评论