diff --git a/CustomKeyboard/Network/KBNetworkManager.m b/CustomKeyboard/Network/KBNetworkManager.m index ad5e78e..b507b98 100644 --- a/CustomKeyboard/Network/KBNetworkManager.m +++ b/CustomKeyboard/Network/KBNetworkManager.m @@ -4,11 +4,12 @@ // #import "KBNetworkManager.h" +#import 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 *)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 *)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 *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 *)headers toRequest:(NSMutableURLRequest *)req contentType:(NSString *)contentType { +- (void)applyHeaders:(NSDictionary *)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