Files
keyboard/keyBoard/Class/Network/KBNetworkManager.m
2025-10-31 16:06:54 +08:00

170 lines
8.0 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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