Cocos2dx+Lua手游开发cocos2dx

#Cocos2dx手游开发#11重构Lua端UserDefaul

2017-01-11  本文已影响240人  KomalZheng

序言

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)

我们要求

读取操作

ud:getValueForKey(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

测试通过!


总结

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

上一篇 下一篇

猜你喜欢

热点阅读