170 lines
8.0 KiB
Objective-C
170 lines
8.0 KiB
Objective-C
//
|
||
// KBNetworkManager.m
|
||
// CustomKeyboard
|
||
//
|
||
|
||
#import "KBNetworkManager.h"
|
||
#import "AFNetworking.h"
|
||
#import "KBAuthManager.h"
|
||
|
||
NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
|
||
|
||
@interface KBNetworkManager ()
|
||
@property (nonatomic, strong) AFHTTPSessionManager *manager; // AFN 管理器(ephemeral 配置)
|
||
// 私有错误派发
|
||
- (void)fail:(KBNetworkError)code completion:(KBNetworkCompletion)completion;
|
||
@end
|
||
|
||
@implementation KBNetworkManager
|
||
|
||
+ (instancetype)shared {
|
||
static KBNetworkManager *m; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ m = [KBNetworkManager new]; });
|
||
return m;
|
||
}
|
||
|
||
- (instancetype)init {
|
||
if (self = [super init]) {
|
||
_enabled = NO; // 键盘扩展默认无网络能力,需外部显式开启
|
||
_timeout = 10.0;
|
||
_defaultHeaders = @{ @"Accept": @"application/json" };
|
||
// 设置基础域名,路径可相对该地址拼接
|
||
_baseURL = [NSURL URLWithString:KB_BASE_URL];
|
||
}
|
||
return self;
|
||
}
|
||
|
||
#pragma mark - Public
|
||
|
||
- (NSURLSessionDataTask *)GET:(NSString *)path
|
||
parameters:(NSDictionary *)parameters
|
||
headers:(NSDictionary<NSString *,NSString *> *)headers
|
||
completion:(KBNetworkCompletion)completion {
|
||
if (![self ensureEnabled:completion]) return nil;
|
||
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
|
||
jsonBody:(id)jsonBody
|
||
headers:(NSDictionary<NSString *,NSString *> *)headers
|
||
completion:(KBNetworkCompletion)completion {
|
||
if (![self ensureEnabled:completion]) return nil;
|
||
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
|
||
|
||
- (BOOL)ensureEnabled:(KBNetworkCompletion)completion {
|
||
if (!self.isEnabled) {
|
||
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorDisabled userInfo:@{NSLocalizedDescriptionKey: @"网络未启用(可能未开启完全访问)"}];
|
||
if (completion) completion(nil, nil, e);
|
||
return NO;
|
||
}
|
||
return YES;
|
||
}
|
||
|
||
- (NSString *)buildURLStringWithPath:(NSString *)path {
|
||
if (path.length == 0) return nil;
|
||
if ([path hasPrefix:@"http://"] || [path hasPrefix:@"https://"]) {
|
||
return path;
|
||
}
|
||
if (self.baseURL) {
|
||
return [NSURL URLWithString:path relativeToURL:self.baseURL].absoluteURL.absoluteString;
|
||
}
|
||
return path; // 当无 baseURL 且 path 不是完整 URL 时,让 AFN 处理(可能失败)
|
||
}
|
||
|
||
- (void)applyHeaders:(NSDictionary<NSString *,NSString *> *)headers toMutableRequest:(NSMutableURLRequest *)req contentType:(NSString *)contentType {
|
||
// 合并默认头与局部头,并注入授权头(若可用)。局部覆盖优先。
|
||
NSMutableDictionary *all = [self.defaultHeaders mutableCopy] ?: [NSMutableDictionary new];
|
||
NSDictionary *auth = [[KBAuthManager shared] authorizationHeader];
|
||
[auth enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { all[key] = obj; }];
|
||
if (contentType) all[@"Content-Type"] = contentType;
|
||
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { all[key] = obj; }];
|
||
[all enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { [req setValue:obj forHTTPHeaderField:key]; }];
|
||
}
|
||
|
||
- (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; }
|
||
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) {
|
||
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);
|
||
}
|
||
}];
|
||
[task resume];
|
||
return task;
|
||
}
|
||
|
||
#pragma mark - AFHTTPSessionManager
|
||
|
||
- (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; }
|
||
_manager = [[AFHTTPSessionManager alloc] initWithBaseURL:nil sessionConfiguration:cfg];
|
||
// 默认不使用 JSON 解析器,保持原生数据,再按需解析
|
||
_manager.responseSerializer = [AFHTTPResponseSerializer serializer];
|
||
}
|
||
return _manager;
|
||
}
|
||
|
||
#pragma mark - Private helpers
|
||
|
||
- (void)fail:(KBNetworkError)code completion:(KBNetworkCompletion)completion {
|
||
NSString *msg = @"网络错误";
|
||
switch (code) {
|
||
case KBNetworkErrorDisabled: msg = @"网络未启用(可能未开启完全访问)"; break;
|
||
case KBNetworkErrorInvalidURL: msg = @"无效的URL"; break;
|
||
case KBNetworkErrorInvalidResponse: msg = @"无效的响应"; break;
|
||
case KBNetworkErrorDecodeFailed: msg = @"解析失败"; break;
|
||
default: break;
|
||
}
|
||
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
|
||
code:code
|
||
userInfo:@{NSLocalizedDescriptionKey: msg}];
|
||
if (completion) completion(nil, nil, e);
|
||
}
|
||
|
||
@end
|