iOS存储离线数据到本地待有网后自动发布
2018-11-05 本文已影响2人
朝阳小麦
使用人群:iOS开发(OC语言)。
本文内容:手机断网时存储对象到本地,有网时再自动发布。
难点:自定义对象的本地存储,以及UIImage如何存储本地。
借用第三方:
AFNetworkReachabilityManager(pod AFNetworking)判断是否有网络,以及监听网络状况;
YYModel.h(pod YYModel)来帮助对象和json数据互转;
YYCache.h(pod YYCache)来帮助存储数据到本地。
步骤:
1.判断网络状态;
2.无网络,则存储数据到本地;
3.设置网络监听,当有网络时,检测是否有未发布数据,有则发布;
4.每次启动APP,检测是否有未发布数据,有则发布。
详述:
这里,我封装了一个SMDelaySubmitManager类,提供方法:判断网络状态、保存数据到本地、发布数据等方法。代码如下:
SMDelaySubmitManager.h文件代码:
#import <Foundation/Foundation.h>
@interface SMDelaySubmitManager : NSObject
//获取单例
+ (SMDelaySubmitManager *)sharedDelaySubmitManager;
//发布离线数据
+ (void)submitDelayDatas;
//判断当前网络状态
- (BOOL)hasNetwork;
//保存离线数据到本地
- (void)saveDatasWaitingSubmitWithParams:(NSDictionary *)params photos:(NSArray <SMPhotoModel *>*)photos path:(NSString *)path;
//发布离线数据
- (void)submitDatas;
@end
SMDelaySubmitManager.m文件代码:
#import "SMDelaySubmitManager.h"
#import "AFNetworking.h"
#import <YYCache/YYCache.h>
#import "SMPhotoUploadNetworkManager.h"
@interface SMDelaySubmitManager ()
@property (nonatomic, assign) NSInteger delayIndex;
@property (nonatomic, strong) YYDiskCache *diskCache;
@end
@implementation SMDelaySubmitManager
//创建单例
+ (SMDelaySubmitManager *)sharedDelaySubmitManager {
static SMDelaySubmitManager *delaySubmitManager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (!delaySubmitManager) {
delaySubmitManager = [[SMDelaySubmitManager alloc] init];
}
});
return delaySubmitManager;
}
- (instancetype)init
{
self = [super init];
if (self) {
//初始化diskCache对象
NSString *pathString = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
self.diskCache = [[YYDiskCache alloc] initWithPath:pathString inlineThreshold:YYKVStorageTypeMixed];
//存储delayIndex字段,为了拼接缓存数据的keyName,一定要存储本地,确保避免名字重复。
NSUserDefaults *userDef = [NSUserDefaults standardUserDefaults];
_delayIndex = [userDef integerForKey:@"delayIndex"];
}
return self;
}
//判断是否有网络
- (BOOL)hasNetwork {
AFNetworkReachabilityManager *net = [AFNetworkReachabilityManager sharedManager];
return net.isReachable;
}
//保存数据到本地。需要接口参数param、接口名interface(path)、图片数据(根据需求,我这里需要传图片到OSS的)。
- (void)saveDatasWaitingSubmitWithParams:(NSDictionary *)params photos:(NSArray <SMPhotoModel *>*)photos path:(NSString *)path {
//注意每次保存都要增加delayIndex,避免keyName重复。
_delayIndex ++;
NSUserDefaults *userDef = [NSUserDefaults standardUserDefaults];
[userDef setInteger:_delayIndex forKey:@"delayIndex"];
[userDef synchronize];
NSString *paramStr = [params yy_modelToJSONString];
NSMutableArray *tempArr = [NSMutableArray array];
for (int i=0; i<photos.count; i++) {
SMPhotoModel *model = photos[i];
[model.photo turnImageToEncodeString];//把UIImage图片资源转成字符串。
[tempArr addObject:[model yy_modelToJSONObject]];
}
NSString *photoStr = [tempArr yy_modelToJSONString];
//把数据拼接成json数据,存储本地,文件存储。这里记得打个点断,把json字符串复制下来,去网上json校验下格式是否正确。
NSString *keyStr = [NSString stringWithFormat:@"SMDelaySubmitManager_%ld", _delayIndex];
NSString *json = [NSString stringWithFormat:@"{\"paramStr\":%@,\"photoData\":%@, \"path\":\"%@\"}", paramStr, photoStr, path];
[self.diskCache setObject:json forKey:keyStr];
//Toast:"无网络,会在有网络后自动发布离线数据!";
}
- (void)submitDatas {
// 获取缓存数据
for (NSInteger i=1; i<=_delayIndex; i++) {
NSString *keyStr = [NSString stringWithFormat:@"SMDelaySubmitManager_%ld", i];
NSString *jsonStr = (NSString *)[self.diskCache objectForKey:keyStr];
if (jsonStr.length > 0) {
NSData *jsonData = [jsonStr dataUsingEncoding:NSUTF8StringEncoding];
NSError *err;
NSDictionary *jsonDic = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:&err];
if (!err && jsonDic) {
NSDictionary *paramJsonDic = [jsonDic objectForKey:@"paramStr"];
NSString *path = [jsonDic objectForKey:@"path"];
NSArray *photoArr = [jsonDic objectForKey:@"photoData"];
NSArray *photos = [NSArray yy_modelArrayWithClass:[SMPhotoModel class] json:photoArr];
for (SMPhotoModel *m in photos) {
if (m.photo.imageDataStr.length > 0) {
NSData *decodedImageData = [[NSData alloc]initWithBase64EncodedString:m.photo.imageDataStr options:NSDataBase64DecodingIgnoreUnknownCharacters];
UIImage *decodedImage = [UIImage imageWithData:decodedImageData];
m.photo.image = decodedImage;
m.photo.imageDataStr = @"";//清掉该数据,因为数据太庞大,会卡死。
}
}
//请求接口,发布数据。根据自己项目的封装,修改。
[SMPhotoUploadNetworkManager postDataWithUploadFiles:photos?:@[] params:paramJsonDic?:@{} path:path complete:^(AppBaseModel *response) {
if (response && response.status) {
//Toast:离线数据发布成功!
//把离线数据移除(Warning:一定要移除,不然会重复发送数据)
[self.diskCache removeObjectForKey:keyStr];
}
}];
}
}
}
}
+ (void)submitDelayDatas {
//发布离线缓存数据
SMDelaySubmitManager *delaySubmitManager = [SMDelaySubmitManager sharedDelaySubmitManager];
if ([delaySubmitManager hasNetwork]) {
[delaySubmitManager submitDatas];
}
}
封装的代码粘贴完成。
解析:
问题1:如何存储UIImage数据到本地?
存储UIImage到本地肯定是很多方法的。这里我选择把UIImage对象,转成string字符串,进行存储。如果不转的话,发布的时候再转回UIImage对象,可能会数据损坏。
UIImage转NSString参考代码如下:
/**
手机选择的图片文件.
*/
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) NSString *imageDataStr;
- (void)turnImageToEncodeString;//把image图片资源,转为字符串,方便存储本地文件内做缓存。
//把UIImage转成NSString字符串
- (void)turnImageToEncodeString {
NSData *data = UIImageJPEGRepresentation(_image, 1.0f);
if (data) {
NSString *strimage64 = [data tf_base64EncodedString];
_imageDataStr = strimage64;
} else {
_imageDataStr = @"";
}
}
那么怎么检测数据是否损坏呢?如下:
//如果image对象能正常转成NSData数据,则该图片未损坏。
NSData *imageData = UIImageJPEGRepresentation(model.image, 1.0);
if (imageData) {
//未损坏
} else {
//已损坏
}
问题2:对象如何存储?
自定义对象Model需要实现<NSCoding>协议。实现它的两个方法,示例代码如下:
//注意,BOOL和NSInteger等非对象数据,是存储在栈中的,不需要写。
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.photo = [_photo initWithCoder:aDecoder];//photo是个其他Model对象
self.markedPhoto = [_markedPhoto initWithCoder:aDecoder];
self.imageTime = [aDecoder decodeObjectForKey:@"imageTime"];
self.userName = [aDecoder decodeObjectForKey:@"userName"];
self.userId = [aDecoder decodeObjectForKey:@"userId"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[_photo encodeWithCoder:aCoder];
[_markedPhoto encodeWithCoder:aCoder];
[aCoder encodeObject:self.imageTime forKey:@"imageTime"];
[aCoder encodeObject:self.userName forKey:@"userName"];
[aCoder encodeObject:self.userId forKey:@"userId"];
}
问题3:网络的监听?
#import <AFNetworking/AFNetworking.h>
/**
检测网络状态
*/
+ (void)startMonitoringNetworkStatus {
//创建网络监测者
AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager];
/*枚举里面四个状态 分别对应 未知 无网络 数据 WiFi
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
AFNetworkReachabilityStatusUnknown = -1, 未知
AFNetworkReachabilityStatusNotReachable = 0, 无网络
AFNetworkReachabilityStatusReachableViaWWAN = 1, 蜂窝数据网络
AFNetworkReachabilityStatusReachableViaWiFi = 2, WiFi
};
*/
[manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusUnknown:
// NSLog(@"未知网络状态");
break;
case AFNetworkReachabilityStatusNotReachable:
// NSLog(@"无网络");
break;
case AFNetworkReachabilityStatusReachableViaWWAN:
// NSLog(@"蜂窝数据网");
[SMDelaySubmitManager submitDelayDatas];//发布离线数据
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
// NSLog(@"WiFi网络");
[SMDelaySubmitManager submitDelayDatas];//发布离线数据
break;
default:
break;
}
}] ;
[manager startMonitoring];
}
有其他问题,欢迎留言。