#Cocos2dx手遊開發#11重構Lua端UserDefault類

weixin_33869377發表於2017-01-11

序言

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

測試通過!


總結

我們使用了一種更加優雅的訪問方式,雖然增加了本地資料儲存量,但由於它並非是一種頻繁讀寫的資料庫,且儲存量並不會很大,於是,追求訪問方式的優雅度,是我們最關注的,對於這樣的結果,我個人表示很滿意。

相關文章