最近在重構自己寫的框架中的定時器模塊,需要把回調函數保存起來,大概如下:
function timer_mgr:save_timer( this,callback ) return { this = this,callback = callback} end -- 創建新定時器 -- @after:延遲N秒後啓動定時器 -- @repeated:N秒循環 -- @this:回調對象 -- @callbakck:回調函數 function timer_mgr:new_timer( after,repeated,this,callback ) local timer_id = self:next_id() self.timers[timer_id] = save_timer( this,callback ) end function timer_mgr:do_timer( timer_id ) local timer = self.timers[timer_id] -- 調用之前保存的定時器回調 return timer.callback( timer.this ) end
正常情況下,用table保存定時器的回調參數,畢竟lua中也沒有太多的數據結構可以選擇。不過,我們也可以這樣用closure來保存:
function timer_mgr:save_timer( this,callback ) return function() return callback( this ) end end -- 創建新定時器 -- @after:延遲N秒後啓動定時器 -- @repeated:N秒循環 -- @this:回調對象 -- @callbakck:回調函數 function timer_mgr:new_timer( after,repeated,this,callback ) local timer_id = self:next_id() self.timers[timer_id] = save_timer( this,callback ) end function timer_mgr:do_timer( timer_id ) local timer = self.timers[timer_id] -- 調用之前保存的定時器回調 return timer() end
這樣似乎看起來更優雅更方便一些,不過,頻繁創建closure也是很消耗內存和cpu的,需要和table對比一下:
function test_table_object( this,cb ) return { this = this,cb = cb } end function test_closure_object( this,cb ) return function() return cb( this ) end end local function cb() print("do nothing ...") end local max = 1000000 local table_mgr = {} local closure_mgr = {} function test_table() local beg_m = collectgarbage("count") local beg_tm = os.clock() for idx = 1,max do table_mgr[idx] = test_table_object( {},cb ) end local end_m = collectgarbage("count") local end_tm = os.clock() print("table test",end_m - beg_m,end_tm - beg_tm) end function test_closure() local beg_m = collectgarbage("count") local beg_tm = os.clock() for idx = 1,max do table_mgr[idx] = test_closure_object( {},cb ) end local end_m = collectgarbage("count") local end_tm = os.clock() print("closure test",end_m - beg_m,end_tm - beg_tm) end collectgarbage("stop") collectgarbage("collect") test_closure() test_table()
這段代碼,分別在win10 - I3 cpu和debian7(虛擬機) - A8 cpu下測試:
closure test 117946.5 0.489 table test 125000.0 0.446 closure test 180446.5 0.75 table test 171875.0 0.72
可以看到,table和closure的消耗其實都差不多。但closure在這個應用場景下就優雅得多,因爲可以很方便地傳更多的參數,比如:
function timer_mgr:save_timer( this,callback,... ) return function() return callback( this,... ) end end function timer_mgr:new_timer( after,repeated,this,callback,... ) local timer_id = self:next_id() self.timers[timer_id] = save_timer( this,callback,... ) end function timer_mgr:do_timer( timer_id ) local timer = self.timers[timer_id] -- 調用之前保存的定時器回調 return timer() end
而table要傳可變參則不太好處理了,需要另外創建一個table來pack參數,調用時再unpack,或者只用一個table把callback函數和參數存一起,調用時把函數移除再unpack。總而言之,在這種需要保存參數的回調,closure更合適。當然,如果你的回調函數不帶參數,那就是另外一碼事了。
查了下,之前也有人做了類似的測試,結果也差不多:http://lua-users.org/wiki/ObjectOrientationClosureApproach