处理网络请求

This commit is contained in:
2025-10-28 16:11:35 +08:00
parent f28f7de49d
commit 02dd204744

View File

@@ -4,11 +4,12 @@
// //
#import "KBNetworkManager.h" #import "KBNetworkManager.h"
#import <AFNetworking/AFNetworking.h>
NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
@interface KBNetworkManager () @interface KBNetworkManager ()
@property (nonatomic, strong) NSURLSession *session; // ephemeral @property (nonatomic, strong) AFHTTPSessionManager *manager; // AFN ephemeral
// //
- (void)fail:(KBNetworkError)code completion:(KBNetworkCompletion)completion; - (void)fail:(KBNetworkError)code completion:(KBNetworkCompletion)completion;
@end @end
@@ -36,12 +37,17 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
headers:(NSDictionary<NSString *,NSString *> *)headers headers:(NSDictionary<NSString *,NSString *> *)headers
completion:(KBNetworkCompletion)completion { completion:(KBNetworkCompletion)completion {
if (![self ensureEnabled:completion]) return nil; if (![self ensureEnabled:completion]) return nil;
NSURL *url = [self buildURLWithPath:path params:parameters]; NSString *urlString = [self buildURLStringWithPath:path];
if (!url) { [self fail:KBNetworkErrorInvalidURL completion:completion]; return nil; } if (!urlString) { [self fail:KBNetworkErrorInvalidURL completion:completion]; return nil; }
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:self.timeout]; // 使 AFHTTPRequestSerializer
req.HTTPMethod = @"GET"; AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
[self applyHeaders:headers toRequest:req contentType:nil]; serializer.timeoutInterval = self.timeout;
return [self startTaskWithRequest:req completion:completion]; 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 - (NSURLSessionDataTask *)POST:(NSString *)path
@@ -49,21 +55,19 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
headers:(NSDictionary<NSString *,NSString *> *)headers headers:(NSDictionary<NSString *,NSString *> *)headers
completion:(KBNetworkCompletion)completion { completion:(KBNetworkCompletion)completion {
if (![self ensureEnabled:completion]) return nil; if (![self ensureEnabled:completion]) return nil;
NSURL *url = [self buildURLWithPath:path params:nil]; NSString *urlString = [self buildURLStringWithPath:path];
if (!url) { [self fail:KBNetworkErrorInvalidURL completion:completion]; return nil; } if (!urlString) { [self fail:KBNetworkErrorInvalidURL completion:completion]; return nil; }
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:self.timeout]; // JSON JSON Body
req.HTTPMethod = @"POST"; AFJSONRequestSerializer *serializer = [AFJSONRequestSerializer serializer];
NSData *body = nil; serializer.timeoutInterval = self.timeout;
if (jsonBody) { NSError *error = nil;
if ([NSJSONSerialization isValidJSONObject:jsonBody]) { NSMutableURLRequest *req = [serializer requestWithMethod:@"POST"
body = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:NULL]; URLString:urlString
} else if ([jsonBody isKindOfClass:[NSData class]]) { parameters:jsonBody
body = (NSData *)jsonBody; // NSData error:&error];
} if (error) { if (completion) completion(nil, nil, error); return nil; }
} [self applyHeaders:headers toMutableRequest:req contentType:nil];
if (body) req.HTTPBody = body; return [self startAFTaskWithRequest:req completion:completion];
[self applyHeaders:headers toRequest:req contentType:(body ? @"application/json" : nil)];
return [self startTaskWithRequest:req completion:completion];
} }
#pragma mark - Core #pragma mark - Core
@@ -77,28 +81,18 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
return YES; return YES;
} }
- (NSURL *)buildURLWithPath:(NSString *)path params:(NSDictionary *)params { - (NSString *)buildURLStringWithPath:(NSString *)path {
NSURL *url = nil;
if (path.length == 0) return nil; if (path.length == 0) return nil;
if ([path hasPrefix:@"http://"] || [path hasPrefix:@"https://"]) { if ([path hasPrefix:@"http://"] || [path hasPrefix:@"https://"]) {
url = [NSURL URLWithString:path]; return path;
} else if (self.baseURL) {
url = [NSURL URLWithString:path relativeToURL:self.baseURL].absoluteURL;
} }
if (!url) return nil; if (self.baseURL) {
if (params.count == 0) return url; return [NSURL URLWithString:path relativeToURL:self.baseURL].absoluteURL.absoluteString;
NSURLComponents *c = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES]; }
NSMutableArray<NSURLQueryItem *> *items = c.queryItems.mutableCopy ?: [NSMutableArray array]; return path; // baseURL path URL AFN
[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;
} }
- (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]; NSMutableDictionary *all = [self.defaultHeaders mutableCopy] ?: [NSMutableDictionary new];
if (contentType) all[@"Content-Type"] = contentType; 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]; }]; [all enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { [req setValue:obj forHTTPHeaderField:key]; }];
} }
- (NSURLSessionDataTask *)startTaskWithRequest:(NSURLRequest *)req completion:(KBNetworkCompletion)completion { - (NSURLSessionDataTask *)startAFTaskWithRequest:(NSURLRequest *)req completion:(KBNetworkCompletion)completion {
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:req // Content-Type JSON
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 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 (error) { if (completion) completion(nil, response, error); return; }
if (!data) { if (completion) completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorInvalidResponse userInfo:@{NSLocalizedDescriptionKey:@"空响应"}]); return; } NSData *data = (NSData *)responseObject;
id json = nil; if (![data isKindOfClass:[NSData class]]) {
// JSON if (completion) completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorInvalidResponse userInfo:@{NSLocalizedDescriptionKey:@"无数据"}]);
return;
}
NSString *ct = nil; NSString *ct = nil;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) { if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
ct = ((NSHTTPURLResponse *)response).allHeaderFields[@"Content-Type"]; ct = ((NSHTTPURLResponse *)response).allHeaderFields[@"Content-Type"];
} }
BOOL looksJSON = (ct && [ct.lowercaseString containsString:@"application/json"]); BOOL looksJSON = (ct && [ct.lowercaseString containsString:@"application/json"]);
if (looksJSON) { if (looksJSON) {
json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; NSError *jsonErr = nil;
if (!json && completion) { id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];
completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorDecodeFailed userInfo:@{NSLocalizedDescriptionKey:@"JSON解析失败"}]); if (jsonErr) { if (completion) completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorDecodeFailed userInfo:@{NSLocalizedDescriptionKey:@"JSON解析失败"}]); return; }
return;
}
if (completion) completion(json, response, nil); if (completion) completion(json, response, nil);
} else { } else {
if (completion) completion(data, response, nil); if (completion) completion(data, response, nil);
@@ -133,18 +128,20 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
return task; return task;
} }
#pragma mark - Session #pragma mark - AFHTTPSessionManager
- (NSURLSession *)session { - (AFHTTPSessionManager *)manager {
if (!_session) { if (!_manager) {
NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration ephemeralSessionConfiguration]; NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration ephemeralSessionConfiguration];
cfg.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; cfg.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
cfg.timeoutIntervalForRequest = self.timeout; cfg.timeoutIntervalForRequest = self.timeout;
cfg.timeoutIntervalForResource = MAX(self.timeout, 30.0); cfg.timeoutIntervalForResource = MAX(self.timeout, 30.0);
if (@available(iOS 11.0, *)) { cfg.waitsForConnectivity = YES; } 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 #pragma mark - Private helpers