안녕하세요.
유니티에서 게임을 개발할 때 많이 사용하는 함수 중 코루틴이 있습니다.
그리고 코루틴 함수를 사용할 때는 항상 IEnumerator를 사용해야한다고만 배웠죠.
그런데 C#문법을 공부하다 보면 코루틴을 사용하지 않은 코드에서 IEnumerator가 나온다 던지 아니면 IEnumerator와 비슷하게 생긴 IEnumerable 가 나온다던지 하면서 매우 헷갈리는 상황이 발생합니다.
이번 포스팅에서는IEnumerator와 IEnumerable에 대해서 제가 오랜시간 고민하며 나름대로 이해한 바를 설명해보고 그것을 적용해서 코루틴이 작동하는 방식에 대해서 좀 더 이해하는 시간을 가져보려고 합니다.
이제부터 설명하는 것은 제가 나름대로 이해하기 좋게 재구성해서 설명하는 것이니 정확하지 않은 용어의 사용이나 개념이 있을 수 있습니다.
만일 그렇다면 댓글로 가르쳐주시면 감사하겠습니다. 그리고 더 나은 정보들이 구글링을 하면 얼마든지 나오니 그 정보들을 활용하시는 것도 도움이 되실겁니다.
그럼 이제부터 IEnumerator, IEnumerable 에 대해서 함께 살펴보겠습니다.
IEnumerator와 IEnumerable는 인터페이스(Interface)다.
가장 먼저 이해해야하는 개념은 IEnumerator, IEnumerable 둘다 인터페이스라는 겁니다. 보통 인터페이스에는 앞에 대문자 'I '를 붙입니다.
유니티게임을 만들데 드래그 앤 드롭을 구현하기 위해서 반드시 사용해야 IDragHandler, IDropHandler 역시 I가 앞에 붙어 있으니 인터페이스입니다.
인터페이스는 클래스와 비슷한 성격을 가지고 있지만 보통은 구현하고 싶은 가장 중요한 기능을 추상적으로 구현해 놓은 일종의 약속입니다.
다시 말해 어떤 인터페이스가 있다고 할때 이 인터페이스를 가지고 있다면 반드시 '정해놓은 어떤 동작'을 해야한다 라고 정해 놓는 법칙 같은 것입니다. 하지만 그 동작의 이름만 명시할 뿐 그 동작이 어떤 식으로 작동하는지 구체적으로 구현해 놓지는 않습니다.
어려우니까 예를 들어볼까요?
예를 들어 아이템에 내구도를 감소하는 기능을 구현하려고 합니다. 하지만 모든 아이템에 내구도가 필요하지는 않습니다. 예를 들어 반지나 목걸이 같은 액세서리에는 내구도를 넣지 않아도 됩니다. 그럴때 아이템 클래스에 내구도를 구현한다면 일부만 사용하고 일부는 사용하지 않는 일이 발생할 수도 있습니다. 그럼 비효율이 발생할 수 있습니다.
게다가 내구도라는 기능을 아이템에만 적용하는 것이 아니라 건물에도 적용할 수 있습니다.
클래스만 사용한다면 아이템 클래스에 내구도 기능을 만들고 건물 클래스에 또 내구도 기능을 만들어야 할겁니다.
그렇게 하지 않기 위해서 어떤 기능만을 명시해서 따로 만들어서 사용할 수 있게 만든 것이 바로 인터페이스입니다.
내구도 라는 인터페이스를 따로 만들면 아이템 클래스를 상속받은 자식클래스에 붙여서 그 기능을 사용할 수 있고, 인터페이스를 붙이지 않고, 사용하지 않을 수도 있습니다.
게다가 이 내구도라는 인터페이스를 건물 클래스의 자식클래스에게 붙여서 사용할 수도 있고, 사용하지 않을 수도 있게 되는 겁니다.
이런식으로 클래스와 비슷하지만 클래스의 상속에 구해받지 않고 다른 클래스에 붙여서 그 기능을 구현하도록 하는 인터페이스를 이용할 수 있게 되는 겁니다.
이러한 예시를 한번 코드로 살펴보겠습니다.
public interface IDurability
{
int Durability // 내구도
void DecreaseDurability(int amount); // 내구도 감소 메서드
}
// Item클래스를 상속받으면서 동시에 IDurability인터페이스 상속
public class Weapon : Item, IDurability
{
Durability = 100;
public void DecreaseDurability(int amount); //IDurability인터페이스 상속시 반드시 구현 하는 함수
{
Durability -= amount;
Debug.Log($"아이템의 내구도가 {amount} 감소했습니다. 현재 내구도: {Durability}");
}
}
public class House : Building, IDurability
{
Durability = 1000;
// 내구도 감소 메서드
public void DecreaseDurability(int amount)
{
Durability -= amount;
Debug.Log($"건물의 내구도가 {amount} 감소했습니다. 현재 내구도: {Durability}");
}
}
IDurability라는 인터페이스를 만들어봤습니다.
그리고 Item클래스를 상속받는 Weapon이라는 클래스에서 IDurability를 함께 상속받아 사용합니다. Building클래스를 상속받은 House라는 클래스에서 IDurability를 상속받아 사용합니다. IDurability를 상속받으면 반드시Durabilitydecreases() 함수를 구현해야합니다. 따라서 Item클래스와 House클래스는 둘 다 Durabilitydecreases()를 포함하고 그 내용을 구현하고 있습니다.
(사실 인터페이스는 상속이라는 말을 사용하지는 않고, A클래스에 B인터페이스를 구현했다. 표현합니다. 따라서 위의 문단을 올바르게 표현하지만 Item크래스를 상속받는 Weapon이라는 클래스에 IDurability인터페이스를 구현했다고 하는 것이 올바른 표현입니다. 다만 코드를 사용하는 방식이 클래스의 상속과 비슷한 표기를 쓰기에 이해를 돕기 위해서 상속이라고 사용한 것입니다.)
그리고 이렇게 IDurability를 구현한 클래스들을 객체화하여 사용하는 방식은 다음과 같습니다.
public class GameManager : MonoBehaviour
{
void Start()
{
// 무기와 집 객체 생성
Weapon weapon = new Weapon();
House house = new House();
// 내구도 감소 테스트
weapon.DecreaseDurability(10);
house.DecreaseDurability(20);
}
}
하지만 여기서 인터페이스의 좀 더 놀라운 사용법이 있습니다.
public class GameManager : MonoBehaviour
{
void Start()
{
Weapon weapon = new Weapon(); //무기 객체 생성
House house = new House(); // 집 객체 생성
// 인터페이스 변수 생성
IDurability Durability;
// 아이템과 건물 객체 생성 후 인터페이스 변수에 할당
Durability = weapon;
Durability.DecreaseDurability(10);
Durability = house;
Durability.DecreaseDurability(20);
}
}
위에 있는 두개의 코드들은 사실 같은 기능을 하는 코드입니다. 차이는 위에 있는 방식은 각 Item클래스와 Building클래스 객체를 만들어 그안에 구현된 함수를 호출해서 사용했다는 점이고, 아래에서 사용한 방식은 각 Item객체와 Builfing 객체를 IDurability 타입의 itemOrBuilding이라는 변수에 할당하여 그 가능을 작동시키는 형태로 사용하고 있다는 점입니다.
이것은 일종의 업캐스팅이라고 할 수 있습니다. 다만 이렇게 인터페이스 타입의 변수에 객체를 넣어서 사용할때에는 오직 인터페이스안에 명시된 함수나 프로티만을 사용할 수 있습니다.
위 코드에서 Durability 변수에 weapon 객체를 할당 한다 해도 IDurability 가 데이터 타입이기 때문에 IDurability에 속한 함수인 DecreaseDurability()만 참조해 사용할 수 있습니다.
그러나 만일 Durability 변수에 weapon 객체를 할당 해서 weapon 클래스에 속한 AttackPow라는 프로퍼티를 사용하려고 해서 Durability.AttackPow 라고 해도 사용할 수 없게 되는 겁니다.
여기까지 이해하셔야만 IEnumerator, IEnumerable 를 이해할 수 있습니다.
IEnumerator와 IEnumerable 는 둘 다 인터페이스입니다. 둘 다 클래스 상속에 더해서 구현하는 것이 가능하고, 변수에 넣어서 프로퍼티를 호출 하는 방식으로 사용할 수 있는 겁니다. (그리고 하나의 클래스에 여러개의 인터페이스를 동시에 구현하는 것도 가능합니다.)
이번 포스팅에서는 IEnumerator와 IEnumerable를 이해하기 위해서 먼저 인터페이스에 대해서 알아봤습니다.
어디까지나 IEnumerator와 IEnumerable를 이해하기 위한 정도의 지식으로만 인터페이스를 설명한 것이니 인터페이스에 대해서 자세한 것을 알고 싶으시다면 다른 분들의 포스팅을 참고하는 것도 좋은 일인것 같습니다.
다음 시간에는 인터페이스에 대한 이해를 가지고 IEnumerator와 IEnumerable에 대해 좀 더 깊이 알아보겠습니다.