#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可以是nil、boolean、number、string四種類型,當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

測試通過!


總結

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,868評論 18 139
  • Spark SQL, DataFrames and Datasets Guide Overview SQL Dat...
    Joyyx閱讀 8,344評論 0 16
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,754評論 18 399
  • 雖然大部分時候都是不作不死,不過在很多時候程序的崩潰也是讓人措不及手啊。自動化的備份和同步必須要有。 我沒有什么很...
    左藍閱讀 3,537評論 4 9
  • 突然醒來,第一反應是:誰幫我關的燈?我明明記得我睡覺之前并沒有關燈,這是我很確定的。拿出手手機,點亮屏幕,正好02...
    還未過河的卒子閱讀 696評論 0 0