lua原生並無提供try-catch的語法來捕獲異常處理,可是提供了pcall/xpcall
等接口,可在保護模式下執行lua函數。ios
所以,能夠經過封裝這兩個接口,來實現try-catch塊的捕獲機制。git
咱們能夠先來看下,封裝後的try-catch使用方式:github
try { -- try 代碼塊 function () error("error message") end, -- catch 代碼塊 catch { -- 發生異常後,被執行 function (errors) print(errors) end } }
上面的代碼中,在try塊內部認爲引起了一個異常,而且拋出錯誤消息,在catch中進行了捕獲,而且將錯誤消息進行輸出顯示。api
這裏除了對pcall/xpcall
進行了封裝,用來捕獲異常信息,還利用了lua的函數調用語法特性,在只有一個參數傳遞的狀況下,lua能夠直接傳遞一個table類型,而且省略()
函數
其實try後面的整個{...}
都是一個table而已,做爲參數傳遞給了try函數,其具體實現以下:ui
function try(block) -- get the try function local try = block[1] assert(try) -- get catch and finally functions local funcs = block[2] if funcs and block[3] then table.join2(funcs, block[2]) end -- try to call it local ok, errors = pcall(try) if not ok then -- run the catch function if funcs and funcs.catch then funcs.catch(errors) end end -- run the finally function if funcs and funcs.finally then funcs.finally(ok, errors) end -- ok? if ok then return errors end end
能夠看到這裏用了pcall
來實際調用try塊裏面的函數,這樣就算函數內部出現異常,也不會中斷程序,pcall
會返回false表示運行失敗lua
而且返回實際的出錯信息,若是想要自定義格式化錯誤信息,能夠經過調用xpcall來替換pcall,這個接口與pcall相比,多了個錯誤處理函數:插件
local ok, errors = xpcall(try, debug.traceback)
你能夠直接傳入debug.traceback
,使用默認的traceback處理接口,也能夠自定義一個處理函數:debug
-- traceback function my_traceback(errors) -- make results local level = 2 while true do -- get debug info local info = debug.getinfo(level, "Sln") -- end? if not info or (info.name and info.name == "xpcall") then break end -- function? if info.what == "C" then results = results .. string.format(" [C]: in function '%s'\n", info.name) elseif info.name then results = results .. string.format(" [%s:%d]: in function '%s'\n", info.short_src, info.currentline, info.name) elseif info.what == "main" then results = results .. string.format(" [%s:%d]: in main chunk\n", info.short_src, info.currentline) break else results = results .. string.format(" [%s:%d]:\n", info.short_src, info.currentline) end -- next level = level + 1 end -- ok? return results end -- 調用自定義traceback函數 local ok, errors = xpcall(try, my_traceback)
回到try-catch上來,經過上面的實現,會發現裏面其實還有個finally的處理,這個的做用是對於try{}
代碼塊,無論是否執行成功,都會執行到finally塊中code
也就說,其實上面的實現,完整的支持語法是:try-catch-finally
模式,其中catch和finally都是可選的,根據本身的實際需求提供
例如:
try { -- try 代碼塊 function () error("error message") end, -- catch 代碼塊 catch { -- 發生異常後,被執行 function (errors) print(errors) end }, -- finally 代碼塊 finally { -- 最後都會執行到這裏 function (ok, errors) -- 若是try{}中存在異常,ok爲true,errors爲錯誤信息,不然爲false,errors爲try中的返回值 end } }
或者只有finally塊:
try { -- try 代碼塊 function () return "info" end, -- finally 代碼塊 finally { -- 因爲此try代碼沒發生異常,所以ok爲true,errors爲返回值: "info" function (ok, errors) end } }
處理能夠在finally中獲取try裏面的正常返回值,其實在僅有try的狀況下,也是能夠獲取返回值的:
-- 若是沒發生異常,result 爲返回值:"xxxx",不然爲nil local result = try { function () return "xxxx" end }
能夠看到,這個基於pcall的try-catch-finally
異常捕獲封裝,使用上仍是很是靈活的,並且其實現至關的簡單
這也充分說明了lua是一門已很是強大靈活,又很是精簡的語言。
在xmake的自定義腳本、插件開發中,也是徹底基於此異常捕獲機制
這樣使得擴展腳本的開發很是的精簡可讀,省去了繁瑣的if err ~= nil then
返回值判斷,在發生錯誤時,xmake會直接拋出異常進行中斷,而後高亮提示詳細的錯誤信息。
例如:
target("test") set_kind("binary") add_files("src/*.c") -- 在編譯完ios程序後,對目標程序進行ldid簽名 after_build(function (target)) os.run("ldid -S %s", target:targetfile()) end
只須要一行os.run
就好了,也不須要返回值判斷是否運行成功,由於運行失敗後,xmake會自動拋異常,中斷程序而且提示錯誤
若是你想在運行失敗後,不直接中斷xmake,繼續往下運行,能夠本身加個try快就好了:
target("test") set_kind("binary") add_files("src/*.c") after_build(function (target)) try { function () os.run("ldid -S %s", target:targetfile()) end } end
若是還想捕獲出錯信息,能夠再加個catch:
target("test") set_kind("binary") add_files("src/*.c") after_build(function (target)) try { function () os.run("ldid -S %s", target:targetfile()) end, catch { function (errors) print(errors) end } } end
不過通常狀況下,在xmake中寫自定義腳本,是不須要手動加try-catch的,直接調用各類api,出錯後讓xmake默認的處理程序接管,直接中斷就好了。。
最後附上try-catch-finally
實現的相關完整源碼:
我的主頁:TBOOX開源工程