歡迎前往個人博客 駑馬點滴 和視頻空間 嗶哩嗶哩-《挨踢日志》
序言
Cocos2dx引擎為我們提供了 cc.UserDefault 類,用于本地數據存儲。在C++端的UserDefault類提供了6種數據類型的讀寫接口, 每種類型對應有讀接口和寫接口,其中的讀接口還有一個重載。
這需要記住相當多的API,是一個記憶的負擔。我們的問題是:
能不能有更簡潔的接口?
于是,基于此問題,在不改變UserDefault類的接口基礎上,在Lua腳本中增加一個新類 ud ,用于提供更簡潔的key-value數據結構的存儲解決方案。
關于UserDefault類的源碼分析,可請參見此文章
#Cocos2dx+Lua源碼#UserDefault類
大綱
本文將從以下要點進行說明
- 我們希望以怎樣的方式在本地讀寫數據;
- 解決方案的設計;
- 解決方案的實現;
- 測試API接口;
1. 我們希望以怎樣的方式在本地讀寫數據
我們希望是僅僅使用2個接口來實現我們的讀寫操作:
寫入操作
ud:setValueForKey(key, value)
我們要求
- key是string類型的非空字符串
- value可以是nil、boolean、number、string四種類型,當value是nil類型時,我們希望這是一個delete操作
- 我們不希望寫入過于復雜的數據類型,導致系統復雜度的增加。
讀取操作
ud:getValueForKey(key, default)
我們要求
- key是string類型的非空字符串
- 當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
測試通過!
總結
我們使用了一種更加優雅的訪問方式,雖然增加了本地數據存儲量,但由于它并非是一種頻繁讀寫的數據庫,且存儲量并不會很大,于是,追求訪問方式的優雅度,是我們最關注的,對于這樣的結果,我個人表示很滿意。