美文网首页iOS旅途iOS高级开发面试题整理
iOS存储离线数据到本地待有网后自动发布

iOS存储离线数据到本地待有网后自动发布

作者: 朝阳小麦 | 来源:发表于2018-11-05 16:56 被阅读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];
    }
    

    有其他问题,欢迎留言。

    相关文章

      网友评论

        本文标题:iOS存储离线数据到本地待有网后自动发布

        本文链接:https://www.haomeiwen.com/subject/vqvsxqtx.html