第一種實現方式:javascript
功能:GET POST 請求html
緩存邏輯:java
1.是否要刷新本地緩存,不須要就直接發起無緩存的網絡請求,不然直接讀取本地數據ios
2.須要刷新本地緩存,先讀取本地數據,有就返回,沒有就發起緩存的網絡請求git
3.無網絡時直接讀取本地緩存github
#import "AFHTTPSessionManager.h" /** *該類默認只要導入頭文件就會自動檢測網絡狀態,且會在沒有網絡和未知網絡的時候,自動從本地數據庫中讀取緩存。 *數據庫網絡緩存是基於猿題庫公司對FMDB進行封裝的輕量級 key-value 存儲框架 *詳情請見 https://github.com/yuantiku/YTKKeyValueStore *對該類若有疑問能夠拉個issues */ @interface JMHttpRequestMethod : AFHTTPSessionManager typedef NS_ENUM(NSUInteger, JMRequestSerializer) { JMRequestSerializerJSON, // 設置請求數據爲JSON格式 JMRequestSerializerPlainText // 設置請求數據爲普通 text/html }; typedef NS_ENUM(NSUInteger, JMResponseSerializer) { JMResponseSerializerJSON, // 設置響應數據爲JSON格式 JMResponseSerializerHTTP, // 設置響應數據爲二進制格式 JMResponseSerializerXML // 設置響應數據爲XML格式 }; #pragma mark - 程序入口設置網絡請求頭API 通常調用一次便可 /** 設置 請求和響應類型和超時時間 @param requestType 默認爲請求類型爲JSON格式 @param responseType 默認響應格式爲JSON格式 @param timeOut 請求超時時間 默認爲20秒 */ +(void)setTimeOutWithTime:(NSTimeInterval)timeOut requestType:(JMRequestSerializer)requestType responseType:(JMResponseSerializer)responseType; /** 設置 請求頭 @param httpBody 根據服務器要求 配置相應的請求體 */ + (void)setHttpBodyWithDic:(NSDictionary *)httpBody; #pragma mark - 網絡工具 API /** 獲取當前的網絡狀態 @return YES 有網 NO 沒有聯網 */ +(BOOL)getCurrentNetWorkStatus; /** 獲取網絡緩存 文件大小 @return size 單位M 默認保留兩位小數 如: 0.12M */ + (NSString *)fileSizeWithDBPath; /** 清除全部網絡緩存 */ + (void)cleanNetWorkRefreshCache; #pragma mark - GET 請求API /** GET 請求 不用傳參 API @param url 請求的url @param refreshCache 是否對該頁面進行緩存 @param success 請求成功回調 @param fail 請求失敗回調 @return self */ + (JMHttpRequestMethod *)getWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache success:(void(^)(id responseObject))success fail:(void(^)(NSError *error))fail; /** GET 請求 傳參數的API @param url 請求的url @param refreshCache 是否對該頁面進行緩存 @param params 請求數據向服務器傳的參數 @param success 請求成功回調 @param fail 請求失敗回調 @return self */ + (JMHttpRequestMethod *)getWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params success:(void(^)(id responseObject))success fail:(void(^)(NSError *error))fail; /** GET 請求 帶有進度回調的 API @param url 請求的url @param refreshCache 是否對該頁面進行緩存 @param params 請求數據向服務器傳的參數 @param progress 請求進度回調 @param success 請求成功回調 @param fail 請求失敗回調 @return self */ + (JMHttpRequestMethod *)getWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params progress:(void(^)(int64_t bytesRead, int64_t totalBytesRead))progress success:(void(^)(id responseObject))success fail:(void(^)(NSError *error))fail; #pragma mark - POST 請求API /** POST 請求API @param url 請求的url @param refreshCache 是否對該頁面進行緩存 @param params 請求數據向服務器傳的參數 @param success 請求成功回調 @param fail 請求失敗回調 @return self */ + (JMHttpRequestMethod *)postWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params success:(void(^)(id responseObject))success fail:(void(^)(NSError *error))fail; /** POST 請求 帶有進度回調的 API @param url 請求的url @param refreshCache 是否對該頁面進行緩存 @param params 請求數據向服務器傳的參數 @param progress 請求進度回調 @param success 請求成功回調 @param fail 請求失敗回調 @return self */ + (JMHttpRequestMethod *)postWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params progress:(void(^)(int64_t bytesRead, int64_t totalBytesRead))progress success:(void(^)(id responseObject))success fail:(void(^)(NSError *error))fail; @end
#import "JMHttpRequestMethod.h" #import "YTKKeyValueStore.h" #define PATH_OF_NetWork [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] typedef NS_ENUM(NSUInteger, JMNetworkStatus) { JMNetworkStatusUnknown, //未知的網絡 JMNetworkStatusNotNetWork, //沒有網絡 JMNetworkStatusReachableViaWWAN,//手機蜂窩數據網絡 JMNetworkStatusReachableViaWiFi //WIFI 網絡 }; @interface JMHttpRequestMethod () @end @implementation JMHttpRequestMethod static NSString *const httpCache = @"NetworkCache"; static YTKKeyValueStore *_store; static JMNetworkStatus _status; static BOOL _isHasNetWork; + (void)load { JMHttpRequestMethod *httpMethod; httpMethod.requestSerializer = [AFJSONRequestSerializer serializer]; //設置請求的超時時間 httpMethod.requestSerializer.timeoutInterval = 20.f; //設置服務器返回結果的類型:JSON (AFJSONResponseSerializer,AFHTTPResponseSerializer) httpMethod.responseSerializer = [AFJSONResponseSerializer serializer]; httpMethod.responseSerializer.acceptableContentTypes = [NSSet setWithObjects: @"application/json", @"text/html", @"text/json", @"text/plain", @"text/javascript", @"text/xml", @"image/*", nil]; [self startMonitoringNetworkStatus]; } /** 監測網絡狀態 (在程序入口,調用一次便可) */ + (void)startMonitoringNetworkStatus { AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager]; [manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { switch (status) { case AFNetworkReachabilityStatusUnknown: _isHasNetWork = NO; _status = JMNetworkStatusUnknown; break; case AFNetworkReachabilityStatusNotReachable: _isHasNetWork = NO; _status = JMNetworkStatusNotNetWork; NSLog(@"沒有網的狀態"); break; case AFNetworkReachabilityStatusReachableViaWWAN: _isHasNetWork = YES; _status = JMNetworkStatusReachableViaWWAN; break; case AFNetworkReachabilityStatusReachableViaWiFi: _isHasNetWork = YES; _status = JMNetworkStatusReachableViaWiFi; NSLog(@"如今是有網狀態"); break; } }]; [manager startMonitoring]; } /** 設置 請求和響應類型和超時時間 @param requestType 默認爲請求類型爲JSON格式 @param responseType 默認響應格式爲JSON格式 @param timeOut 請求超時時間 默認爲20秒 */ +(void)setTimeOutWithTime:(NSTimeInterval)timeOut requestType:(JMRequestSerializer)requestType responseType:(JMResponseSerializer)responseType { JMHttpRequestMethod *httpMethod; httpMethod.requestSerializer.timeoutInterval = timeOut; switch (requestType) { case JMRequestSerializerJSON: httpMethod.requestSerializer = [AFJSONRequestSerializer serializer]; break; case JMRequestSerializerPlainText: httpMethod.requestSerializer = [AFHTTPRequestSerializer serializer]; break; default: break; } switch (responseType) { case JMResponseSerializerJSON: httpMethod.responseSerializer = [AFJSONResponseSerializer serializer]; break; case JMResponseSerializerHTTP: httpMethod.responseSerializer = [AFHTTPResponseSerializer serializer]; break; case JMResponseSerializerXML: httpMethod.responseSerializer = [AFXMLParserResponseSerializer serializer]; default: break; } } /** 設置 請求頭 @param httpBody 根據服務器要求 配置相應的請求體 */ + (void)setHttpBodyWithDic:(NSDictionary *)httpBody { JMHttpRequestMethod *httpMethod; for (NSString *key in httpBody.allKeys) { if (httpBody[key] != nil) { [httpMethod.requestSerializer setValue:httpBody[key] forHTTPHeaderField:key]; } } } /** 獲取當前的網絡狀態 @return YES 有網 NO 沒有聯網 */ +(BOOL)getCurrentNetWorkStatus { return _isHasNetWork; } /** 獲取網絡緩存 文件大小 @return size 單位M */ + (NSString *)fileSizeWithDBPath { NSFileManager* manager = [NSFileManager defaultManager]; if ([manager fileExistsAtPath:[PATH_OF_NetWork stringByAppendingPathComponent:httpCache]]){ unsigned long long fileSize = [[manager attributesOfItemAtPath:[PATH_OF_NetWork stringByAppendingPathComponent:httpCache] error:nil] fileSize]; NSString *size = [NSString stringWithFormat:@"%.2fM",fileSize/1024.0/1024.0]; return size; }else { return @"0M"; } return 0; } /** 清除全部網絡緩存 */ + (void)cleanNetWorkRefreshCache { NSError *error; BOOL isSuccess = [[NSFileManager defaultManager]removeItemAtPath:[PATH_OF_NetWork stringByAppendingPathComponent:httpCache] error:&error]; if (isSuccess) { NSLog(@"clean cache file is success"); }else { if ([PATH_OF_NetWork stringByAppendingPathComponent:httpCache]) { NSLog(@"error:%@",error.description); }else { NSLog(@"error: cache file is not exist"); } } } #pragma mark - /**************GET 請求API ******************/ + (JMHttpRequestMethod *)getWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache success:(void(^)(id responseObject))success fail:(void(^)(NSError *error))fail { return [self getWithUrl:url refreshCache:refreshCache params:nil success:success fail:fail]; } // 多一個params參數 + (JMHttpRequestMethod *)getWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params success:(void(^)(id responseObject))success fail:(void(^)(NSError *error))fail { return [self getWithUrl:url refreshCache:refreshCache params:params progress:nil success:success fail:fail]; } // 多一個帶進度回調 + (JMHttpRequestMethod *)getWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params progress:(void(^)(int64_t bytesRead, int64_t totalBytesRead))progress success:(void(^)(id responseObject))success fail:(void(^)(NSError *error))fail { _store = [[YTKKeyValueStore alloc] initDBWithName:httpCache]; [_store createTableWithName:httpCache]; JMHttpRequestMethod *request = nil; if ([JMHttpRequestMethod getCurrentNetWorkStatus]) { if (!refreshCache) { [self requestNotCacheWithHttpMethod:0 url:url params:params progress:progress success:success fail:fail]; }else { NSDictionary *dict = [_store getObjectById:url fromTable:httpCache]; if (dict) { success(dict); }else { [[self manager] GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) { progress(downloadProgress.completedUnitCount, downloadProgress.totalUnitCount); } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { [_store putObject:responseObject withId:url intoTable:httpCache]; success(responseObject); NSLog(@"\nRequest success, URL: %@\n params:%@\n response:%@\n\n",url,params,responseObject); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { fail(error); NSLog(@"error = %@",error.description); }]; } } } else { NSDictionary *dict = [_store getObjectById:url fromTable:httpCache]; if (dict) { success(dict); }else { NSLog(@"當前爲無網絡狀態,本地也沒有緩存數據"); } } return request; }
#pragma mark - /*********************** POST 請求API **********************/ + (JMHttpRequestMethod *)postWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params success:(void(^)(id responseObject))success fail:(void(^)(NSError *error))fail { return [self postWithUrl:url refreshCache:refreshCache params:params progress:nil success:success fail:fail]; } + (JMHttpRequestMethod *)postWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params progress:(void(^)(int64_t bytesRead, int64_t totalBytesRead))progress success:(void(^)(id responseObject))success fail:(void(^)(NSError *error))fail { JMHttpRequestMethod *request = nil; if ([JMHttpRequestMethod getCurrentNetWorkStatus]) { if (!refreshCache) { [self requestNotCacheWithHttpMethod:1 url:url params:params progress:progress success:success fail:fail]; }else { NSDictionary *dict = [_store getObjectById:url fromTable:httpCache]; if (dict) { success(dict); }else { [[self manager] POST:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) { progress(downloadProgress.completedUnitCount, downloadProgress.totalUnitCount); } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { [_store putObject:responseObject withId:url intoTable:httpCache]; success(responseObject); NSLog(@"\nRequest success, URL: %@\n params:%@\n response:%@\n\n",url,params,responseObject); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { fail(error); }]; } } } else { NSDictionary *dict = [_store getObjectById:url fromTable:httpCache]; if (dict) { success(dict); }else { NSLog(@"當前爲無網絡狀態,本地也沒有緩存數據"); } } return request; } + (void)requestNotCacheWithHttpMethod:(NSInteger)httpMethod url:(NSString *)url params:(NSDictionary *)params progress:(void(^)(int64_t bytesRead, int64_t totalBytesRead))progress success:(void(^)(id responseObject))success fail:(void(^)(NSError *error))fail { if (httpMethod == 0) { [[self manager]GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) { progress(downloadProgress.completedUnitCount, downloadProgress.totalUnitCount); } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { success(responseObject); NSLog(@"\nRequest success, URL: %@\n params:%@\n response:%@\n\n",url,params,responseObject); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { fail(error); }]; }else { [[self manager ]POST:url parameters:params progress:^(NSProgress * _Nonnull uploadProgress) { progress(uploadProgress.completedUnitCount, uploadProgress.totalUnitCount); } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { success(responseObject); NSLog(@"\nRequest success, URL: %@\n params:%@\n response:%@\n\n",url,params,responseObject); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { fail(error); }]; } } @end
#import <Foundation/Foundation.h> @interface YTKKeyValueItem : NSObject @property (strong, nonatomic) NSString *itemId; @property (strong, nonatomic) id itemObject; @property (strong, nonatomic) NSDate *createdTime; @end @interface YTKKeyValueStore : NSObject - (id)initDBWithName:(NSString *)dbName; - (id)initWithDBWithPath:(NSString *)dbPath; - (void)createTableWithName:(NSString *)tableName; - (void)clearTable:(NSString *)tableName; - (void)close; ///************************ Put&Get methods ***************************************** - (void)putObject:(id)object withId:(NSString *)objectId intoTable:(NSString *)tableName; - (id)getObjectById:(NSString *)objectId fromTable:(NSString *)tableName; - (YTKKeyValueItem *)getYTKKeyValueItemById:(NSString *)objectId fromTable:(NSString *)tableName; - (void)putString:(NSString *)string withId:(NSString *)stringId intoTable:(NSString *)tableName; - (NSString *)getStringById:(NSString *)stringId fromTable:(NSString *)tableName; - (void)putNumber:(NSNumber *)number withId:(NSString *)numberId intoTable:(NSString *)tableName; - (NSNumber *)getNumberById:(NSString *)numberId fromTable:(NSString *)tableName; - (NSArray *)getAllItemsFromTable:(NSString *)tableName; - (void)deleteObjectById:(NSString *)objectId fromTable:(NSString *)tableName; - (void)deleteObjectsByIdArray:(NSArray *)objectIdArray fromTable:(NSString *)tableName; - (void)deleteObjectsByIdPrefix:(NSString *)objectIdPrefix fromTable:(NSString *)tableName; @end
#import "YTKKeyValueStore.h" #import "FMDatabase.h" #import "FMDatabaseQueue.h" #ifdef DEBUG #define debugLog(s, ...) NSLog( @"[%@ in line %d] ➡️➡️➡️%@", [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] ) #define debugMethod() NSLog(@"%s", __func__) #define debugError(s, ...) NSLog( @"[%@ in line %d] ➡️➡️➡️%@", [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] ) #else #define debugLog(s,...) #define debugMethod() #define debugError(s, ...) #endif #define PATH_OF_DOCUMENT [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] @implementation YTKKeyValueItem - (NSString *)description { return [NSString stringWithFormat:@"id=%@, value=%@, timeStamp=%@", _itemId, _itemObject, _createdTime]; } @end @interface YTKKeyValueStore() @property (strong, nonatomic) FMDatabaseQueue * dbQueue; @end @implementation YTKKeyValueStore static NSString *const DEFAULT_DB_NAME = @"database.sqlite"; static NSString *const CREATE_TABLE_SQL = @"CREATE TABLE IF NOT EXISTS %@ ( \ id TEXT NOT NULL, \ json TEXT NOT NULL, \ createdTime TEXT NOT NULL, \ PRIMARY KEY(id)) \ "; static NSString *const UPDATE_ITEM_SQL = @"REPLACE INTO %@ (id, json, createdTime) values (?, ?, ?)"; static NSString *const QUERY_ITEM_SQL = @"SELECT json, createdTime from %@ where id = ? Limit 1"; static NSString *const SELECT_ALL_SQL = @"SELECT * from %@"; static NSString *const CLEAR_ALL_SQL = @"DELETE from %@"; static NSString *const DELETE_ITEM_SQL = @"DELETE from %@ where id = ?"; static NSString *const DELETE_ITEMS_SQL = @"DELETE from %@ where id in ( %@ )"; static NSString *const DELETE_ITEMS_WITH_PREFIX_SQL = @"DELETE from %@ where id like ? "; + (BOOL)checkTableName:(NSString *)tableName { if (tableName == nil || tableName.length == 0 || [tableName rangeOfString:@" "].location != NSNotFound) { debugLog(@"ERROR, table name: %@ format error.", tableName); return NO; } return YES; } - (id)init { return [self initDBWithName:DEFAULT_DB_NAME]; } - (id)initDBWithName:(NSString *)dbName { self = [super init]; if (self) { NSString * dbPath = [PATH_OF_DOCUMENT stringByAppendingPathComponent:dbName]; debugLog(@"dbPath = %@", dbPath); if (_dbQueue) { [self close]; } _dbQueue = [FMDatabaseQueue databaseQueueWithPath:dbPath]; } return self; } - (id)initWithDBWithPath:(NSString *)dbPath { self = [super init]; if (self) { debugLog(@"dbPath = %@", dbPath); if (_dbQueue) { [self close]; } _dbQueue = [FMDatabaseQueue databaseQueueWithPath:dbPath]; } return self; } - (void)createTableWithName:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return; } NSString * sql = [NSString stringWithFormat:CREATE_TABLE_SQL, tableName]; __block BOOL result; [_dbQueue inDatabase:^(FMDatabase *db) { result = [db executeUpdate:sql]; }]; if (!result) { debugLog(@"ERROR, failed to create table: %@", tableName); } } - (void)clearTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return; } NSString * sql = [NSString stringWithFormat:CLEAR_ALL_SQL, tableName]; __block BOOL result; [_dbQueue inDatabase:^(FMDatabase *db) { result = [db executeUpdate:sql]; }]; if (!result) { debugLog(@"ERROR, failed to clear table: %@", tableName); } } - (void)putObject:(id)object withId:(NSString *)objectId intoTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return; } NSError * error; NSData * data = [NSJSONSerialization dataWithJSONObject:object options:0 error:&error]; if (error) { debugLog(@"ERROR, faild to get json data"); return; } NSString * jsonString = [[NSString alloc] initWithData:data encoding:(NSUTF8StringEncoding)]; NSDate * createdTime = [NSDate date]; NSString * sql = [NSString stringWithFormat:UPDATE_ITEM_SQL, tableName]; __block BOOL result; [_dbQueue inDatabase:^(FMDatabase *db) { result = [db executeUpdate:sql, objectId, jsonString, createdTime]; }]; if (!result) { debugLog(@"ERROR, failed to insert/replace into table: %@", tableName); } } - (id)getObjectById:(NSString *)objectId fromTable:(NSString *)tableName { YTKKeyValueItem * item = [self getYTKKeyValueItemById:objectId fromTable:tableName]; if (item) { return item.itemObject; } else { return nil; } } - (YTKKeyValueItem *)getYTKKeyValueItemById:(NSString *)objectId fromTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return nil; } NSString * sql = [NSString stringWithFormat:QUERY_ITEM_SQL, tableName]; __block NSString * json = nil; __block NSDate * createdTime = nil; [_dbQueue inDatabase:^(FMDatabase *db) { FMResultSet * rs = [db executeQuery:sql, objectId]; if ([rs next]) { json = [rs stringForColumn:@"json"]; createdTime = [rs dateForColumn:@"createdTime"]; } [rs close]; }]; if (json) { NSError * error; id result = [NSJSONSerialization JSONObjectWithData:[json dataUsingEncoding:NSUTF8StringEncoding] options:(NSJSONReadingAllowFragments) error:&error]; if (error) { debugLog(@"ERROR, faild to prase to json"); return nil; } YTKKeyValueItem * item = [[YTKKeyValueItem alloc] init]; item.itemId = objectId; item.itemObject = result; item.createdTime = createdTime; return item; } else { return nil; } } - (void)putString:(NSString *)string withId:(NSString *)stringId intoTable:(NSString *)tableName { if (string == nil) { debugLog(@"error, string is nil"); return; } [self putObject:@[string] withId:stringId intoTable:tableName]; } - (NSString *)getStringById:(NSString *)stringId fromTable:(NSString *)tableName { NSArray * array = [self getObjectById:stringId fromTable:tableName]; if (array && [array isKindOfClass:[NSArray class]]) { return array[0]; } return nil; } - (void)putNumber:(NSNumber *)number withId:(NSString *)numberId intoTable:(NSString *)tableName { if (number == nil) { debugLog(@"error, number is nil"); return; } [self putObject:@[number] withId:numberId intoTable:tableName]; } - (NSNumber *)getNumberById:(NSString *)numberId fromTable:(NSString *)tableName { NSArray * array = [self getObjectById:numberId fromTable:tableName]; if (array && [array isKindOfClass:[NSArray class]]) { return array[0]; } return nil; } - (NSArray *)getAllItemsFromTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return nil; } NSString * sql = [NSString stringWithFormat:SELECT_ALL_SQL, tableName]; __block NSMutableArray * result = [NSMutableArray array]; [_dbQueue inDatabase:^(FMDatabase *db) { FMResultSet * rs = [db executeQuery:sql]; while ([rs next]) { YTKKeyValueItem * item = [[YTKKeyValueItem alloc] init]; item.itemId = [rs stringForColumn:@"id"]; item.itemObject = [rs stringForColumn:@"json"]; item.createdTime = [rs dateForColumn:@"createdTime"]; [result addObject:item]; } [rs close]; }]; // parse json string to object NSError * error; for (YTKKeyValueItem * item in result) { error = nil; id object = [NSJSONSerialization JSONObjectWithData:[item.itemObject dataUsingEncoding:NSUTF8StringEncoding] options:(NSJSONReadingAllowFragments) error:&error]; if (error) { debugLog(@"ERROR, faild to prase to json."); } else { item.itemObject = object; } } return result; } - (void)deleteObjectById:(NSString *)objectId fromTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return; } NSString * sql = [NSString stringWithFormat:DELETE_ITEM_SQL, tableName]; __block BOOL result; [_dbQueue inDatabase:^(FMDatabase *db) { result = [db executeUpdate:sql, objectId]; }]; if (!result) { debugLog(@"ERROR, failed to delete item from table: %@", tableName); } } - (void)deleteObjectsByIdArray:(NSArray *)objectIdArray fromTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return; } NSMutableString *stringBuilder = [NSMutableString string]; for (id objectId in objectIdArray) { NSString *item = [NSString stringWithFormat:@" '%@' ", objectId]; if (stringBuilder.length == 0) { [stringBuilder appendString:item]; } else { [stringBuilder appendString:@","]; [stringBuilder appendString:item]; } } NSString *sql = [NSString stringWithFormat:DELETE_ITEMS_SQL, tableName, stringBuilder]; __block BOOL result; [_dbQueue inDatabase:^(FMDatabase *db) { result = [db executeUpdate:sql]; }]; if (!result) { debugLog(@"ERROR, failed to delete items by ids from table: %@", tableName); } } - (void)deleteObjectsByIdPrefix:(NSString *)objectIdPrefix fromTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return; } NSString *sql = [NSString stringWithFormat:DELETE_ITEMS_WITH_PREFIX_SQL, tableName]; NSString *prefixArgument = [NSString stringWithFormat:@"%@%%", objectIdPrefix]; __block BOOL result; [_dbQueue inDatabase:^(FMDatabase *db) { result = [db executeUpdate:sql, prefixArgument]; }]; if (!result) { debugLog(@"ERROR, failed to delete items by id prefix from table: %@", tableName); } } - (void)close { [_dbQueue close]; _dbQueue = nil; } @end
[JMHttpRequestMethod getWithUrl:url1 refreshCache:YES success:^(id responseObject) { NSData *jsonData = [NSJSONSerialization dataWithJSONObject:responseObject options:NSJSONWritingPrettyPrinted error:nil]; self.textView.text = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; // [JMHttpRequestMethod cleanNetWorkRefreshCache]; NSLog(@"緩存大小爲%@", [JMHttpRequestMethod fileSizeWithDBPath]); } fail:^(NSError *error) { }];
第二種實現方式:sql
功能:POST GET Upload download數據庫
#import "HYBNetworking.h" #import "AFNetworkActivityIndicatorManager.h" #import "AFNetworking.h" #import "AFHTTPSessionManager.h" #import <CommonCrypto/CommonDigest.h> @interface NSString (md5) + (NSString *)hybnetworking_md5:(NSString *)string; @end @implementation NSString (md5) + (NSString *)hybnetworking_md5:(NSString *)string { if (string == nil || [string length] == 0) { return nil; } unsigned char digest[CC_MD5_DIGEST_LENGTH], i; CC_MD5([string UTF8String], (int)[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding], digest); NSMutableString *ms = [NSMutableString string]; for (i = 0; i < CC_MD5_DIGEST_LENGTH; i++) { [ms appendFormat:@"%02x", (int)(digest[i])]; } return [ms copy]; } @end static NSString *sg_privateNetworkBaseUrl = nil; static BOOL sg_isEnableInterfaceDebug = NO; static BOOL sg_shouldAutoEncode = NO; static NSDictionary *sg_httpHeaders = nil; static HYBResponseType sg_responseType = kHYBResponseTypeJSON; static HYBRequestType sg_requestType = kHYBRequestTypePlainText; static HYBNetworkStatus sg_networkStatus = kHYBNetworkStatusReachableViaWiFi; static NSMutableArray *sg_requestTasks; static BOOL sg_cacheGet = YES; static BOOL sg_cachePost = NO; static BOOL sg_shouldCallbackOnCancelRequest = YES; static NSTimeInterval sg_timeout = 60.0f; static BOOL sg_shoulObtainLocalWhenUnconnected = NO; static BOOL sg_isBaseURLChanged = YES; static AFHTTPSessionManager *sg_sharedManager = nil; static NSUInteger sg_maxCacheSize = 0; @implementation HYBNetworking + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 嘗試清除緩存 if (sg_maxCacheSize > 0 && [self totalCacheSize] > 1024 * 1024 * sg_maxCacheSize) { [self clearCaches]; } }); } + (void)autoToClearCacheWithLimitedToSize:(NSUInteger)mSize { sg_maxCacheSize = mSize; } + (void)cacheGetRequest:(BOOL)isCacheGet shoulCachePost:(BOOL)shouldCachePost { sg_cacheGet = isCacheGet; sg_cachePost = shouldCachePost; } + (void)updateBaseUrl:(NSString *)baseUrl { if (![baseUrl isEqualToString:sg_privateNetworkBaseUrl] && baseUrl && baseUrl.length) { sg_isBaseURLChanged = YES; } else { sg_isBaseURLChanged = NO; } sg_privateNetworkBaseUrl = baseUrl; } + (NSString *)baseUrl { return sg_privateNetworkBaseUrl; } + (void)setTimeout:(NSTimeInterval)timeout { sg_timeout = timeout; } + (void)obtainDataFromLocalWhenNetworkUnconnected:(BOOL)shouldObtain { sg_shoulObtainLocalWhenUnconnected = shouldObtain; if (sg_shoulObtainLocalWhenUnconnected && (sg_cacheGet || sg_cachePost)) { [self detectNetwork]; } } + (void)enableInterfaceDebug:(BOOL)isDebug { sg_isEnableInterfaceDebug = isDebug; } + (BOOL)isDebug { return sg_isEnableInterfaceDebug; } static inline NSString *cachePath() { return [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/HYBNetworkingCaches"]; } + (void)clearCaches { NSString *directoryPath = cachePath(); if ([[NSFileManager defaultManager] fileExistsAtPath:directoryPath isDirectory:nil]) { NSError *error = nil; [[NSFileManager defaultManager] removeItemAtPath:directoryPath error:&error]; if (error) { NSLog(@"HYBNetworking clear caches error: %@", error); } else { NSLog(@"HYBNetworking clear caches ok"); } } } + (unsigned long long)totalCacheSize { NSString *directoryPath = cachePath(); BOOL isDir = NO; unsigned long long total = 0; if ([[NSFileManager defaultManager] fileExistsAtPath:directoryPath isDirectory:&isDir]) { if (isDir) { NSError *error = nil; NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPath error:&error]; if (error == nil) { for (NSString *subpath in array) { NSString *path = [directoryPath stringByAppendingPathComponent:subpath]; NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error]; if (!error) { total += [dict[NSFileSize] unsignedIntegerValue]; } } } } } return total; } + (NSMutableArray *)allTasks { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (sg_requestTasks == nil) { sg_requestTasks = [[NSMutableArray alloc] init]; } }); return sg_requestTasks; } + (void)cancelAllRequest { @synchronized(self) { [[self allTasks] enumerateObjectsUsingBlock:^(HYBURLSessionTask * _Nonnull task, NSUInteger idx, BOOL * _Nonnull stop) { if ([task isKindOfClass:[HYBURLSessionTask class]]) { [task cancel]; } }]; [[self allTasks] removeAllObjects]; }; } + (void)cancelRequestWithURL:(NSString *)url { if (url == nil) { return; } @synchronized(self) { [[self allTasks] enumerateObjectsUsingBlock:^(HYBURLSessionTask * _Nonnull task, NSUInteger idx, BOOL * _Nonnull stop) { if ([task isKindOfClass:[HYBURLSessionTask class]] && [task.currentRequest.URL.absoluteString hasSuffix:url]) { [task cancel]; [[self allTasks] removeObject:task]; return; } }]; }; } + (void)configRequestType:(HYBRequestType)requestType responseType:(HYBResponseType)responseType shouldAutoEncodeUrl:(BOOL)shouldAutoEncode callbackOnCancelRequest:(BOOL)shouldCallbackOnCancelRequest { sg_requestType = requestType; sg_responseType = responseType; sg_shouldAutoEncode = shouldAutoEncode; sg_shouldCallbackOnCancelRequest = shouldCallbackOnCancelRequest; } + (BOOL)shouldEncode { return sg_shouldAutoEncode; } + (void)configCommonHttpHeaders:(NSDictionary *)httpHeaders { sg_httpHeaders = httpHeaders; } + (HYBURLSessionTask *)getWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail { return [self getWithUrl:url refreshCache:refreshCache params:nil success:success fail:fail]; } + (HYBURLSessionTask *)getWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail { return [self getWithUrl:url refreshCache:refreshCache params:params progress:nil success:success fail:fail]; } + (HYBURLSessionTask *)getWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params progress:(HYBGetProgress)progress success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail { return [self _requestWithUrl:url refreshCache:refreshCache httpMedth:1 params:params progress:progress success:success fail:fail]; } + (HYBURLSessionTask *)postWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail { return [self postWithUrl:url refreshCache:refreshCache params:params progress:nil success:success fail:fail]; } + (HYBURLSessionTask *)postWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params progress:(HYBPostProgress)progress success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail { return [self _requestWithUrl:url refreshCache:refreshCache httpMedth:2 params:params progress:progress success:success fail:fail]; } + (HYBURLSessionTask *)_requestWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache httpMedth:(NSUInteger)httpMethod params:(NSDictionary *)params progress:(HYBDownloadProgress)progress success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail { if ([self shouldEncode]) { url = [self encodeUrl:url]; } AFHTTPSessionManager *manager = [self manager]; NSString *absolute = [self absoluteUrlWithPath:url]; if ([self baseUrl] == nil) { if ([NSURL URLWithString:url] == nil) { HYBAppLog(@"URLString無效,沒法生成URL。多是URL中有中文,請嘗試Encode URL"); return nil; } } else { NSURL *absoluteURL = [NSURL URLWithString:absolute]; if (absoluteURL == nil) { HYBAppLog(@"URLString無效,沒法生成URL。多是URL中有中文,請嘗試Encode URL"); return nil; } } HYBURLSessionTask *session = nil; if (httpMethod == 1) { if (sg_cacheGet) { if (sg_shoulObtainLocalWhenUnconnected) { if (sg_networkStatus == kHYBNetworkStatusNotReachable || sg_networkStatus == kHYBNetworkStatusUnknown ) { id response = [HYBNetworking cahceResponseWithURL:absolute parameters:params]; if (response) { if (success) { [self successResponse:response callback:success]; if ([self isDebug]) { [self logWithSuccessResponse:response url:absolute params:params]; } } return nil; } } } if (!refreshCache) {// 獲取緩存 id response = [HYBNetworking cahceResponseWithURL:absolute parameters:params]; if (response) { if (success) { [self successResponse:response callback:success]; if ([self isDebug]) { [self logWithSuccessResponse:response url:absolute params:params]; } } return nil; } } } session = [manager GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) { if (progress) { progress(downloadProgress.completedUnitCount, downloadProgress.totalUnitCount); } } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { [self successResponse:responseObject callback:success]; if (sg_cacheGet) { [self cacheResponseObject:responseObject request:task.currentRequest parameters:params]; } [[self allTasks] removeObject:task]; if ([self isDebug]) { [self logWithSuccessResponse:responseObject url:absolute params:params]; } } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { [[self allTasks] removeObject:task]; if ([error code] < 0 && sg_cacheGet) {// 獲取緩存 id response = [HYBNetworking cahceResponseWithURL:absolute parameters:params]; if (response) { if (success) { [self successResponse:response callback:success]; if ([self isDebug]) { [self logWithSuccessResponse:response url:absolute params:params]; } } } else { [self handleCallbackWithError:error fail:fail]; if ([self isDebug]) { [self logWithFailError:error url:absolute params:params]; } } } else { [self handleCallbackWithError:error fail:fail]; if ([self isDebug]) { [self logWithFailError:error url:absolute params:params]; } } }]; } else if (httpMethod == 2) { if (sg_cachePost ) {// 獲取緩存 if (sg_shoulObtainLocalWhenUnconnected) { if (sg_networkStatus == kHYBNetworkStatusNotReachable || sg_networkStatus == kHYBNetworkStatusUnknown ) { id response = [HYBNetworking cahceResponseWithURL:absolute parameters:params]; if (response) { if (success) { [self successResponse:response callback:success]; if ([self isDebug]) { [self logWithSuccessResponse:response url:absolute params:params]; } } return nil; } } } if (!refreshCache) { id response = [HYBNetworking cahceResponseWithURL:absolute parameters:params]; if (response) { if (success) { [self successResponse:response callback:success]; if ([self isDebug]) { [self logWithSuccessResponse:response url:absolute params:params]; } } return nil; } } } session = [manager POST:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) { if (progress) { progress(downloadProgress.completedUnitCount, downloadProgress.totalUnitCount); } } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { [self successResponse:responseObject callback:success]; if (sg_cachePost) { [self cacheResponseObject:responseObject request:task.currentRequest parameters:params]; } [[self allTasks] removeObject:task]; if ([self isDebug]) { [self logWithSuccessResponse:responseObject url:absolute params:params]; } } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { [[self allTasks] removeObject:task]; if ([error code] < 0 && sg_cachePost) {// 獲取緩存 id response = [HYBNetworking cahceResponseWithURL:absolute parameters:params]; if (response) { if (success) { [self successResponse:response callback:success]; if ([self isDebug]) { [self logWithSuccessResponse:response url:absolute params:params]; } } } else { [self handleCallbackWithError:error fail:fail]; if ([self isDebug]) { [self logWithFailError:error url:absolute params:params]; } } } else { [self handleCallbackWithError:error fail:fail]; if ([self isDebug]) { [self logWithFailError:error url:absolute params:params]; } } }]; } if (session) { [[self allTasks] addObject:session]; } return session; } + (HYBURLSessionTask *)uploadFileWithUrl:(NSString *)url uploadingFile:(NSString *)uploadingFile progress:(HYBUploadProgress)progress success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail { if ([NSURL URLWithString:uploadingFile] == nil) { HYBAppLog(@"uploadingFile無效,沒法生成URL。請檢查待上傳文件是否存在"); return nil; } NSURL *uploadURL = nil; if ([self baseUrl] == nil) { uploadURL = [NSURL URLWithString:url]; } else { uploadURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", [self baseUrl], url]]; } if (uploadURL == nil) { HYBAppLog(@"URLString無效,沒法生成URL。多是URL中有中文或特殊字符,請嘗試Encode URL"); return nil; } AFHTTPSessionManager *manager = [self manager]; NSURLRequest *request = [NSURLRequest requestWithURL:uploadURL]; HYBURLSessionTask *session = nil; [manager uploadTaskWithRequest:request fromFile:[NSURL URLWithString:uploadingFile] progress:^(NSProgress * _Nonnull uploadProgress) { if (progress) { progress(uploadProgress.completedUnitCount, uploadProgress.totalUnitCount); } } completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { [[self allTasks] removeObject:session]; [self successResponse:responseObject callback:success]; if (error) { [self handleCallbackWithError:error fail:fail]; if ([self isDebug]) { [self logWithFailError:error url:response.URL.absoluteString params:nil]; } } else { if ([self isDebug]) { [self logWithSuccessResponse:responseObject url:response.URL.absoluteString params:nil]; } } }]; if (session) { [[self allTasks] addObject:session]; } return session; } + (HYBURLSessionTask *)uploadWithImage:(UIImage *)image url:(NSString *)url filename:(NSString *)filename name:(NSString *)name mimeType:(NSString *)mimeType parameters:(NSDictionary *)parameters progress:(HYBUploadProgress)progress success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail { if ([self baseUrl] == nil) { if ([NSURL URLWithString:url] == nil) { HYBAppLog(@"URLString無效,沒法生成URL。多是URL中有中文,請嘗試Encode URL"); return nil; } } else { if ([NSURL URLWithString:[NSString stringWithFormat:@"%@%@", [self baseUrl], url]] == nil) { HYBAppLog(@"URLString無效,沒法生成URL。多是URL中有中文,請嘗試Encode URL"); return nil; } } if ([self shouldEncode]) { url = [self encodeUrl:url]; } NSString *absolute = [self absoluteUrlWithPath:url]; AFHTTPSessionManager *manager = [self manager]; HYBURLSessionTask *session = [manager POST:url parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { NSData *imageData = UIImageJPEGRepresentation(image, 1); NSString *imageFileName = filename; if (filename == nil || ![filename isKindOfClass:[NSString class]] || filename.length == 0) { NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; formatter.dateFormat = @"yyyyMMddHHmmss"; NSString *str = [formatter stringFromDate:[NSDate date]]; imageFileName = [NSString stringWithFormat:@"%@.jpg", str]; } // 上傳圖片,以文件流的格式 [formData appendPartWithFileData:imageData name:name fileName:imageFileName mimeType:mimeType]; } progress:^(NSProgress * _Nonnull uploadProgress) { if (progress) { progress(uploadProgress.completedUnitCount, uploadProgress.totalUnitCount); } } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { [[self allTasks] removeObject:task]; [self successResponse:responseObject callback:success]; if ([self isDebug]) { [self logWithSuccessResponse:responseObject url:absolute params:parameters]; } } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { [[self allTasks] removeObject:task]; [self handleCallbackWithError:error fail:fail]; if ([self isDebug]) { [self logWithFailError:error url:absolute params:nil]; } }]; [session resume]; if (session) { [[self allTasks] addObject:session]; } return session; } + (HYBURLSessionTask *)downloadWithUrl:(NSString *)url saveToPath:(NSString *)saveToPath progress:(HYBDownloadProgress)progressBlock success:(HYBResponseSuccess)success failure:(HYBResponseFail)failure { if ([self baseUrl] == nil) { if ([NSURL URLWithString:url] == nil) { HYBAppLog(@"URLString無效,沒法生成URL。多是URL中有中文,請嘗試Encode URL"); return nil; } } else { if ([NSURL URLWithString:[NSString stringWithFormat:@"%@%@", [self baseUrl], url]] == nil) { HYBAppLog(@"URLString無效,沒法生成URL。多是URL中有中文,請嘗試Encode URL"); return nil; } } NSURLRequest *downloadRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; AFHTTPSessionManager *manager = [self manager]; HYBURLSessionTask *session = nil; session = [manager downloadTaskWithRequest:downloadRequest progress:^(NSProgress * _Nonnull downloadProgress) { if (progressBlock) { progressBlock(downloadProgress.completedUnitCount, downloadProgress.totalUnitCount); } } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) { return [NSURL fileURLWithPath:saveToPath]; } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) { [[self allTasks] removeObject:session]; if (error == nil) { if (success) { success(filePath.absoluteString); } if ([self isDebug]) { HYBAppLog(@"Download success for url %@", [self absoluteUrlWithPath:url]); } } else { [self handleCallbackWithError:error fail:failure]; if ([self isDebug]) { HYBAppLog(@"Download fail for url %@, reason : %@", [self absoluteUrlWithPath:url], [error description]); } } }]; [session resume]; if (session) { [[self allTasks] addObject:session]; } return session; } #pragma mark - Private + (AFHTTPSessionManager *)manager { @synchronized (self) { // 只要不切換baseurl,就一直使用同一個session manager if (sg_sharedManager == nil || sg_isBaseURLChanged) { // 開啓轉圈圈 [AFNetworkActivityIndicatorManager sharedManager].enabled = YES; AFHTTPSessionManager *manager = nil;; if ([self baseUrl] != nil) {
#warning session 容易形成循環引用 delegate 是強引用類型 解決方法:1.經過單例的方式建立 2.delloc中釋放delegate manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:[self baseUrl]]]; } else { manager = [AFHTTPSessionManager manager]; } switch (sg_requestType) { case kHYBRequestTypeJSON: { manager.requestSerializer = [AFJSONRequestSerializer serializer]; break; } case kHYBRequestTypePlainText: { manager.requestSerializer = [AFHTTPRequestSerializer serializer]; break; } default: { break; } } switch (sg_responseType) { case kHYBResponseTypeJSON: { manager.responseSerializer = [AFJSONResponseSerializer serializer]; break; } case kHYBResponseTypeXML: { manager.responseSerializer = [AFXMLParserResponseSerializer serializer]; break; } case kHYBResponseTypeData: { manager.responseSerializer = [AFHTTPResponseSerializer serializer]; break; } default: { break; } } manager.requestSerializer.stringEncoding = NSUTF8StringEncoding; for (NSString *key in sg_httpHeaders.allKeys) { if (sg_httpHeaders[key] != nil) { [manager.requestSerializer setValue:sg_httpHeaders[key] forHTTPHeaderField:key]; } } manager.responseSerializer.acceptableContentTypes = [NSSet setWithArray:@[@"application/json", @"text/html", @"text/json", @"text/plain", @"text/javascript", @"text/xml", @"image/*"]]; manager.requestSerializer.timeoutInterval = sg_timeout; // 設置容許同時最大併發數量,過大容易出問題 manager.operationQueue.maxConcurrentOperationCount = 3; sg_sharedManager = manager; } } return sg_sharedManager; } + (void)detectNetwork { AFNetworkReachabilityManager *reachabilityManager = [AFNetworkReachabilityManager sharedManager]; [reachabilityManager startMonitoring]; [reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { if (status == AFNetworkReachabilityStatusNotReachable){ sg_networkStatus = kHYBNetworkStatusNotReachable; } else if (status == AFNetworkReachabilityStatusUnknown){ sg_networkStatus = kHYBNetworkStatusUnknown; } else if (status == AFNetworkReachabilityStatusReachableViaWWAN){ sg_networkStatus = kHYBNetworkStatusReachableViaWWAN; } else if (status == AFNetworkReachabilityStatusReachableViaWiFi){ sg_networkStatus = kHYBNetworkStatusReachableViaWiFi; } }]; } + (void)logWithSuccessResponse:(id)response url:(NSString *)url params:(NSDictionary *)params { HYBAppLog(@"\n"); HYBAppLog(@"\nRequest success, URL: %@\n params:%@\n response:%@\n\n", [self generateGETAbsoluteURL:url params:params], params, [self tryToParseData:response]); } + (void)logWithFailError:(NSError *)error url:(NSString *)url params:(id)params { NSString *format = @" params: "; if (params == nil || ![params isKindOfClass:[NSDictionary class]]) { format = @""; params = @""; } HYBAppLog(@"\n"); if ([error code] == NSURLErrorCancelled) { HYBAppLog(@"\nRequest was canceled mannully, URL: %@ %@%@\n\n", [self generateGETAbsoluteURL:url params:params], format, params); } else { HYBAppLog(@"\nRequest error, URL: %@ %@%@\n errorInfos:%@\n\n", [self generateGETAbsoluteURL:url params:params], format, params, [error localizedDescription]); } } // 僅對一級字典結構起做用 + (NSString *)generateGETAbsoluteURL:(NSString *)url params:(id)params { if (params == nil || ![params isKindOfClass:[NSDictionary class]] || [params count] == 0) { return url; } NSString *queries = @""; for (NSString *key in params) { id value = [params objectForKey:key]; if ([value isKindOfClass:[NSDictionary class]]) { continue; } else if ([value isKindOfClass:[NSArray class]]) { continue; } else if ([value isKindOfClass:[NSSet class]]) { continue; } else { queries = [NSString stringWithFormat:@"%@%@=%@&", (queries.length == 0 ? @"&" : queries), key, value]; } } if (queries.length > 1) { queries = [queries substringToIndex:queries.length - 1]; } if (([url hasPrefix:@"http://"] || [url hasPrefix:@"https://"]) && queries.length > 1) { if ([url rangeOfString:@"?"].location != NSNotFound || [url rangeOfString:@"#"].location != NSNotFound) { url = [NSString stringWithFormat:@"%@%@", url, queries]; } else { queries = [queries substringFromIndex:1]; url = [NSString stringWithFormat:@"%@?%@", url, queries]; } } return url.length == 0 ? queries : url; } + (NSString *)encodeUrl:(NSString *)url { return [self hyb_URLEncode:url]; } + (id)tryToParseData:(id)responseData { if ([responseData isKindOfClass:[NSData class]]) { // 嘗試解析成JSON if (responseData == nil) { return responseData; } else { NSError *error = nil; NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:&error]; if (error != nil) { return responseData; } else { return response; } } } else { return responseData; } } + (void)successResponse:(id)responseData callback:(HYBResponseSuccess)success { if (success) { success([self tryToParseData:responseData]); } } + (NSString *)hyb_URLEncode:(NSString *)url { return [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; // 採用下面的方式反而不能請求成功 // NSString *newString = // CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, // (CFStringRef)url, // NULL, // CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding))); // if (newString) { // return newString; // } // // return url; } + (id)cahceResponseWithURL:(NSString *)url parameters:params { id cacheData = nil; if (url) { // Try to get datas from disk NSString *directoryPath = cachePath(); NSString *absoluteURL = [self generateGETAbsoluteURL:url params:params]; NSString *key = [NSString hybnetworking_md5:absoluteURL]; NSString *path = [directoryPath stringByAppendingPathComponent:key]; NSData *data = [[NSFileManager defaultManager] contentsAtPath:path]; if (data) { cacheData = data; HYBAppLog(@"Read data from cache for url: %@\n", url); } } return cacheData; } + (void)cacheResponseObject:(id)responseObject request:(NSURLRequest *)request parameters:params { if (request && responseObject && ![responseObject isKindOfClass:[NSNull class]]) { NSString *directoryPath = cachePath(); NSError *error = nil; if (![[NSFileManager defaultManager] fileExistsAtPath:directoryPath isDirectory:nil]) { [[NSFileManager defaultManager] createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error]; if (error) { HYBAppLog(@"create cache dir error: %@\n", error); return; } } NSString *absoluteURL = [self generateGETAbsoluteURL:request.URL.absoluteString params:params]; NSString *key = [NSString hybnetworking_md5:absoluteURL]; NSString *path = [directoryPath stringByAppendingPathComponent:key]; NSDictionary *dict = (NSDictionary *)responseObject; NSData *data = nil; if ([dict isKindOfClass:[NSData class]]) { data = responseObject; } else { data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error]; } if (data && error == nil) { BOOL isOk = [[NSFileManager defaultManager] createFileAtPath:path contents:data attributes:nil]; if (isOk) { HYBAppLog(@"cache file ok for request: %@\n", absoluteURL); } else { HYBAppLog(@"cache file error for request: %@\n", absoluteURL); } } } } + (NSString *)absoluteUrlWithPath:(NSString *)path { if (path == nil || path.length == 0) { return @""; } if ([self baseUrl] == nil || [[self baseUrl] length] == 0) { return path; } NSString *absoluteUrl = path; if (![path hasPrefix:@"http://"] && ![path hasPrefix:@"https://"]) { if ([[self baseUrl] hasSuffix:@"/"]) { if ([path hasPrefix:@"/"]) { NSMutableString * mutablePath = [NSMutableString stringWithString:path]; [mutablePath deleteCharactersInRange:NSMakeRange(0, 1)]; absoluteUrl = [NSString stringWithFormat:@"%@%@", [self baseUrl], mutablePath]; } else { absoluteUrl = [NSString stringWithFormat:@"%@%@",[self baseUrl], path]; } } else { if ([path hasPrefix:@"/"]) { absoluteUrl = [NSString stringWithFormat:@"%@%@",[self baseUrl], path]; } else { absoluteUrl = [NSString stringWithFormat:@"%@/%@", [self baseUrl], path]; } } } return absoluteUrl; } + (void)handleCallbackWithError:(NSError *)error fail:(HYBResponseFail)fail { if ([error code] == NSURLErrorCancelled) { if (sg_shouldCallbackOnCancelRequest) { if (fail) { fail(error); } } } else { if (fail) { fail(error); } } } @end
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> // 項目打包上線都不會打印日誌,所以可放心。 #ifdef DEBUG #define HYBAppLog(s, ... ) NSLog( @"[%@ in line %d] ===============>%@", [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] ) #else #define HYBAppLog(s, ... ) #endif /*! * @author 黃, 16-01-08 14:01:26 * * 下載進度 * * @param bytesRead 已下載的大小 * @param totalBytesRead 文件總大小 * @param totalBytesExpectedToRead 還有多少須要下載 */ typedef void (^HYBDownloadProgress)(int64_t bytesRead, int64_t totalBytesRead); typedef HYBDownloadProgress HYBGetProgress; typedef HYBDownloadProgress HYBPostProgress; /*! * @author 黃, 16-01-08 14:01:26 * * 上傳進度 * * @param bytesWritten 已上傳的大小 * @param totalBytesWritten 總上傳大小 */ typedef void (^HYBUploadProgress)(int64_t bytesWritten, int64_t totalBytesWritten); typedef NS_ENUM(NSUInteger, HYBResponseType) { kHYBResponseTypeJSON = 1, // 默認 kHYBResponseTypeXML = 2, // XML // 特殊狀況下,一轉換服務器就沒法識別的,默認會嘗試轉換成JSON,若失敗則須要本身去轉換 kHYBResponseTypeData = 3 }; typedef NS_ENUM(NSUInteger, HYBRequestType) { kHYBRequestTypeJSON = 1, // 默認 kHYBRequestTypePlainText = 2 // 普通text/html }; typedef NS_ENUM(NSInteger, HYBNetworkStatus) { kHYBNetworkStatusUnknown = -1,//未知網絡 kHYBNetworkStatusNotReachable = 0,//網絡無鏈接 kHYBNetworkStatusReachableViaWWAN = 1,//2,3,4G網絡 kHYBNetworkStatusReachableViaWiFi = 2,//WIFI網絡 }; @class NSURLSessionTask; // 請勿直接使用NSURLSessionDataTask,以減小對第三方的依賴 // 全部接口返回的類型都是基類NSURLSessionTask,若要接收返回值 // 且處理,請轉換成對應的子類類型 typedef NSURLSessionTask HYBURLSessionTask; typedef void(^HYBResponseSuccess)(id response); typedef void(^HYBResponseFail)(NSError *error); /*! * @author huangyibiao, 15-11-15 13:11:31 * * 基於AFNetworking的網絡層封裝類. * * @note 這裏只提供公共api */ @interface HYBNetworking : NSObject /*! * @author 黃, 15-11-15 13:11:45 * * 用於指定網絡請求接口的基礎url,如: * http://henishuo.com或者http://101.200.209.244 * 一般在AppDelegate中啓動時就設置一次就能夠了。若是接口有來源 * 於多個服務器,能夠調用更新 * * @param baseUrl 網絡接口的基礎url */ + (void)updateBaseUrl:(NSString *)baseUrl; + (NSString *)baseUrl; /** * 設置請求超時時間,默認爲60秒 * * @param timeout 超時時間 */ + (void)setTimeout:(NSTimeInterval)timeout; /** * 當檢查到網絡異常時,是否從從本地提取數據。默認爲NO。一旦設置爲YES,當設置刷新緩存時, * 若網絡異常也會從緩存中讀取數據。一樣,若是設置超時不回調,一樣也會在網絡異常時回調,除非 * 本地沒有數據! * * @param shouldObtain YES/NO */ + (void)obtainDataFromLocalWhenNetworkUnconnected:(BOOL)shouldObtain; /** * @author 黃 * * 默認只緩存GET請求的數據,對於POST請求是不緩存的。若是要緩存POST獲取的數據,須要手動調用設置 * 對JSON類型數據有效,對於PLIST、XML不肯定! * * @param isCacheGet 默認爲YES * @param shouldCachePost 默認爲NO */ + (void)cacheGetRequest:(BOOL)isCacheGet shoulCachePost:(BOOL)shouldCachePost; /** * @author 黃 * * 獲取緩存總大小/bytes * * @return 緩存大小 */ + (unsigned long long)totalCacheSize; /** * 默認不會自動清除緩存,若是須要,能夠設置自動清除緩存,而且須要指定上限。當指定上限>0M時, * 若緩存達到了上限值,則每次啓動應用則嘗試自動去清理緩存。 * * @param mSize 緩存上限大小,單位爲M(兆),默認爲0,表示不清理 */ + (void)autoToClearCacheWithLimitedToSize:(NSUInteger)mSize; /** * @author 黃 * * 清除緩存 */ + (void)clearCaches; /*! * @author 黃, 15-11-15 14:11:40 * * 開啓或關閉接口打印信息 * * @param isDebug 開發期,最好打開,默認是NO */ + (void)enableInterfaceDebug:(BOOL)isDebug; /*! * @author 黃, 15-12-25 15:12:45 * * 配置請求格式,默認爲JSON。若是要求傳XML或者PLIST,請在全局配置一下 * * @param requestType 請求格式,默認爲JSON * @param responseType 響應格式,默認爲JSO, * @param shouldAutoEncode YES or NO,默認爲NO,是否自動encode url * @param shouldCallbackOnCancelRequest 當取消請求時,是否要回調,默認爲YES */ + (void)configRequestType:(HYBRequestType)requestType responseType:(HYBResponseType)responseType shouldAutoEncodeUrl:(BOOL)shouldAutoEncode callbackOnCancelRequest:(BOOL)shouldCallbackOnCancelRequest; /*! * @author 黃, 15-11-16 13:11:41 * * 配置公共的請求頭,只調用一次便可,一般放在應用啓動的時候配置就能夠了 * * @param httpHeaders 只須要將與服務器商定的固定參數設置便可 */ + (void)configCommonHttpHeaders:(NSDictionary *)httpHeaders; /** * @author 黃 * * 取消全部請求 */ + (void)cancelAllRequest; /** * @author 黃 * * 取消某個請求。若是是要取消某個請求,最好是引用接口所返回來的HYBURLSessionTask對象, * 而後調用對象的cancel方法。若是不想引用對象,這裏額外提供了一種方法來實現取消某個請求 * * @param url URL,能夠是絕對URL,也能夠是path(也就是不包括baseurl) */ + (void)cancelRequestWithURL:(NSString *)url; /*! * @author 黃, 15-11-15 13:11:50 * * GET請求接口,若不指定baseurl,可傳完整的url * * @param url 接口路徑,如/path/getArticleList * @param refreshCache 是否刷新緩存。因爲請求成功也可能沒有數據,對於業務失敗,只能經過人爲手動判斷 * @param params 接口中所須要的拼接參數,如@{"categoryid" : @(12)} * @param success 接口成功請求到數據的回調 * @param fail 接口請求數據失敗的回調 * * @return 返回的對象中有可取消請求的API */ + (HYBURLSessionTask *)getWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail; // 多一個params參數 + (HYBURLSessionTask *)getWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail; // 多一個帶進度回調 + (HYBURLSessionTask *)getWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params progress:(HYBGetProgress)progress success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail; /*! * @author 黃, 15-11-15 13:11:50 * * POST請求接口,若不指定baseurl,可傳完整的url * * @param url 接口路徑,如/path/getArticleList * @param params 接口中所需的參數,如@{"categoryid" : @(12)} * @param success 接口成功請求到數據的回調 * @param fail 接口請求數據失敗的回調 * * @return 返回的對象中有可取消請求的API */ + (HYBURLSessionTask *)postWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail; + (HYBURLSessionTask *)postWithUrl:(NSString *)url refreshCache:(BOOL)refreshCache params:(NSDictionary *)params progress:(HYBPostProgress)progress success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail; /** * @author 黃, 16-01-31 00:01:40 * * 圖片上傳接口,若不指定baseurl,可傳完整的url * * @param image 圖片對象 * @param url 上傳圖片的接口路徑,如/path/images/ * @param filename 給圖片起一個名字,默認爲當前日期時間,格式爲"yyyyMMddHHmmss",後綴爲`jpg` * @param name 與指定的圖片相關聯的名稱,這是由後端寫接口的人指定的,如imagefiles * @param mimeType 默認爲image/jpeg * @param parameters 參數 * @param progress 上傳進度 * @param success 上傳成功回調 * @param fail 上傳失敗回調 * * @return */ + (HYBURLSessionTask *)uploadWithImage:(UIImage *)image url:(NSString *)url filename:(NSString *)filename name:(NSString *)name mimeType:(NSString *)mimeType parameters:(NSDictionary *)parameters progress:(HYBUploadProgress)progress success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail; /** * @author 黃, 16-01-31 00:01:59 * * 上傳文件操做 * * @param url 上傳路徑 * @param uploadingFile 待上傳文件的路徑 * @param progress 上傳進度 * @param success 上傳成功回調 * @param fail 上傳失敗回調 * * @return */ + (HYBURLSessionTask *)uploadFileWithUrl:(NSString *)url uploadingFile:(NSString *)uploadingFile progress:(HYBUploadProgress)progress success:(HYBResponseSuccess)success fail:(HYBResponseFail)fail; /*! * @author 黃, 16-01-08 15:01:11 * * 下載文件 * * @param url 下載URL * @param saveToPath 下載到哪一個路徑下 * @param progressBlock 下載進度 * @param success 下載成功後的回調 * @param failure 下載失敗後的回調 */ + (HYBURLSessionTask *)downloadWithUrl:(NSString *)url saveToPath:(NSString *)saveToPath progress:(HYBDownloadProgress)progressBlock success:(HYBResponseSuccess)success failure:(HYBResponseFail)failure; @end
使用:json
// 一般放在appdelegate就能夠了 [HYBNetworking updateBaseUrl:@"http://apistore.baidu.com"]; [HYBNetworking enableInterfaceDebug:YES]; // 配置請求和響應類型,因爲部分夥伴們的服務器不接收JSON傳過去,如今默認值改爲了plainText [HYBNetworking configRequestType:kHYBRequestTypePlainText responseType:kHYBResponseTypeJSON shouldAutoEncodeUrl:YES callbackOnCancelRequest:NO]; /* [HYBNetworking.m:in line: 189]-->[message: absoluteUrl: http://apistore.baidu.com/microservice/cityinfo?cityname=%E5%8C%97%E4%BA%AC params:(null) response:{ errNum = 0; retData = { cityCode = 101010100; cityName = "\U5317\U4eac"; provinceName = "\U5317\U4eac"; telAreaCode = 010; zipCode = 100000; }; retMsg = success; } ] */ // 設置GET、POST請求都緩存 [HYBNetworking cacheGetRequest:YES shoulCachePost:YES]; // 測試GET API NSString *url = @"http://api.map.baidu.com/telematics/v3/weather?location=嘉興&output=json&ak=5slgyqGDENN7Sy7pw29IUvrZ"; // 設置請求類型爲text/html類型 // [HYBNetworking configRequestType:kHYBRequestTypePlainText]; // [HYBNetworking configResponseType:kHYBResponseTypeData]; // 若是請求回來的數據是業務數據,可是是失敗的,這時候須要外部開發人員才能判斷是業務失敗。 // 內部處理是隻有走failure的才能判斷爲無效數據,纔不會緩存 // 若是設置爲YES,則每次會去刷新緩存,也就是不會讀取緩存,即便已經緩存起來 // 新下載的數據會從新緩存起來 [HYBNetworking getWithUrl:url refreshCache:NO params:nil progress:^(int64_t bytesRead, int64_t totalBytesRead) { NSLog(@"progress: %f, cur: %lld, total: %lld", (bytesRead * 1.0) / totalBytesRead, bytesRead, totalBytesRead); } success:^(id response) { } fail:^(NSError *error) { }]; // 測試POST API: // 假數據 NSDictionary *postDict = @{ @"urls": @"http://www.henishuo.com/git-use-inwork/", @"goal" : @"site", @"total" : @(123) }; NSString *path = @"/urls?site=www.henishuo.com&token=bRidefmXoNxIi3Jp"; // 因爲這裏有兩套基礎路徑,用時就須要更新 [HYBNetworking updateBaseUrl:@"http://data.zz.baidu.com"]; // 每次刷新緩存 // 若是獲取到的業務數據是不正確的,則須要下次調用時設置爲YES,表示要刷新緩存 // HYBURLSessionTask *task = [HYBNetworking postWithUrl:path refreshCache:YES params:postDict success:^(id response) { } fail:^(NSError *error) { }]; // 取消所有請求 // [HYBNetworking cancelAllRequest]; // 取消單個請求方法一 // [HYBNetworking cancelRequestWithURL:path]; // 取消單個請求方法二 // [task cancel]; NSLog(@"%lld", [HYBNetworking totalCacheSize]); // [HYBNetworking clearCaches]; path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/b.zip"]; [HYBNetworking downloadWithUrl:@"http://wiki.lbsyun.baidu.com/cms/iossdk/sdk/BaiduMap_IOSSDK_v2.10.2_All.zip" saveToPath:path progress:^(int64_t bytesRead, int64_t totalBytesRead) { } success:^(id response) { } failure:^(NSError *error) { }]; // NSLog(@"%@", task); }