• iOS应用千万级架构:存储持久化


    应用场景

    iOS10后,发现在大量用户在NSUserDefaults中取值失败,导致一系列的持久化状态丢失。故切换到SQLitie3持久化更适合大型项目的使用。

    场景一:在大型项目中,经常需要我们写的代码支持可降级,对一些新功能进行灰度验证。那运营开关是必不可少的。运营开关的配置就需要持久化设置了,即时网络异常,也需要读取上一次的正常记录的。

    场景二:在大型项目中,有一些资源是需要动态配置的,那就可以集中放在一个接口里处理。比如大促的动态图、资源背景图、弹窗提示文案等。当接口异常时,需要读取本地数据库里上次存储的

    场景三:用户的常规操作设置,也需要持久化记录,比如,新手指引弹框,当用户点了不再提醒,就应该记录到本地,下次不再提醒。还有就是超过多少次,不再提醒等一下用户的设置

    场景四:需要全局使用的数据,也可以放到内存中,但不存入数据库,只当是内存的一个单例管理。

    具体思路

    1. 依赖第三方库FMDB,YYModel
    2. 基于FMDB,按业务建立不同的数据表,将需要持久化的数据,通过key-Value的存储方式,存储到sqlite3数据库中
    3. 全局状态量均由VSPersistence这个单例来进行管理,拆分为5个模块: defaultPersistence / dynamicPersistence / memoryPersistence / switchPersistence / userPersistence
    4. APP启动,将sqlite3的数据加载到JSPersistence中

    场景及数据表说明

    1.defaultPersistence(YSDefaultPersistence)

    用于管理需要持久化的普通状态量,可理解为不属于这4个模块(dynamicPersistence / memoryPersistence / switchPersistence / userPersistence)的状态量。

    2.dynamicPersistence(YSDynamicPersistence)

    动态资源,APP动态配置的资源,APP启动时,请求接口返回 

    3.memoryPersistence(YSMemoryPersistence)

    用于管理不需要持久化的配置项。

    4.switchPersistence(YSSwitchPersistence)

    运营开关,降级开关,APP启动时请求接口返回

    5.userPersistence(YSUserPersistence)

    用于管理记录用户行为的配置项,如:记录提示页是否展示过,记录提示页上次展示的时间……

    YSPersistence全局单例类

    全局单例类,管理上面5个模块场景

    @interface YSPersistence : NSObject
    
    /** 用于管理需要持久化的普通状态量,可理解为不属于这4个模块(dynamicPersistence / memoryPersistence / switchPersistence / userPersistence)的状态量 */
    + (YSDefaultPersistence *)defaultPersistence;
    
    /** 动态资源,APP动态配置的资源,APP启动时,请求接口返回 */
    + (YSDynamicPersistence *)dynamicPersistence;
    
    /** 用于管理不需要持久化的配置项,仅存在内存中。比如某个接口数据返回,需要记录某个字段的值 */
    + (YSMemoryPersistence *)memoryPersistence;
    
    /** 运营开关,降级开关,APP启动时请求接口返回 */
    + (YSSwitchPersistence *)switchPersistence;
    
    /** 用于管理记录用户行为的配置项,如:记录提示页是否展示过,记录提示页上次展示的时间 */
    + (YSUserPersistence *)userPersistence;
    
    @end

    底层结构

    1. YSKeyValueStack

    轻量级的用于单例化FMDatabaseQueue的堆栈类,基于Key-Value的存储方式,做了一些定制化的优化配置。

    主要方法:

    // 给数据表增加一个key-value
    void YSKeyValueSetKeyValueToTable(FMDatabase *db, NSString *key, id value, NSString *tableName) {
        NSDictionary<NSString *, id> *JSONObject = @{ key: value };
        // [JSONObject yy_modelToJSONData]可能是因为多线程修改了内容导致崩溃
        NSData *data = [[JSONObject copy] yy_modelToJSONData];
        if (!data) {
    #if DEBUG
            dispatch_async(dispatch_get_main_queue(), ^{
                @throw [NSException exceptionWithName:@"Key-Value 不是有效的JSON对象(上报到RTX吧)" reason:@"保持统一性" userInfo:nil];
            });
    #endif
            return;
        }
        NSString *SQLScript = [NSString stringWithFormat:kSQLFormatterForReplaceValue, tableName, key];
        [db executeUpdate:SQLScript, data];
    }
    
    // 从数据表里删除key-value
    void JSKeyValueRemoveKeyValueToTable(FMDatabase *db, NSString *key, NSString *tableName) {
        NSString *SQLScript = [NSString stringWithFormat:kSQLFormatterForDeleteValue, tableName, key];
        [db executeUpdate:SQLScript];
    }

    2. NSObject+YSKeyValueModel

    用于基于KeyValueStack的ORM,具体使用方法可参考代码中的例子。

    最主要的方法是,需要给每个属性添加KVO监听,当接口数据回来或者属性的值有变化时,就会触发KVO监听方法,从而更新数据库

    /**
     * 监听值的变化
     * 1、将需要变化的keyValue都存储到VSKeyValueStackCachedChangeObjects中
     * 2、每隔1秒,将VSKeyValueStackCachedChangeObjects中的keyValue拷贝到局部变量cachedChangeObjects中,并清空VSKeyValueStackCachedChangeObjects里面所有的数据
     * 3、将cachedChangeObjects循环存储到sqlite中
     */
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id<VSKeyValueModel>)object change:(NSDictionary<NSString *, id> *)change context:(void *)context {
        if (context != VSKeyValueStackObserverContext) {
            return;
        }
        
        void(^batchHandler)(void) = ^(void) {
            UIBackgroundTaskIdentifier __block taskToken = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), VSKeyValueStackCachedChangeQueue, ^{
                NSArray<__VSKeyValueChange *> *cachedChangeObjects = [VSKeyValueStackCachedChangeObjects copy];
                [JSKeyValueStackCachedChangeObjects removeAllObjects];
                [self handleTransaction:^(FMDatabase *db) {
                    for (__VSKeyValueChange *__change in cachedChangeObjects) {
                        @autoreleasepool {
                            // 如果值为空,则从数据表中移除;有值则更新
                            if (IsEmpty(__change.value)) {
                                VSKeyValueRemoveKeyValueToTable(db, __change.key, __change.tableName);
                            } else {
                                VSKeyValueSetKeyValueToTable(db, __change.key, __change.value, __change.tableName);
                            }
                        }
                    }
                    [[UIApplication sharedApplication] endBackgroundTask:taskToken];
                    taskToken = UIBackgroundTaskInvalid;
                }];
            });
        };
        
        id value = change[NSKeyValueChangeNewKey];
        dispatch_async(JSKeyValueStackCachedChangeQueue, ^{
            // batchHandler 限定了每隔1秒,处理一次VSKeyValueStackCachedChangeObjects所有的key,并清空
            if (!VSKeyValueStackCachedChangeObjects.count) {
                batchHandler();
            }
            // 将需要变化的keyValue都存储到VSKeyValueStackCachedChangeObjects中
            __JSKeyValueChange *__change = [__JSKeyValueChange new];
            __change.tableName = [object keyValueTableName];
            __change.key = keyPath;
            __change.value = value;
            [JSKeyValueStackCachedChangeObjects addObject:__change];
        });
    }

    使用方法

    1. 新建一个YSDemoPersistence,实现协议YSKeyValueModel

    2. 在YSDemoPersistence.h 中添加你需要存入数据库的字段名,当然也可以存入数组和字典

    3. 在YSDemoPersistence.m 中,给 -initWithKeyValueStack 初始化方法的所有属性加上KVO监听、dealloc 的时候移除监听、实现协议keyValueTableName返回数据库表名、如果有默认值,在

    setUpWithDefaultValue设置。

    @implementation YSUserPersistence
    
    - (instancetype)initWithKeyValueStack
    {
        self = [super initWithKeyValueStack];
        if (self) {
            [self addKeyPathObserverToKeyValueStack];
        }
        return self;
    }
    
    - (void)dealloc
    {
        [self removeKeyPathObserverFromKeyValueStack];
    }
    
    #pragma mark - <YSKeyValueModel>
    
    - (NSString *)keyValueTableName
    {
        return @"UserPersistence";
    }
    
    // 设置属性默认值
    - (void)setUpWithDefaultValue
    {
        _themeStyle = 0;
        _checkoutAgreeProtocolsMap = @{};
    }
    
    @end
  • 相关阅读:
    面向对象思想
    jQuery随笔
    总结关于linux操作
    转.linux上安装python
    sql server 基本语句
    linux 常见指令
    loadrunner 录制时不自动弹出网页
    Linux 安装MySQL
    linux关于安装
    loadrunner 性能测试
  • 原文地址:https://www.cnblogs.com/jys509/p/13257950.html
Copyright © 2020-2023  润新知