external modules 를 require 할 때 정확히 어떤 일이 일어나는지 헛갈릴 때가 있습니다. 더군다나 스토리보드 scene들이나 여러분의 custom module들과 같이 동작을 하는데 에상하지 못했던 결과가 나오면 더 혼동되죠.
오늘은 여러분과 같이 Lua 에서 module들은 정확히 어떻게 작동을 하는지에 대해 몇가지 실습을 해 보고 설명도 덧 붙이겠습니다. 이 글을 읽으시면 모듈 내의 코드가 실행되거나 built-in require() function 을 call 했을 때 어떤 코드가 run 하지 않는지 등에 대해 이해 하실 수 있을 겁니다.
Including External Modules
가장 간단한 형식은 external module이 테이블 같은 어떤 것을 return 하는 Lua 파일인 경우입니다. 외부 모듈이 하나의 function 인 셈이죠.
아래 정말 간단한 모듈이 있습니다. example1.lua 인데요. 터미널에 한 문장을 print 하고 빈 테이블을 return 하는 것입니다.
example1.lua
local t = {}
print( "example1.lua has been loaded." )
return t
이제 main.lua를 볼까요? 여기서 우선 example1.lua를 require 합니다.
main.lua
local ex1 = require "example1"
ex1.testvar = "Hello world"
예상한대로 ex1은 빈 테이블입니다. example1.lua에서 빈 테이블을 return 했으니까요. 그리고 “example1.lua has been
loaded.” 라는 문장이 터미널에 쓰여질 겁니다. 그 다음에 위에 보면 ex1 테이블에 어떤 프로퍼티를 할당했습니다.
이 부분을 주목해 보세요. 여기 약간의 트릭이 있습니다. 다른 모듈에서 이 example1.lua를 require 해 봅시다. scene1.lua라는 모듈에서요. 이미 main.lua에서 example1.lua를 require 했었죠. 이제 어떤 일이 일어나는지 볼까요?
scene1.lua (previous main.lua still applies)
-- ...
local examp1 = require "example1"
print( examp1.testvar )
-- ...
scene1.lua에서 example1.lua를 require 하면 “example1.lua has been loaded” 이 터미널에 뿌려지지 않습니다. 그리고 ex1.testvar 값을 print 하면 터미널에 “Hello World” 가 뿌려집니다. (이 의미는 뭐냐하면 testvar 프로퍼티가 존재한다는 것이죠.
여기서 뭘 알 수 있나요?
첫번쨰로 “example1.lua has been loaded” 라는 문장에 터미널에 뿌려지지 않은 이유는 그 모듈이 이미 main.lua에서 로드 됐기 때문이죠. 모듈이 한번 로드 되면 그 안의 코드가 다 실행되게 되죠. 그리고 모듈의 return 값은 package.loaded라고 하는 글로벌 테이블에 저장됩니다.
여러분이 require를 call 하면 첫번째로 이전에 이 모듈이 이미 로드 된 것인지 아닌지 확인하기 위해 package.loaded table 을 살펴 봅니다. 만약에 있으면 외부 모듈을 새로 require 하는 대신 package.loaded 에 저장된 return value를 return 합니다. 이 return 된 value는 copy가 아니라 reference 입니다. 그러니까 여러분의 모듈이 테이블을 return 한다면 미래에 같은 모듈에 대해 require를 call 하면 같은 테이블을 get 하게 될 겁니다.
만약에 package.loaded 안에 그 모듈이 없으면 그 모듈이 로드 될 겁니다. (그 모듈안의 코드들이 실행 되겠죠.) 그리고 그 모듈의 return value가 package.loaded table안에 저장될 겁니다. (미래에 사용하기 위해 저장하는 거겠죠.) 그래서 두번째에서는
“example1.lua has been loaded.” 이 터미널에 뿌려지지 않은 겁니다. require() 가 call 됐을 떄 global package.loaded table 안에 이미 그 모듈이 존재하면 그 모듈을 re-load 하지 않으니까요.
모듈 안에 있는 코드를 두번 실행하도록 하는 방법에는 두가지가 있습니다.
- 코드를 모듈 내에서 함수 안에 넣고 그 함수를 부르는 경우
- package.loaded table 에서 그 모듈을 remove 한 다음에 다시 require 하는 경우
첫번째 시나리오에 대한 예제입니다.
example2.lua
local t = {}
print( "example2.lua has been loaded." )
t.hello = function()
print( 'Hello world.' )
end
return t
main.lua
local ex2 = require "example2"
-- Terminal: example 2 has been loaded.
ex2.hello()
-- Terminal: Hello world.
scene1.lua
local examp2 = require "example2"
examp2.hello()
-- Terminal: Hello world.
예제에서 보듯이 example2.lua를 두개의 다른 모듈(main.lua and scene1.lua)에서 require 했습니다. 첫번째 print statement는 오직 한번 보여집니다. 첫번쨰로 그 모듈이 require 됐을 때죠.
두번째 print statement는 hello() 함수 안에 있습니다. 이것은 hello() 함수가 call 될 때마다 실행되겠죠. 그 코드가 한번 이상 실행되기를 원한다면 함수를 table에 attach 해서 모듈 마지막 부분에서 return 되게 하세요. (또는 단지 require 만 했을 떄 print 되지 않도록 할 때도 그렇게 하면 되겠죠.)
Removing from package.loaded
여러분이 모듈을 require 하려면 (예를 들어 example2.lua) require "example2" 라고 해야 되죠.
그러면 example2.lua의 return value 가 package.loaded table 에 저장 될 겁니다.
package.loaded["example2"]
만약 example2.lua에 있는 코드가 다시 execute 될 필요가 있다면 package.loaded table 에서 remove 한 다음에 다시 call 하시면 됩니다.
아래 그 예제가 있습니다.
main.lua
require "example2"
-- Terminal: example2.lua has been loaded.
package.loaded["example2"] = nil
require "example2"
-- Terminal: example2.lua has been loaded.
require "example2"
-- Terminal:
This is Universal
여러분이 만든 모듈이든 아니면 다운로드 된 모듈이든 혹은 built-in 모듈이든 이 모듈들을 다룰 때는 위에 설명드린 대로 외부 모듈들이 작동합니다.
스토리 보드의 scene들을 다룰 때 그 scene이 로드될 떄 스토리보드 리스너 함수안에 있는 코드들이 어떤 것은 실행되고 어떤 것은 실행되지 않는지에 대한것도 궁금하실 겁니다.
거기에 대한 것은 다음 기회에 다루도록 하겠습니다.