处理网络请求
This commit is contained in:
@@ -4,11 +4,12 @@
|
||||
//
|
||||
|
||||
#import "KBNetworkManager.h"
|
||||
#import <AFNetworking/AFNetworking.h>
|
||||
|
||||
NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
|
||||
|
||||
@interface KBNetworkManager ()
|
||||
@property (nonatomic, strong) NSURLSession *session; // 懒创建,ephemeral 配置
|
||||
@property (nonatomic, strong) AFHTTPSessionManager *manager; // AFN 管理器(ephemeral 配置)
|
||||
// 私有错误派发
|
||||
- (void)fail:(KBNetworkError)code completion:(KBNetworkCompletion)completion;
|
||||
@end
|
||||
@@ -36,12 +37,17 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
|
||||
headers:(NSDictionary<NSString *,NSString *> *)headers
|
||||
completion:(KBNetworkCompletion)completion {
|
||||
if (![self ensureEnabled:completion]) return nil;
|
||||
NSURL *url = [self buildURLWithPath:path params:parameters];
|
||||
if (!url) { [self fail:KBNetworkErrorInvalidURL completion:completion]; return nil; }
|
||||
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:self.timeout];
|
||||
req.HTTPMethod = @"GET";
|
||||
[self applyHeaders:headers toRequest:req contentType:nil];
|
||||
return [self startTaskWithRequest:req completion:completion];
|
||||
NSString *urlString = [self buildURLStringWithPath:path];
|
||||
if (!urlString) { [self fail:KBNetworkErrorInvalidURL completion:completion]; return nil; }
|
||||
// 使用 AFHTTPRequestSerializer 生成带参数与头的请求
|
||||
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
|
||||
serializer.timeoutInterval = self.timeout;
|
||||
NSMutableURLRequest *req = [serializer requestWithMethod:@"GET"
|
||||
URLString:urlString
|
||||
parameters:parameters
|
||||
error:NULL];
|
||||
[self applyHeaders:headers toMutableRequest:req contentType:nil];
|
||||
return [self startAFTaskWithRequest:req completion:completion];
|
||||
}
|
||||
|
||||
- (NSURLSessionDataTask *)POST:(NSString *)path
|
||||
@@ -49,21 +55,19 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
|
||||
headers:(NSDictionary<NSString *,NSString *> *)headers
|
||||
completion:(KBNetworkCompletion)completion {
|
||||
if (![self ensureEnabled:completion]) return nil;
|
||||
NSURL *url = [self buildURLWithPath:path params:nil];
|
||||
if (!url) { [self fail:KBNetworkErrorInvalidURL completion:completion]; return nil; }
|
||||
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:self.timeout];
|
||||
req.HTTPMethod = @"POST";
|
||||
NSData *body = nil;
|
||||
if (jsonBody) {
|
||||
if ([NSJSONSerialization isValidJSONObject:jsonBody]) {
|
||||
body = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:NULL];
|
||||
} else if ([jsonBody isKindOfClass:[NSData class]]) {
|
||||
body = (NSData *)jsonBody; // 允许直接传入 NSData
|
||||
}
|
||||
}
|
||||
if (body) req.HTTPBody = body;
|
||||
[self applyHeaders:headers toRequest:req contentType:(body ? @"application/json" : nil)];
|
||||
return [self startTaskWithRequest:req completion:completion];
|
||||
NSString *urlString = [self buildURLStringWithPath:path];
|
||||
if (!urlString) { [self fail:KBNetworkErrorInvalidURL completion:completion]; return nil; }
|
||||
// 用 JSON 序列化器生成 JSON Body 的请求
|
||||
AFJSONRequestSerializer *serializer = [AFJSONRequestSerializer serializer];
|
||||
serializer.timeoutInterval = self.timeout;
|
||||
NSError *error = nil;
|
||||
NSMutableURLRequest *req = [serializer requestWithMethod:@"POST"
|
||||
URLString:urlString
|
||||
parameters:jsonBody
|
||||
error:&error];
|
||||
if (error) { if (completion) completion(nil, nil, error); return nil; }
|
||||
[self applyHeaders:headers toMutableRequest:req contentType:nil];
|
||||
return [self startAFTaskWithRequest:req completion:completion];
|
||||
}
|
||||
|
||||
#pragma mark - Core
|
||||
@@ -77,28 +81,18 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSURL *)buildURLWithPath:(NSString *)path params:(NSDictionary *)params {
|
||||
NSURL *url = nil;
|
||||
- (NSString *)buildURLStringWithPath:(NSString *)path {
|
||||
if (path.length == 0) return nil;
|
||||
if ([path hasPrefix:@"http://"] || [path hasPrefix:@"https://"]) {
|
||||
url = [NSURL URLWithString:path];
|
||||
} else if (self.baseURL) {
|
||||
url = [NSURL URLWithString:path relativeToURL:self.baseURL].absoluteURL;
|
||||
return path;
|
||||
}
|
||||
if (!url) return nil;
|
||||
if (params.count == 0) return url;
|
||||
NSURLComponents *c = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES];
|
||||
NSMutableArray<NSURLQueryItem *> *items = c.queryItems.mutableCopy ?: [NSMutableArray array];
|
||||
[params enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
NSString *k = [key description];
|
||||
NSString *v = [obj isKindOfClass:[NSString class]] ? obj : [obj description];
|
||||
[items addObject:[NSURLQueryItem queryItemWithName:k value:v]];
|
||||
}];
|
||||
c.queryItems = items;
|
||||
return c.URL;
|
||||
if (self.baseURL) {
|
||||
return [NSURL URLWithString:path relativeToURL:self.baseURL].absoluteURL.absoluteString;
|
||||
}
|
||||
return path; // 当无 baseURL 且 path 不是完整 URL 时,让 AFN 处理(可能失败)
|
||||
}
|
||||
|
||||
- (void)applyHeaders:(NSDictionary<NSString *,NSString *> *)headers toRequest:(NSMutableURLRequest *)req contentType:(NSString *)contentType {
|
||||
- (void)applyHeaders:(NSDictionary<NSString *,NSString *> *)headers toMutableRequest:(NSMutableURLRequest *)req contentType:(NSString *)contentType {
|
||||
// 合并默认头与局部头,局部覆盖
|
||||
NSMutableDictionary *all = [self.defaultHeaders mutableCopy] ?: [NSMutableDictionary new];
|
||||
if (contentType) all[@"Content-Type"] = contentType;
|
||||
@@ -106,24 +100,25 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
|
||||
[all enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { [req setValue:obj forHTTPHeaderField:key]; }];
|
||||
}
|
||||
|
||||
- (NSURLSessionDataTask *)startTaskWithRequest:(NSURLRequest *)req completion:(KBNetworkCompletion)completion {
|
||||
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:req
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
- (NSURLSessionDataTask *)startAFTaskWithRequest:(NSURLRequest *)req completion:(KBNetworkCompletion)completion {
|
||||
// 响应先用原始数据返回,再按 Content-Type 解析 JSON(与原实现一致)
|
||||
self.manager.responseSerializer = [AFHTTPResponseSerializer serializer];
|
||||
NSURLSessionDataTask *task = [self.manager dataTaskWithRequest:req uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
|
||||
if (error) { if (completion) completion(nil, response, error); return; }
|
||||
if (!data) { if (completion) completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorInvalidResponse userInfo:@{NSLocalizedDescriptionKey:@"空响应"}]); return; }
|
||||
id json = nil;
|
||||
// 简单判断是否为 JSON
|
||||
NSData *data = (NSData *)responseObject;
|
||||
if (![data isKindOfClass:[NSData class]]) {
|
||||
if (completion) completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorInvalidResponse userInfo:@{NSLocalizedDescriptionKey:@"无数据"}]);
|
||||
return;
|
||||
}
|
||||
NSString *ct = nil;
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
ct = ((NSHTTPURLResponse *)response).allHeaderFields[@"Content-Type"];
|
||||
}
|
||||
BOOL looksJSON = (ct && [ct.lowercaseString containsString:@"application/json"]);
|
||||
if (looksJSON) {
|
||||
json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
|
||||
if (!json && completion) {
|
||||
completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorDecodeFailed userInfo:@{NSLocalizedDescriptionKey:@"JSON解析失败"}]);
|
||||
return;
|
||||
}
|
||||
NSError *jsonErr = nil;
|
||||
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];
|
||||
if (jsonErr) { if (completion) completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorDecodeFailed userInfo:@{NSLocalizedDescriptionKey:@"JSON解析失败"}]); return; }
|
||||
if (completion) completion(json, response, nil);
|
||||
} else {
|
||||
if (completion) completion(data, response, nil);
|
||||
@@ -133,18 +128,20 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
|
||||
return task;
|
||||
}
|
||||
|
||||
#pragma mark - Session
|
||||
#pragma mark - AFHTTPSessionManager
|
||||
|
||||
- (NSURLSession *)session {
|
||||
if (!_session) {
|
||||
- (AFHTTPSessionManager *)manager {
|
||||
if (!_manager) {
|
||||
NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration ephemeralSessionConfiguration];
|
||||
cfg.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
|
||||
cfg.timeoutIntervalForRequest = self.timeout;
|
||||
cfg.timeoutIntervalForResource = MAX(self.timeout, 30.0);
|
||||
if (@available(iOS 11.0, *)) { cfg.waitsForConnectivity = YES; }
|
||||
_session = [NSURLSession sessionWithConfiguration:cfg delegate:nil delegateQueue:nil];
|
||||
_manager = [[AFHTTPSessionManager alloc] initWithBaseURL:nil sessionConfiguration:cfg];
|
||||
// 默认不使用 JSON 解析器,保持原生数据,再按需解析
|
||||
_manager.responseSerializer = [AFHTTPResponseSerializer serializer];
|
||||
}
|
||||
return _session;
|
||||
return _manager;
|
||||
}
|
||||
|
||||
#pragma mark - Private helpers
|
||||
|
||||
Reference in New Issue
Block a user