* 정의

 

__newindex는 테이블의 메타테이블 안에 정의할 수 있는 메타메소드이다. 테이블에서 테이블에 정의되어 있지 않은 새로운 키를 할당하려고 할 때 해당 테이블의 메타테이블에서 __newindex가 정의되어 있다면 새로운 키에 대한 처리 방식을 정의할 수 있다. 

 

 

* 사용 이유

 

새로운 키가 할당되려고 할 때 동작방식을 정의함으로 써 여러 가지 기능을 만들어낼 수 있다. 예를 들어보면 읽기 전용 테이블을 구현할 수도 있다.

 

 

* 동작 방식

 

- 테이블에 새로운 키를 처리하는 과정

 

만약 테이블에 존재하지 않는 키에 대해 값을 할당하려고 하면  루아 인터프리터는 __newindex 메타메소드를 찾아보고 만약 __newindex가 정의되어 있다면 값을 할당하는 대신에 __newindex 메타메소드를 호출한다. 

 

__newindex는 __index와 함께 사용하면 활용도가 더 높다. 읽기 전용 테이블, 기본 값을 가지고 있는 테이블, 상속 관련 기능들도 구현할 수 있다. 

 

(메타메소드를 호출하지 않고 우회할 수 있는 rawset이라는 원시 함수를 제공한다. 이는 내부 로직을 구현할 때 무한 루프 방지 등 유용하게 사용될 수 있다.)

 

 

- __newindex 함수 버전 예시

 

__index와 다르게 새로운 값들을 할당하는 과정이기 때문에 value 매개변수도 같이 들어온다. 예시에서는 메타테이블에 정보를 저장하도록 만들었다.

local table1 = {}
local table2 = setmetatable({value1 = 1}, table1)
table1.__newindex = function(table, key, value)
	
	local metatable = getmetatable(table)
	
	--if not metatable  then
	--	-- 원래 테이블에 추가하도록 할수도 있다.
	--	-- rawset(table, key, value) -- 무한 재귀를 막기 위해 메타메소드 우회
	--	return
	--end
	
	-- metatable도 __newindex가 정의되어 있다면 이에 따라 수행된다.
	-- 다 찾아보고 부모 클래스도 없으면 그냥 본인 테이블에 넣는 것으로 정했다.
    -- 제일 아래에 있는 부모 클래스에 넣을 수도 있다.
	local rv = metatable[key]
	if not rv then
		rawset(table, key, value)
	else
		metatable[key] = value
	end
end

table2.newValue = 5
print(table2.newValue, table1.newValue)

table2.value1 = 10
print(table2.value1, table1.value1)
nil 5
10 nil

 

 

- __newindex 단순화 버전 예시

 

간단히 함수가 아닌 테이블로 지정하면 해당 테이블로 할당하게 된다.

local table1 = {}
local table2 = setmetatable({value1 = 1}, table1)
table1.__newindex = table1

table2.newValue = 5
print(table2.newValue, table1.newValue)

table2.value1 = 10
print(table2.value1, table1.value1)
nil 5
10 nil

 

 

- __newindex 함수 버전과 단순화 버전 호출 원리(추측)

 

myTable[key] = value / myTable.key = value로 할당할 때 다음과 같은 함수가 호출되어 처리될 것이라고 추측하고 있다. 메타메소드를 우회하기 위해 rawget, rawset을 사용하였다.

local function SetKeyValue(table, key, value)
	
	-- 원래 있는 값이면 그냥 갱신해준다. 메타메소드를 우회하기 위해 rawget을 사용한다.
	local targetValue = rawget(table, key)
	if targetValue then
		rawset(table, key, value)
		return
	end
	
	-- 메타테이블에서 __newindex가 정의되어 있지 않다면 테이블에 그냥 넣어준다. 기본 동작이다.
	local metatable = getmetatable(table)
	if not metatable or not metatable.__newindex then 
		-- table[key] = value를 사용하면 다시 메타테이블의 __newindex 통하게 되기 때문에 무한 재귀가 발생한다.
		-- table[key] = value

		-- 따라서 메타메소드를 우회할 수 있는 rawset함수를 사용한다.
		rawset(table, key, value)
		return
	end

	-- 타입에 따라서 적절하게 할당해 준다.
	local typeString = type(metatable.__newindex)

	if typeString == "table" then

		metatable.__newindex[key] = value
		return
	end

	metatable.__newindex(table, key, value)
end

 

 

 

+ Recent posts