#Cocos2dx手游开发#11重构Lua端UserDefault类

欢迎前往个人博客 驽马点滴 和视频空间 哔哩哔哩-《挨踢日志》

序言

Cocos2dx引擎为我们提供了 cc.UserDefault 类,用于本地数据存储。在C++端的UserDefault类提供了6种数据类型的读写接口, 每种类型对应有读接口和写接口,其中的读接口还有一个重载。

这需要记住相当多的API,是一个记忆的负担。我们的问题是:

能不能有更简洁的接口?

于是,基于此问题,在不改变UserDefault类的接口基础上,在Lua脚本中增加一个新类 ud ,用于提供更简洁的key-value数据结构的存储解决方案。

关于UserDefault类的源码分析,可请参见此文章
#Cocos2dx+Lua源码#UserDefault类


大纲

本文将从以下要点进行说明

  1. 我们希望以怎样的方式在本地读写数据;
  2. 解决方案的设计;
  3. 解决方案的实现;
  4. 测试API接口;

1. 我们希望以怎样的方式在本地读写数据

我们希望是仅仅使用2个接口来实现我们的读写操作:

写入操作

ud:setValueForKey(key, value)

我们要求

  • keystring类型的非空字符串
  • value可以是nilbooleannumberstring四种类型,当valuenil类型时,我们希望这是一个delete操作
  • 我们不希望写入过于复杂的数据类型,导致系统复杂度的增加。

读取操作

ud:getValueForKey(key, default)

我们要求

  • keystring类型的非空字符串
  • key有存储数据的时候,返回存储的数据
  • key没有存储数据的时候,返回default

2. 解决方案的设计

我们假设关键字

key = "TestKey"

我们为其分配两个新的关键字

type_key = "TestKey_TYPE_KEY"

value_key = "TestKey_VALUE_KEY"

写入操作

当我们调用

ud:setValueForKey("TestKey", "John")

xml表中,会出现这样的数据结构

<?xml version="1.0" encoding="UTF-8"?>
<userDefaultRoot>
    <TestKey_TYPE_KEY>S</TestKey_TYPE_KEY>
    <TestKey_VALUE_KEY>John</TestKey_VALUE_KEY>
</userDefaultRoot>

其中
TestKey_TYPE_KEY节点存储的S 标记为String类型
TestKey_VALUE_KEY节点存储的这个类型对应的值John


当我们调用

ud:setValueForKey("TestKey", false)

xml表中,会出现这样的数据结构

<?xml version="1.0" encoding="UTF-8"?>
<userDefaultRoot>
    <TestKey_TYPE_KEY>B</TestKey_TYPE_KEY>
    <TestKey_VALUE_KEY>false</TestKey_VALUE_KEY>
</userDefaultRoot>

其中
TestKey_TYPE_KEY节点存储的B 标记为Bool类型
TestKey_VALUE_KEY节点存储的这个类型对应的值false


当我们调用

ud:setValueForKey("TestKey", 99)

xml表中,会出现这样的数据结构

<?xml version="1.0" encoding="UTF-8"?>
<userDefaultRoot>
    <TestKey_TYPE_KEY>I</TestKey_TYPE_KEY>
    <TestKey_VALUE_KEY>99</TestKey_VALUE_KEY>
</userDefaultRoot>

其中
TestKey_TYPE_KEY节点存储的I 标记为Integer类型
TestKey_VALUE_KEY节点存储的这个类型对应的值99


当我们调用

ud:setValueForKey("TestKey", 88.88)

xml表中,会出现这样的数据结构

<?xml version="1.0" encoding="UTF-8"?>
<userDefaultRoot>
    <TestKey_TYPE_KEY>D</TestKey_TYPE_KEY>
    <TestKey_VALUE_KEY>88.88</TestKey_VALUE_KEY>
</userDefaultRoot>

其中
TestKey_TYPE_KEY节点存储的D 标记为Double类型
TestKey_VALUE_KEY节点存储的这个类型对应的值88.88


当我们调用

ud:setValueForKey("TestKey", nil)

xml表中
TestKey_TYPE_KEY节点和TestKey_VALUE_KEY节点都将被删除。


读取操作

当我们调用

ud:getValueForKey("TestKey", default)

若xml表中 TestKey_TYPE_KEY节点,那么它一定是合法的数据,一定能够取到TestKey_VALUE_KEY的正确值。
若xml表中 没有 TestKey_TYPE_KEY节点,则返回default


3. 解决方案的实现

-- 首先定义两个key值的转换函数
local function __typeKey(key)
    return string.format("%s_TYPE_KEY", key)
end 

local function __valueKey(key)
    return string.format("%s_VALUE_KEY", key)
end 

-- 再定义一个从判断其存储数据类型的接口
local function __cppType(value)
    local lua_value_type = type(value)
    local cpp_value_type 
    if lua_value_type == "string" then 
        cpp_value_type = "S"
    elseif lua_value_type == "boolean" then 
        cpp_value_type = "B"
    else
        assert(lua_value_type == "number")
        if value%1 == 0 then 
            cpp_value_type = "I"
        else
            cpp_value_type = "D"
        end 
    end 
    return cpp_value_type
end 

-- 定义类
local  XXUserDefault = class(" XXUserDefault")

-- 定义类的写入接口 
function XXUserDefault:setValueForKey(key, value)
    local key_type = type(key)
    local value_type = type(value)
    assert(key ~= "" and key_type == "string")
    if (value_type == "string" or value_type == "number" or value_type == "boolean") then
        local _type_key = __typeKey(key)
        local _value_key = __valueKey(key)
        local _cpp_value_type = __cppType(value)
        local _record_cpp_value_type = cc.UserDefault:getInstance():getStringForKey(_type_key)

        if _record_cpp_value_type == "" then 
            -- 空的, 说明从来没有赋值过
            cc.UserDefault:getInstance():setStringForKey(_type_key, _cpp_value_type)
            _record_cpp_value_type = _cpp_value_type
        end 
        if _record_cpp_value_type ~= _cpp_value_type then 
            -- 两个类型不一样
            cc.UserDefault:getInstance():setStringForKey(_type_key, _cpp_value_type)
        end 
        if _cpp_value_type == "S" then 
            cc.UserDefault:getInstance():setStringForKey(_value_key, value)
        elseif _cpp_value_type == "B" then 
            cc.UserDefault:getInstance():setBoolForKey(_value_key, value)
        elseif _cpp_value_type == "I" then
            cc.UserDefault:getInstance():setIntegerForKey(_value_key, value)
        elseif _cpp_value_type == "D" then
            cc.UserDefault:getInstance():setDoubleForKey(_value_key, value)
        end
    elseif (value_type == "nil") then 
        -- 清空工作
        cc.UserDefault:getInstance():deleteValueForKey(__typeKey(key))
        cc.UserDefault:getInstance():deleteValueForKey(__valueKey(key))
    end
end 

-- 定义类的读取接口
function XXUserDefault:getValueForKey(key, default)
    assert(type(key) == "string" and key ~= "", "[ERROR] key must be of type string and not empty!")
    local _cpp_value_type = cc.UserDefault:getInstance():getStringForKey(__typeKey(key))
    if _cpp_value_type == "S" then 
        return cc.UserDefault:getInstance():getStringForKey(__valueKey(key))
    elseif _cpp_value_type == "B" then 
        return cc.UserDefault:getInstance():getBoolForKey(__valueKey(key))
    elseif _cpp_value_type == "I" then 
        return cc.UserDefault:getInstance():getIntegerForKey(__valueKey(key))
    elseif _cpp_value_type == "D" then 
        return cc.UserDefault:getInstance():getDoubleForKey(__valueKey(key))
    else 
        assert(_cpp_value_type == "")
        return default 
    end 
end 
-- xx.convert.singleton是将类转化为单例的函数
return xx.convert.singleton(XXUserDefault)

4. 测试API接口

我们需要测试在TestKey对应各种取值情况下,附带各种默认参数时,返回值是否正确。

-- 定义打印帮助类和基础的数据类型
local convert_ret_to_print_string = function(ret)
    if type(ret) == "string" then 
        return ("'"..ret.."'")
    end 
    return tostring(ret)
end

local function printRet(ret)
    print("type of ret:", type(ret), " value of ret:", convert_ret_to_print_string(ret))
end 

local function printJudge(bSame)
    print("----> ", bSame == true and "OK" or "ERROR")
end 

local test_list = {
    nil,
    false,
    true,
    100,
    88.88,
    "",
    "default",
}

local function test(origin_value)
    for i = 1, 7 do 
        local default_value = test_list[i]
        local ret = xx.ud:getValueForKey('TestKey', default_value)
        if origin_value ~= nil then 
            if ret ~= origin_value then
                return false
            end
        else 
            if ret ~= default_value then 
                return false
            end 
        end
    end
    return true 
end

local function test_all()
    for j=1, 7 do 
        local _value = test_list[i]
        xx.ud:setValueForKey('TestKey', _value)
        if not test(_value) then 
            return false
        end 
    end 
    return true 
end

printJudge(test_all())

——>

---->   OK

测试通过!


总结

我们使用了一种更加优雅的访问方式,虽然增加了本地数据存储量,但由于它并非是一种频繁读写的数据库,且存储量并不会很大,于是,追求访问方式的优雅度,是我们最关注的,对于这样的结果,我个人表示很满意。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • Spark SQL, DataFrames and Datasets Guide Overview SQL Dat...
    Joyyx阅读 8,326评论 0 16
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,605评论 18 399
  • 虽然大部分时候都是不作不死,不过在很多时候程序的崩溃也是让人措不及手啊。自动化的备份和同步必须要有。 我没有什么很...
    左蓝阅读 3,506评论 4 9
  • 突然醒来,第一反应是:谁帮我关的灯?我明明记得我睡觉之前并没有关灯,这是我很确定的。拿出手手机,点亮屏幕,正好02...
    还未过河的卒子阅读 675评论 0 0