Files
keyboard/keyBoard/Class/Network/KBNetworkManager.m

619 lines
28 KiB
Mathematica
Raw Normal View History

2025-10-29 20:57:45 +08:00
//
// KBNetworkManager.m
// CustomKeyboard
//
#import "KBNetworkManager.h"
2025-12-02 21:32:49 +08:00
#import <TargetConditionals.h>
2025-10-29 20:57:45 +08:00
#import "AFNetworking.h"
2025-10-31 16:06:54 +08:00
#import "KBAuthManager.h"
2025-12-02 21:32:49 +08:00
#import "KBBizCode.h"
// App HUD
#import "KBUserSessionManager.h"
#import "KBHUD.h"
2025-12-04 20:27:26 +08:00
#import "KBSignUtils.h"
2025-10-29 20:57:45 +08:00
NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
@interface KBNetworkManager ()
@property (nonatomic, strong) AFHTTPSessionManager *manager; // AFN ephemeral
//
- (void)fail:(KBNetworkError)code completion:(KBNetworkCompletion)completion;
@end
2025-11-13 15:34:56 +08:00
#if DEBUG
// 使 Debug
// No visible @interface declares the selector
@interface KBNetworkManager (Debug)
- (NSString *)kb_prettyJSONStringFromObject:(id)obj;
- (NSString *)kb_textFromData:(NSData *)data;
- (NSString *)kb_trimmedString:(NSString *)s maxLength:(NSUInteger)maxLen;
@end
#endif
2025-10-29 20:57:45 +08:00
@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;
2025-12-04 20:34:23 +08:00
2025-12-02 20:33:17 +08:00
NSString *lang = [KBLocalizationManager shared].currentLanguageCode ?: KBLanguageCodeEnglish;
2025-12-03 20:31:33 +08:00
NSString *token = [KBUserSessionManager shared].accessToken ? [KBUserSessionManager shared].accessToken : @"";
2025-12-04 20:27:26 +08:00
// query
// signParams[@"lang"] = @"zh";
2025-12-04 20:34:23 +08:00
2025-12-04 20:27:26 +08:00
// 2.
2025-12-02 19:39:37 +08:00
_defaultHeaders = @{
@"Accept": @"*/*",
2025-12-03 20:14:14 +08:00
@"Accept-Language": lang,
2025-12-04 20:27:26 +08:00
@"auth-token" : token,
2025-12-02 19:39:37 +08:00
};
2025-10-30 13:10:33 +08:00
//
_baseURL = [NSURL URLWithString:KB_BASE_URL];
2025-10-29 20:57:45 +08:00
}
return self;
}
2025-12-04 20:34:23 +08:00
- (void)getSignWithParare:(NSDictionary *)bodyParams{
2025-12-04 20:57:39 +08:00
2025-12-04 20:34:23 +08:00
NSString *appId = @"loveKeyboard";
NSString *secret = @"kZJM39HYvhxwbJkG1fmquQRVkQiLAh2H"; //
NSString *timestamp = [KBSignUtils currentTimestamp];
NSString *nonce = [KBSignUtils generateNonceWithLength:16];
// 1.
NSMutableDictionary<NSString *, NSString *> *signParams = [NSMutableDictionary dictionary];
signParams[@"appId"] = appId;
signParams[@"timestamp"] = timestamp;
signParams[@"nonce"] = nonce;
// body
[bodyParams enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([obj isKindOfClass:[NSString class]]) {
signParams[key] = obj;
} else {
signParams[key] = [obj description];
}
}];
NSString *sign = [KBSignUtils signWithParams:signParams secret:secret];
2025-12-04 20:57:39 +08:00
//
NSMutableDictionary<NSString *, NSString *> *headers =
[self.defaultHeaders mutableCopy] ?: [NSMutableDictionary dictionary];
if (sign.length > 0) {
headers[@"X-Sign"] = sign;
}
headers[@"X-App-Id"] = appId;
headers[@"X-Timestamp"] = timestamp;
headers[@"X-Nonce"] = nonce;
// copy
self.defaultHeaders = headers;
2025-12-04 20:34:23 +08:00
}
2025-12-03 14:30:02 +08:00
#
2025-10-29 20:57:45 +08:00
#pragma mark - Public
2025-12-03 14:30:02 +08:00
// JSON GET
2025-10-29 20:57:45 +08:00
- (NSURLSessionDataTask *)GET:(NSString *)path
2025-12-03 14:30:02 +08:00
parameters:(NSDictionary *)parameters
headers:(NSDictionary<NSString *,NSString *> *)headers
autoShowBusinessError:(BOOL)autoShowBusinessError
completion:(KBNetworkCompletion)completion {
NSLog(@"[KBNetworkManager] GET called, enabled=%d, path=%@", self.isEnabled, path);
2025-12-04 20:57:39 +08:00
[self getSignWithParare:parameters];
2025-10-29 20:57:45 +08:00
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;
2025-11-13 15:34:56 +08:00
NSError *serror = nil;
2025-10-29 20:57:45 +08:00
NSMutableURLRequest *req = [serializer requestWithMethod:@"GET"
URLString:urlString
parameters:parameters
2025-11-13 15:34:56 +08:00
error:&serror];
if (serror || !req) {
2025-11-17 20:07:39 +08:00
if (completion) completion(nil, nil, serror ?: [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid URL")}]);
2025-11-13 15:34:56 +08:00
return nil;
}
2025-10-29 20:57:45 +08:00
[self applyHeaders:headers toMutableRequest:req contentType:nil];
2025-11-13 15:34:56 +08:00
#if DEBUG
// GET
NSString *paramStr = [self kb_prettyJSONStringFromObject:parameters] ?: @"(null)";
KBLOG(@"HTTP GET\nURL: %@\nHeaders: %@\n参数: %@",
req.URL.absoluteString,
req.allHTTPHeaderFields ?: @{},
paramStr);
#endif
2025-12-03 14:30:02 +08:00
return [self startJSONTaskWithRequest:req
autoShowBusinessError:autoShowBusinessError
completion:completion];
2025-10-29 20:57:45 +08:00
}
2025-12-03 14:30:02 +08:00
// GET
- (NSURLSessionDataTask *)GET:(NSString *)path
parameters:(NSDictionary *)parameters
headers:(NSDictionary<NSString *,NSString *> *)headers
completion:(KBNetworkCompletion)completion {
2025-12-04 20:57:39 +08:00
[self getSignWithParare:parameters];
2025-12-03 14:30:02 +08:00
return [self GET:path
parameters:parameters
headers:headers
autoShowBusinessError:YES
completion:completion];
}
// JSON POST
2025-10-29 20:57:45 +08:00
- (NSURLSessionDataTask *)POST:(NSString *)path
2025-12-03 14:30:02 +08:00
jsonBody:(id)jsonBody
headers:(NSDictionary<NSString *,NSString *> *)headers
autoShowBusinessError:(BOOL)autoShowBusinessError
completion:(KBNetworkCompletion)completion {
NSLog(@"[KBNetworkManager] POST called, enabled=%d, path=%@", self.isEnabled, path);
2025-12-04 20:57:39 +08:00
[self getSignWithParare:jsonBody];
2025-10-29 20:57:45 +08:00
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];
2025-11-13 15:34:56 +08:00
#if DEBUG
// POST JSON
NSString *bodyStr = [self kb_prettyJSONStringFromObject:jsonBody] ?: @"(null)";
KBLOG(@"HTTP POST\nURL: %@\nHeaders: %@\nJSON: %@",
req.URL.absoluteString,
req.allHTTPHeaderFields ?: @{},
bodyStr);
#endif
2025-12-03 14:30:02 +08:00
return [self startJSONTaskWithRequest:req
autoShowBusinessError:autoShowBusinessError
completion:completion];
}
// POST
- (NSURLSessionDataTask *)POST:(NSString *)path
jsonBody:(id)jsonBody
headers:(NSDictionary<NSString *,NSString *> *)headers
completion:(KBNetworkCompletion)completion {
2025-12-04 20:57:39 +08:00
[self getSignWithParare:jsonBody];
2025-12-03 14:30:02 +08:00
return [self POST:path
jsonBody:jsonBody
headers:headers
autoShowBusinessError:YES
completion:completion];
2025-12-03 13:54:57 +08:00
}
// GET zip
- (NSURLSessionDataTask *)GETData:(NSString *)path
parameters:(NSDictionary *)parameters
headers:(NSDictionary<NSString *,NSString *> *)headers
completion:(KBNetworkDataCompletion)completion {
if (!self.isEnabled) {
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
code:KBNetworkErrorDisabled
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Network disabled (Full Access may be off)")}];
if (completion) completion(nil, nil, e);
return nil;
}
NSString *urlString = [self buildURLStringWithPath:path];
if (!urlString) {
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
code:KBNetworkErrorInvalidURL
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid URL")}];
if (completion) completion(nil, nil, e);
return nil;
}
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
serializer.timeoutInterval = self.timeout;
NSError *serror = nil;
NSMutableURLRequest *req = [serializer requestWithMethod:@"GET"
URLString:urlString
parameters:parameters
error:&serror];
if (serror || !req) {
if (completion) completion(nil, nil, serror ?: [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid URL")}]);
return nil;
}
[self applyHeaders:headers toMutableRequest:req contentType:nil];
return [self startDataTaskWithRequest:req completion:completion];
2025-10-29 20:57:45 +08:00
}
2025-12-04 13:37:11 +08:00
#pragma mark - Upload (multipart/form-data)
- (NSURLSessionDataTask *)uploadFile:(NSString *)path
fileData:(NSData *)fileData
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
headers:(NSDictionary<NSString *,NSString *> *)headers
autoShowBusinessError:(BOOL)autoShowBusinessError
completion:(KBNetworkCompletion)completion
{
NSLog(@"[KBNetworkManager] UPLOAD called, enabled=%d, path=%@", self.isEnabled, path);
if (![self ensureEnabled:completion]) return nil;
NSString *urlString = [self buildURLStringWithPath:path];
if (!urlString) {
[self fail:KBNetworkErrorInvalidURL completion:completion];
return nil;
}
if (!fileData || fileData.length == 0) {
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
code:KBNetworkErrorInvalidResponse
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Empty file data")}];
if (completion) completion(nil, nil, e);
return nil;
}
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
serializer.timeoutInterval = self.timeout;
NSError *error = nil;
NSMutableURLRequest *req =
[serializer multipartFormRequestWithMethod:@"POST"
URLString:urlString
parameters:nil
constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
// Apifox "file"
NSString *fieldName = @"file";
NSString *fname = fileName ?: @"file";
NSString *type = mimeType ?: @"application/octet-stream";
[formData appendPartWithFileData:fileData
name:fieldName
fileName:fname
mimeType:type];
} error:&error];
if (error || !req) {
if (completion) {
completion(nil, nil,
error ?: [NSError errorWithDomain:KBNetworkErrorDomain
code:KBNetworkErrorInvalidURL
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid upload request")}]);
}
return nil;
}
// Content-Type AFN boundary
[self applyHeaders:headers toMutableRequest:req contentType:nil];
#if DEBUG
KBLOG(@"HTTP UPLOAD (multipart)\nURL: %@\nHeaders: %@\nfileName: %@\nlength: %lu",
req.URL.absoluteString,
req.allHTTPHeaderFields ?: @{},
fileName,
(unsigned long)fileData.length);
#endif
// JSON POST {code,message,data,...}
return [self startJSONTaskWithRequest:req
autoShowBusinessError:autoShowBusinessError
completion:completion];
}
- (NSURLSessionDataTask *)uploadFile:(NSString *)path
fileData:(NSData *)fileData
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
headers:(NSDictionary<NSString *,NSString *> *)headers
completion:(KBNetworkCompletion)completion
{
return [self uploadFile:path
fileData:fileData
fileName:fileName
mimeType:mimeType
headers:headers
autoShowBusinessError:YES
completion:completion];
}
2025-10-29 20:57:45 +08:00
#pragma mark - Core
- (BOOL)ensureEnabled:(KBNetworkCompletion)completion {
if (!self.isEnabled) {
2025-11-17 20:07:39 +08:00
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorDisabled userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Network disabled (Full Access may be off)")}];
2025-10-29 20:57:45 +08:00
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) {
2025-11-13 16:23:46 +08:00
// 1) base /
NSString *base = self.baseURL.absoluteString ?: @"";
if (![base hasSuffix:@"/"]) { base = [base stringByAppendingString:@"/"]; }
NSURL *dirBase = [NSURL URLWithString:base];
// 2) / path /api
NSString *relative = ([path hasPrefix:@"/"]) ? [path substringFromIndex:1] : path;
return [NSURL URLWithString:relative relativeToURL:dirBase].absoluteURL.absoluteString;
2025-10-29 20:57:45 +08:00
}
return path; // baseURL path URL AFN
}
- (void)applyHeaders:(NSDictionary<NSString *,NSString *> *)headers toMutableRequest:(NSMutableURLRequest *)req contentType:(NSString *)contentType {
2025-10-31 16:06:54 +08:00
//
2025-10-29 20:57:45 +08:00
NSMutableDictionary *all = [self.defaultHeaders mutableCopy] ?: [NSMutableDictionary new];
2025-10-31 16:06:54 +08:00
NSDictionary *auth = [[KBAuthManager shared] authorizationHeader];
[auth enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { all[key] = obj; }];
2025-10-29 20:57:45 +08:00
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]; }];
}
2025-12-03 13:54:57 +08:00
- (NSURLSessionDataTask *)startJSONTaskWithRequest:(NSURLRequest *)req
2025-12-03 14:30:02 +08:00
autoShowBusinessError:(BOOL)autoShowBusinessError
2025-12-03 13:54:57 +08:00
completion:(KBNetworkCompletion)completion {
NSLog(@"[KBNetworkManager] startAFTaskWithRequest: %@", req.URL.absoluteString);
2025-10-29 20:57:45 +08:00
// 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) {
NSLog(@"[KBNetworkManager] task finished, error = %@", error);
2025-11-13 15:34:56 +08:00
// AFN 2xx error
if (error) {
#if DEBUG
NSInteger status = [response isKindOfClass:NSHTTPURLResponse.class] ? ((NSHTTPURLResponse *)response).statusCode : 0;
KBLOG(@"请求失败\nURL: %@\n状态: %ld\n错误: %@\nUserInfo: %@",
req.URL.absoluteString,
(long)status,
error.localizedDescription,
error.userInfo ?: @{});
#endif
if (completion) completion(nil, response, error);
return;
}
2025-10-29 20:57:45 +08:00
NSData *data = (NSData *)responseObject;
if (![data isKindOfClass:[NSData class]]) {
2025-11-17 20:07:39 +08:00
#if DEBUG
KBLOG(@"无效响应\nURL: %@\n说明: %@", req.URL.absoluteString, KBLocalized(@"未获取到数据"));
#endif
2025-12-03 13:54:57 +08:00
if (completion) completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorInvalidResponse userInfo:@{NSLocalizedDescriptionKey:KBLocalized(@"No data")}]);
return;
}
2025-10-29 20:57:45 +08:00
NSString *ct = nil;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
ct = ((NSHTTPURLResponse *)response).allHeaderFields[@"Content-Type"];
}
2025-11-13 15:34:56 +08:00
// JSON Content-Type json { / [
BOOL looksJSON = (ct && [[ct lowercaseString] containsString:@"json"]);
if (!looksJSON) {
//
const unsigned char *bytes = data.bytes;
NSUInteger len = data.length;
for (NSUInteger i = 0; !looksJSON && i < len; i++) {
unsigned char c = bytes[i];
if (c == ' ' || c == '\n' || c == '\r' || c == '\t') continue;
looksJSON = (c == '{' || c == '[');
break;
}
}
2025-10-29 20:57:45 +08:00
if (looksJSON) {
NSError *jsonErr = nil;
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];
2025-11-13 15:34:56 +08:00
if (jsonErr) {
2025-11-17 20:07:39 +08:00
#if DEBUG
2025-11-13 15:34:56 +08:00
KBLOG(@"响应解析失败(JSON)\nURL: %@\n错误: %@",
req.URL.absoluteString,
jsonErr.localizedDescription);
2025-11-17 20:07:39 +08:00
#endif
if (completion) completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorDecodeFailed userInfo:@{NSLocalizedDescriptionKey:KBLocalized(@"Failed to parse JSON")}]);
2025-11-13 15:34:56 +08:00
return;
}
2025-12-03 13:54:57 +08:00
//
if (![json isKindOfClass:[NSDictionary class]]) {
if (completion) completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorInvalidResponse userInfo:@{NSLocalizedDescriptionKey:KBLocalized(@"Invalid response")}]);
return;
}
2025-11-13 15:34:56 +08:00
#if DEBUG
NSString *pretty = [self kb_prettyJSONStringFromObject:json] ?: @"(null)";
pretty = [self kb_trimmedString:pretty maxLength:4096];
NSInteger status = [response isKindOfClass:NSHTTPURLResponse.class] ? ((NSHTTPURLResponse *)response).statusCode : 0;
2025-12-02 21:32:49 +08:00
KBLOG(@"\n[KBNetwork] 响应成功(JSON)\nURL: %@\n状态: %ld\nContent-Type: %@\n数据: %@\n",
2025-11-13 15:34:56 +08:00
req.URL.absoluteString,
(long)status,
ct ?: @"",
pretty);
#endif
2025-12-03 13:54:57 +08:00
NSDictionary *dict = (NSDictionary *)json;
2025-12-02 21:32:49 +08:00
// code { code, message, data }
2025-12-03 13:54:57 +08:00
NSInteger bizCode = KBBizCodeFromJSONObject(dict);
if (bizCode != NSNotFound && bizCode != KBBizCodeSuccess) {
2025-12-03 14:30:02 +08:00
// code token error
BOOL handledByAuth = [self kb_handleBizCode:bizCode json:dict response:response];
NSString *msg = KBBizMessageFromJSONObject(dict) ?: KBLocalized(@"Server error");
2025-12-12 14:16:48 +08:00
if (handledByAuth == false) {
if (autoShowBusinessError) {
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:msg];
});
}
2025-12-02 21:32:49 +08:00
}
2025-12-03 14:30:02 +08:00
NSError *bizErr = [NSError errorWithDomain:KBNetworkErrorDomain
code:KBNetworkErrorBusiness
userInfo:@{
NSLocalizedDescriptionKey : msg,
@"code" : @(bizCode)
}];
2025-12-12 16:09:14 +08:00
if (completion) completion(dict, response, bizErr);
2025-12-03 14:30:02 +08:00
return;
}
2025-12-02 21:32:49 +08:00
// code
2025-12-04 20:04:02 +08:00
[KBHUD dismiss];
2025-12-03 13:54:57 +08:00
if (completion) completion(dict, response, nil);
2025-10-29 20:57:45 +08:00
} else {
2025-12-03 13:54:57 +08:00
// JSON JSON
if (completion) completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorInvalidResponse userInfo:@{NSLocalizedDescriptionKey:KBLocalized(@"Invalid response")}]);
}
}];
[task resume];
return task;
}
//
- (NSURLSessionDataTask *)startDataTaskWithRequest:(NSURLRequest *)req
completion:(KBNetworkDataCompletion)completion {
NSLog(@"[KBNetworkManager] startDataTaskWithRequest: %@", req.URL.absoluteString);
self.manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSURLSessionDataTask *task = [self.manager dataTaskWithRequest:req uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
NSLog(@"[KBNetworkManager] data task finished, error = %@", 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:KBLocalized(@"No data")}]);
return;
2025-10-29 20:57:45 +08:00
}
2025-12-03 13:54:57 +08:00
if (completion) completion(data, response, nil);
2025-10-29 20:57:45 +08:00
}];
[task resume];
return task;
}
#pragma mark - AFHTTPSessionManager
- (AFHTTPSessionManager *)manager {
if (!_manager) {
NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration ephemeralSessionConfiguration];
cfg.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
2025-11-13 15:34:56 +08:00
// per-request serializer.timeoutInterval
2025-10-29 20:57:45 +08:00
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
2025-12-03 14:30:02 +08:00
/// codetoken 线
/// YES code
- (BOOL)kb_handleBizCode:(NSInteger)bizCode
2025-12-02 21:32:49 +08:00
json:(id)json
response:(NSURLResponse *)response {
switch (bizCode) {
2025-12-03 12:55:51 +08:00
// / / token /线
case KBBizCodeNotLogin:
case KBBizCodeNoAuth:
case KBBizCodeTokenNotFound:
2025-12-02 21:32:49 +08:00
case KBBizCodeTokenInvalid:
2025-12-03 12:55:51 +08:00
case KBBizCodeTokenTimeout:
case KBBizCodeTokenBeReplaced:
case KBBizCodeTokenKickOut:
case KBBizCodeTokenFreeze:
case KBBizCodeTokenNoPrefix:
case KBBizCodeForbidden: {
2025-12-02 21:32:49 +08:00
//
NSString *msg = KBBizMessageFromJSONObject(json);
if (msg.length == 0) {
msg = KBLocalized(@"Your session has expired. Please sign in again.");
}
dispatch_async(dispatch_get_main_queue(), ^{
// Keychain +
[[KBUserSessionManager shared] logout];
2025-12-12 14:16:48 +08:00
[[KBUserSessionManager shared] goLoginVC];
2025-12-02 21:32:49 +08:00
//
[KBHUD showInfo:msg];
// 广便 UI
NSDictionary *info = @{ @"code": @(bizCode),
@"message": msg ?: @"" };
[[NSNotificationCenter defaultCenter] postNotificationName:@"KBUserSessionInvalidNotification"
object:nil
userInfo:info];
});
2025-12-03 14:30:02 +08:00
return YES;
2025-12-02 21:32:49 +08:00
} break;
default:
break;
}
2025-12-03 14:30:02 +08:00
return NO;
2025-12-02 21:32:49 +08:00
}
2025-11-17 20:07:39 +08:00
- (void)fail:(KBNetworkError)code completion:(KBNetworkCompletion)completion {
NSString *msg = KBLocalized(@"Network error");
switch (code) {
case KBNetworkErrorDisabled: msg = KBLocalized(@"Network disabled (Full Access may be off)"); break;
case KBNetworkErrorInvalidURL: msg = KBLocalized(@"Invalid URL"); break;
case KBNetworkErrorInvalidResponse: msg = KBLocalized(@"Invalid response"); break;
case KBNetworkErrorDecodeFailed: msg = KBLocalized(@"Parse failed"); break;
2025-12-02 21:32:49 +08:00
case KBNetworkErrorBusiness: msg = KBLocalized(@"Server error"); break;
2025-11-17 20:07:39 +08:00
default: break;
}
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
code:code
userInfo:@{NSLocalizedDescriptionKey: msg}];
if (completion) completion(nil, nil, e);
2025-10-29 20:57:45 +08:00
}
@end
2025-11-13 15:34:56 +08:00
#pragma mark - Debug helpers
#if DEBUG
@implementation KBNetworkManager (Debug)
// JSON description
- (NSString *)kb_prettyJSONStringFromObject:(id)obj {
if (!obj || obj == (id)kCFNull) return nil;
if (![NSJSONSerialization isValidJSONObject:obj]) {
// JSON description
return [obj description];
}
NSData *data = [NSJSONSerialization dataWithJSONObject:obj options:NSJSONWritingPrettyPrinted error:NULL];
if (!data) return [obj description];
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: [obj description];
}
//
- (NSString *)kb_textFromData:(NSData *)data {
if (!data) return @"(null)";
if (data.length == 0) return @"";
// UTF-8
NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (text.length > 0) return text;
//
NSArray *encs = @[@(NSASCIIStringEncoding), @(NSISOLatin1StringEncoding), @(NSUnicodeStringEncoding)];
for (NSNumber *n in encs) {
text = [[NSString alloc] initWithData:data encoding:n.unsignedIntegerValue];
if (text.length > 0) return text;
}
return [NSString stringWithFormat:@"<binary %lu bytes>", (unsigned long)data.length];
}
//
- (NSString *)kb_trimmedString:(NSString *)s maxLength:(NSUInteger)maxLen {
if (!s) return @"";
if (s.length <= maxLen) return s;
NSString *head = [s substringToIndex:maxLen];
return [head stringByAppendingString:@"\n...<trimmed>..."];
}
@end
#endif