본문으로 건너뛰기

· 약 4분
karais89

조금 늦었지만 2020년에 읽었던 책들에 대해 간단한 감상 및 리스트 기록용도를 위해 포스트를 작성함.

2020년 총 20권의 책을 읽음. 1권은 어떤 걸 읽었는지 기록을 못 함.

대부분 도서 구독 서비스를 사용하여 읽은 책이다. 추리 소설을 좋아해서 추리 소설 위주로 읽었다.

2021년에는 조금 더 다양한 장르의 서적을 읽어야 겠다.

전반기

  • OKR 전설적인 벤처투자자: OKR에 관심을 가졌을 때 봤던 책. OKR 관련 서적중 추천.
  • 쏟아지는 일 완벽하게 해내는 법: GTD 방식에 대한 관심을 가졌을 때 봤던 책. OKR을 안 이후 해당 방식을 사용하지는 않지만. 책 내용 자체는 좋다.
  • 아주 작은 습관의 힘: 습관에 관한 이야기. 내용은 좋지만 별로 기억에 남지는 않았던 책.
  • OKR 실천편: 일본 사람이 쓴 책인데. 그다지 좋지는 않았다.
  • 호숫가 살인 사건: 히가시노 게이고 추리 소설. 재미있게 읽었다.
  • 살인자의 기억법: 김영하 장편소설. 워낙 유명한 책이기도 하고, 영화.로 까지 나온 책.
  • 언니 내가 남자를 죽였어: 소재가 좀 독특해서 기억에 남음. 동생이 연쇄 살인마인데 언니가 뒤치다꺼리를 함.
  • 화랑정 살인사건: 히가시노 게이고 추리 소설. 재미있게 읽었다.
  • 범인 없는 살인의 밤: 히가시노 게이고 추리 소설. 단편이라 부담없이 재미있게 읽었다.
  • 죽음을 보는 재능: 이거도 뭔가 재미있게 봤던 것 같은데 정확히 기억이 안남.

후반기

  • 이니미니: 약간 쏘우 느낌
  • 하쿠바산장 살인사건: 히가시노 게이고 추리 소설은 언제나 잼있다..
  • 살인자의 사랑법: 살인마 이야기. 소재가 좀 독특했던 것 같다.
  • 여름 어디선가 시체가: 한국에서 유명한 작가가 썻다는 소설이다.
  • 모든 것을 기억하는 남자: 이건 시리즈 물로 나왔는데. 조금 내용이 길어서 다음 시리즈에 손이 안가는 중.
  • [함께 자라기]({% post_url 2020-11-19-Growing-together %}): 유일하게 포스팅 했던 책. 애자일 관련 이야기.
  • 방과 후: 히가시노 게이고 추리 소설. 재미있게 읽었다.
  • 작열: 반전이 좀 신기했다.
  • 동급생: 히가시노 게이고 추리 소설. 재미있게 읽었다.

· 약 7분
karais89

book image

기간

7일

목적

우선 온라인에서 종이책으로 구입 함. 조금 할인을 많이 하여, 살만한 책이 없나 찾아보다가 객체지향 이란 제목에 끌려 바로 구입을 함. 개발을 하다보면 이게 맞는 개발 방법인가? 라는 생각이 계속해서 드는 시점이 왔는데.. 조금은 도움이 되지 않을까 싶어 구입을 하게 됨.

리뷰

책이 좀 여러 챕터로 구성되어 있다.

12챕터 까지 구성되어 있고, 기본적인 객체지향 개념. 클래스 설계 지침. 상속과 합성 관련된 내용. 프레임워크에 대한 내용등. 좀 얇고 넓게 전체적인 내용을 다룬것으로 보인다.

보통 책의 번역에 대해 얘기를 잘 안하는 편인데. 이 책의 역자는 좀 번역 자체에 신경을 많이 쓴걸로 보인다. 베타 리더의 의견도 많이 참고하고, 기존 소스들도 조금 더 보기 쉽게 수정한 부분들이 있고 번역 관련된 단어에 신경을 많이 쓴걸로 보여 책 자체를 읽기에 부담이 없었다.

5판 까지 나온 책이라. 오랫동안 계속 사랑 받아 온 것으로 보인다.

이 책의 저자는 객체지향 프로그래밍을 기술 수준이 아닌 개념 수준, 더 나아가서는 철학 수준에서부터 고찰하고 있다.

책에서 다루는 내용

  • 이 책은 객체지향적으로 생각하는 방법을 다루고 있다. 객체지향 개발을 한마디로 말한다면 생각하는 방식이다.
  • 독자가 객체지향적으로 생각하게 하는 데 초점을 맞추었다.

주요 내용

  • 객체지향 개념은 크게 캡슐화, 상속, 다형성, 합성으로 이루어진다고 보면 된다.
  • 객체는 속성과 행위라는 두 가지 성분으로 정의된다.
  • 객체지향 방식에는 데이터와 행위를 훌륭하고 완전한 패키지인 객체로 결합함으로써 데이터 제어 문제를 해결할 수 있다.
  • 일반적으로 객체는 다른 객체의 내부 데이터를 조작해서는 안된다.
  • 일반적으로 큰 객체를 만들어 많은 작업을 하기보다는 작은 객체를 만들어 특정 작업만 감당하게 하는게 좋다.
  • 메서드는 다른 객체에서 호출된 행위(메시지)를 구현하거나 클래스의 기본 내부 행위를 제공할 수 있다.
  • 메시지는 객체 간의 소통 메커니즘이다.
  • 인터페이스가 올바르게 설계된 경우에는 구현부가 변경되어도 사용자 코드를 변경하지 않아도 된다.
  • 행위를 직접 상속하기보다는 인터페이스를 상속하는 편이 더 일반적이다(전자는 상속이고 후자는 구현에 해당한다). 데이터/모델을 주로 상속받고, 행위를 주로 구현하는 경향이 있다.
  • 상속은 is-a 관계, 합성은 has-a 관계.

결론

챕터는 여러개이긴 한데 결국 같은 내용의 반복이라는 생각은 지울 수 없는 것 같긴 하다.

디자인 패턴및 SOLID는 간단히 맛만 보는 정도로 보여주고 있다.

계속 반복되는 코드(동물, 자동차 등)를 예로 들어 설명하는 부분이 오히려 좋았던 부분 인 것 같다.

결국 저자가 강조하는 부분은 상속 보다는 합성을 사용하자로 정리할 수 있다.

아래 간단히 책에 나오는 주요 예제 코드를 정리하였다. 실제 동작하지 않는 수도 코드라는 점은 참고 하자.

상속을 사용하지 않고, 합성을 사용한 예제

// 상속을 사용하지 않고, 합성을 사용한 예
class Mammal {
public void eat() { print("I am Eating"); };
}
class Walkable {
public void walk() { print("I am walking"); };
}
class Flyable {
public void fly() { print("I am flying"); };
}
class Dog {
Mammal dog = new Mammal();
Walkable walker = new Walkable();

// 위임
public void eat() { dog.eat(); }
public void walk() { walker.walk(); }
}
class Bat {
Mammal bat = new Mammal();
Flyable flyer = new Flyable();

// 위임
public void eat() { bat.eat(); }
public void fly() { flyer.fly(); }
}

void main() {
Dog fido = new Dog();
fido.dog.eat();
fido.walker.walk();

Bat brown = new bat();
brown.eat();
brown.fly();
}

의존성 주입 예제

class Mammal {
public void eat() { print("I am Eating"); };
}

class Walkable implements IWalkable {
public void walk() { print("I am walking"); };
}

interface IWalkable {
public void walk();
}

// mammal은 상속받고 IWalkable은 구현으로 처리
class Dog extends Mammal implements IWalkable {
IWalkable walker;

public Dog(IWalkable w) {
this.walker = w;
}

public void walk() {
walker.walk();
}
}

void main() {
Dog fido = new Dog(new Walkable()); // 의존성 주입
fido.walk();
fido.eat();
}

의존성 주입 예제 2

void main() {
Mammal cat = new Mammal(new CatNoise());
Mammal dog = new Mammal(new DogNoise());

cat.makeNoise();
dog.makeNoise();
}

class Mammal {
MakingNoise speaker;

public Mammal(MakingNoise sb) {
speaker = sb;
}

public string makeNoise() {
return speaker.makeNoise();
}
}

interface MakingNoise { public string makeNoise(); }
class CatNoise implements MakingNoise { public string makeNoise() { return "Meow"; } }
class DogNoise implements MakingNoise { public string makeNoise() { return "Bark"; } }
  • Cat, Dog 클래스를 만들지 않고 mammal 에서 해당 처리를 함.

평점 및 한줄평

기초 적인 내용 위주였지만, 다시 한번 객체 지향에 대해 생각해 볼 수 있었던 것 같다.

4/5

· 약 6분
karais89

book image

기간

7일

목적

클린씨리즈.. (클린코드, 클린코더, 클린 소프트웨어)를 만든 로버트 C 마틴이 작성한 책이라 별 고민없이 주문. 심지어 요즘에 거의 모든 IT 회사에서 적용하는 애자일에 관한 이야기를 다룬 책이다. 애자일에 직접적인 관계를 가진 (애자일 선언문을 만든) 당사자 이기 때문에 더 고민 없이 구매했던 것 같다.

리뷰

애자일을 다룬 책이므로 우선 애자일 선언문 부터 살펴 보자.

책의 서문에 나와 있는데로 이 책은 애자일의 근본적인 내용을 다룬다.

애자일은 무엇이고, 무엇이었고, 앞으로도 영원히 무엇일지를 다룬다.

애자일 선언문

우리는 소프트웨어를 개발하고, 또 다른 사람의 개발을
도와주면서 소프트웨어 개발의 더 나은 방법들을 찾아가고
있다. 이 작업을 통해 우리는 다음을 가치 있게 여기게 되었다:

- 공정과 도구보다 개인과 상호작용을
- 포괄적인 문서보다 작동하는 소프트웨어를
- 계약 협상보다 고객과의 협력을
- 계획을 따르기보다 변화에 대응하기를

가치 있게 여긴다. 이 말은, 왼쪽에 있는 것들도 가치가 있지만,
우리는 오른쪽에 있는 것들에 더 높은 가치를 둔다는 것이다.

마감 기한은 바뀌지 않지만, 요구 사항은 계속해서 바뀐다.

반복주기에 대한 설명

  • 스크럼이랑 조금 다른 개념인것으로 보인다.
  • 프로젝트 일정을 일정한 크기로 더 작게 나눈다.
  • 이것을 반복주기 혹은 스프린트라고 한다. 반복주기의 기간은 1~2주가 적당하다.
  • 첫번째 반복 주기는 반복주기 0이라고 부르고 스토리(짧은 기능 목록)를 만든다.
  • 반복주기 1은 스토리를 몇개나 완료할 수 있을지 추산한다.
  • 반복주기 1이 끝나면 반복 주기 하나 동안 스토리를 얼마나 완료할 수 있는지 측정 가능한 수치를 처음으로 얻는다.
  • 아래는 스크럼 프로세스 스크럼 프로세스

애자일의 핵심

  • 애자일은 프로젝트를 더 작은 반복 주기로 나누는 프로세스다
  • 각 반복 주기의 결과물을 측정하여 지속적으로 일정을 평가하는 데 사용한다.
  • 기능은 비즈니스 가치 순서대로 구현하므로 가장 중요한 것이 먼저 구현된다.
  • 품질은 가능한 높게 유지한다. 일정은 주로 범위(구현 해야되는 기능)를 조절하여 관리한다.

XP

  • 모든 애자일 프로세스 중에서 XP가 가장 잘 정의되어 있고, 가장 완전하며, 가장 덜 혼란스럽다.
  • 애자일이 무엇인지 이해하고 싶다면 XP를 공부하는 것이 최선의 방법
  • XP 삶의 순환 도표 xp 삶의 순환 도표

책을 읽으면서, 평소 회사에서 이미 진행하고 있는 애자일에 대해 다시 생각해보았다. 애자일 자체의 개념은 간단하지만, 실천하기는 힘들다. 이러한 부분에 대해 실천하기 위해서는 많은 노력이 필요할 것으로 보인다.

반복주기 관련해서는 현재 개인적으로 진행하고 있는 개인 OKR과도 흡사한 부분이 보인다. 3개월 단위의 목표를 세우고 매 주마다 목표에 대한 할일을 세우고 매 주마다 그것에 대한 피드백을 받는다.

애자일은 1주일의 스프린트를 정한다고 생각하면 매 주 스토리 보드를 만들고(할일). 매주 해당 스토리 보드에 포인트를 계산하고(피드백). 모든 스토리 보드가 완료될 때까지 프로젝트를 진행한다(목표).

애자일을 개인적인 프로젝트에도 적용 가능한지와 활용 가능한지에 대한 부분은 추가로 확인을 해봐야 될 것 같다. (지라, 트렐로 등 도구)

평점 및 한줄평

애자일에 대한 고민. 혹은 궁금한 분은 한번 쯤 읽어 볼 책. 믿고 보는 로버트 C 마틴

5/5

· 약 4분
karais89

book image

기간

7일

목적

OKR에 대해 전사에서 도입 계획이 있어, 좀 더 실용적인 OKR 도입 방법이 있나 찾아봄. 개인적인 OKR을 도입 고려 용도

리뷰

국내에서 처음 나온 OKR 관련 서적

애자일, 스프린트, OKR을 하나의 개념으로 보고 작성한 책

OKR은 보통 분기 단위(3개월)로 세우게 되는데 이 3개월을 12주의 스프린트로 생각하고, 기존의 MBO 방식(1년)과 달리 3개월의 목표 관리를 하여 기민하게(애자일) 빠르게 대처 가능한 목표 관리 방법 이라고 책은 소개 한다.

OKR은 사실 개념 자체는 어렵지 않지만, 실제 적용하기가 어려운 것 같다. 실제 인텔에서도 OKR을 도입하고 1년 반이 지나서 제대로 적응했다고 한다.

이 책을 만든 저자는 실제 여러 회사에 OKR 도입을 적용하면서 자신만의 OKR 적용 방법을 정리 하였다.

목차는 다음과 같다

  • 파트1. 왜 지금은 OKR인가
  • 파트2. OKR, 제대로 알고 시작합시다
  • 파트3. OKR 제목 작성이 실행의 반이다.
  • 파트4. 12번의 파도, 12주의 도전
  • 파트5. OKR, 문화로 정착시켜라
  • OKR 파워 부록

국내에서 처음 OKR 관련에 대해 나온 책이라 기대를 많이 한것에 비해 실속이 없는 느낌이다.

이미 나온 OKR 책에 대한 내용을 정리한 것으로 밖에 볼 수 없다.

아래 2개의 책을 보는걸 추천한다.

  • OKR 전설적인 벤처투자자가 구글에 전해준 성공 방식
  • 구글이 목표를 달성하는 방식 OKR

평점 및 한줄평

OKR 다른 서적을 보는 것을 추천

2/5

책 내용 중 참고 사항

OKR 체크 리스트

  1. 어느 레벨까지 OKR 제목을 세팅할지 결정하였는가?
    1. 전사 - 팀 - 개인 (처음 도입시 팀 단위까지 추천)
  2. OKR 파일럿 진행 여부를 결정하였는가?
    1. 부서별 OKR 세팅 추천
  3. OKR 제목 개수를 결정하였는가?
    1. 3개 이내 추천
  4. OKR 운영 주기를 결정하였는가?
    1. 통상 3개월. 더 짧아도 문제 없음
  5. KR 측정 방법을 결정하였는가?
    1. 구글의 방법 0.0~1.0
  6. OKR 미팅 운영 방법을 세팅하였는가?
    1. 주1회 실행 계획 점검. 월1회 진척사항점검.
  7. OKR 공유 시스템을 세팅하였는가?
    1. 구글시트, 노션, 트렐로등 투명하게 모든 OKR 공개

3-3-3의 원리

구글은 3-3-3의 원리를 강조한다.

OKR은 최대 3개월 단위의 달성 가능한 목표. 3개의 O, 갹 O 별로 3개의 KR을 세운다.

· 약 2분
karais89

book image

기간

2주일

목적

문득 도서 검색을 하다 눈에 띄는 책이라 구입

리뷰

책에 대한 내용은 전체적으로 너무 좋은 평가라 조금 조심스럽다.

책은 총 3부로 구성되어 있다.

1부. 자라기

2부. 함께

3부. 애자일

1부와 2부의 내용은 통계적인 내용 및 연구 결과에 대해 다룬것으로 보인다.

3부는 매우 짧은 내용이고, 애자일에 대해 다룬다. 내 생각엔 3부에 대한 설명을 위한 1부, 2부 였던것으로 보인다.

결국 자기 계발을 계속해서 진행하고, 코더가 아닌 소프트웨어 아키텍쳐를 목표로 하고, 의도적 수련을 하자. 그리고 함께 일을 할때 중요한 부분에 대한 설명.이고 결국 이것이 애자일로 가는 길이다.

애자일은 함께 성장하고, 짧은 주기 동안 지속적인 피드백을 받는 기법이다.

고객에게 날마다 가치를 전하자

이 책의 저자가 말한 애자일에 대한 설명이다.

평점 및 한줄평

3부의 경우 흥미 있게 읽을만한 주제 였지만, 나머지 1~2부는 글쎄..

3/5

· 약 12분
karais89

개요

유니티로 게임을 개발시 좀 더 구조화된 방법으로 개발하고 싶다.

유지보수가 쉽고, 유연하며, 남들이 쉽게 알아볼 수 있는 코드로 개발하는 것이 목표이다.

학부 과정에서는 MVC 패턴에 대해 배우고, 웹 프레임워크들이 MVC 패턴 구조로 많이 개발되어 있는 것도 알고 있다. MVC 패턴의 장점은 사용자 인터페이스로부터 비즈니스 로직을 분리하여 애플리케이션의 시각적 요소나 그 이면에서 실행되는 비즈니스 로직을 서로 영향 없이 쉽게 고칠 수 있는 애플리케이션을 만들 수 있다는 것이다.

MVC에서 파생되는 다양한 패턴들 MVP, MVVM은 결국 위와 같이 역할의 분리에 초점을 맞추고 있다고 보면 되고, 세부적인 내용이 조금 다르다고 이해해도 크게 무리가 없다고 생각한다.

유니티에서의 패턴

MVC, MVP, MVVM 중 유니티에 알맞는 패턴?

안드로이드에서는 보통 MVP나 MVVM 패턴을 사용함.

MVVM은 개인적으로는 너무 복잡해지는 경향이 있는 것 같아 아예 제외시킴. (뷰와 모델의 바인딩 처리도 유니티에서는 따로 처리하기 힘든 부분이 있고, 복잡해짐)

결국 MVC나 MVP 중에 선택을 해야 되는 문제이고, 유니티에서는 MVP가 더 적합하다는 판단이 들어 MVP를 선택하기로 함. (유니티에서는 일반적으로 UI를 표현하는 컴포넌트들이 화면을 직접 그리는 역할과 액션 처리를 함께 담당한다. 이러한 특징으로 MVC패턴을 사용하여 화면을 그리는 View와 액션을 처리하는 Controller을 완전히 분리하기 어렵게 된다)

MVP 패턴이란?

MVP 패턴에 대한 자료를 찾아보면, 정확한 정의는 없고, 대략적인 공통점들은 있다.

기본적인 기조는 같지만, 세부 적인 구현 내용은 개발자의 역량에 달린 것으로 보인다.

대략적으로 MVP 패턴은 아래 특징을 가지고 있다.

Model - View - Presneter

Model

  • Data와 관련된 모든 처리를 담당한다. 비즈니스 로직 처리.
    • 비즈니스 로직은 컴퓨터 프로그램에서 실세계의 규칙에 따라 데이터를 생성·표시·저장·변경하는 부분을 일컫는다.

View

  • 사용자에게 보여지는 UI 부분 (유니티에서는 모든 렌더링 되는 Object)

Presenter

  • View에서 요청한 정보(User actions)로 Model을 가공하여(Update model) 변경된 Model정보를 받아(Model changed) View에게 전달(Update UI)해주는 부분
  • 접착제 역할

관계도

MVP

특징

  • View와 Model은 서로를 알지 못한다. (어떤 방법으로든 접근할 수 없다)
  • Presenter은 View와 Model을 알고 있다.

여기서 알고 있다는 부분에 대한 해석으로 좀 헤매였던 부분이 있었는데, 알고 있다는 부분은 해당 인스턴스를 직접적으로 조작한다로 해석해도 무방할것으로 보인다.

→ 직접 조작하지만 않으면 알고 있지 않은 것 (이벤트 방식, SendMessage 등)

MV(R)P

UniRx 플러그인을 사용하면 유니티에서 MVP 패턴을 좀 더 쉽게 구현할 수 있다. (MVP 패턴에서 구현해야 되는 이벤트 기반 코드들을 더 쉽게 사용) 2020년 10월 9일 기준 v7.10(19년 7월 1일) 까지 나와 있는 상태이고, 원래는 Reactive Programming을 유니티에서 쉽게 사용 하기 위해 만들어진 플러그인이다.

UniRx를 사용하면 MVP (MVRP) 패턴을 구현할 수 있습니다.

MVP

MVVM 대신 MVP를 사용해야하는 이유?

유니티는 UI 바인딩을 제공하지 않으며, 바인딩 레이어를 만드는 것은 복잡하며, 오버헤드가 크다.

MVP 패턴을 사용하는 Presenter는 View의 구성요소를 알고 있으며 업데이트 할 수 있다. 실제 바인딩을 하지 않지만, View를 구독(Observable)하여 바인딩 하는 것과 유사하게 동작하게 할 수 있다. (복잡하지 않고, 오버 헤드도 적게 사용 가능)

이 패턴을 Reactive Presenter라고 한다.

// Presenter는 씬의 canvas 루트에 존재.
public class ReactivePresenter : MonoBehaviour
{
// Presenter는 View를 알고 있다(인스펙터를 통해 바인딩 한다)
public Button MyButton;
public Toggle MyToggle;

// Model의 변화는 ReactiveProperty를 통해 알 수 있다.
Enemy enemy = new Enemy(1000);

void Start()
{
// Rx는 View와 Model의 사용자 이벤트를 제공한다.
MyButton.OnClickAsObservable().Subscribe(_ => enemy.CurrentHp.Value -= 99);
MyToggle.OnValueChangedAsObservable().SubscribeToInteractable(MyButton);

// Model들은 Rx를 통해 Presenter에게 자신의 변화를 알리고, Presenter은 Viw를 업데이트 한다.
enemy.CurrentHp.SubscribeToText(MyText);
enemy.IsDead.Where(isDead => isDead == true)
.Subscribe(_ =>
{
MyToggle.interactable = MyButton.interactable = false;
});
}
}

// Model. 모든 프로퍼티는 값의 변경을 알려 준다. (ReactiveProperty)
public class Enemy
{
public ReactiveProperty<long> CurrentHp { get; private set; }

public ReactiveProperty<bool> IsDead { get; private set; }

public Enemy(int initialHp)
{
// 프로퍼티 정의
CurrentHp = new ReactiveProperty<long>(initialHp);
IsDead = CurrentHp.Select(x => x <= 0).ToReactiveProperty();
}
}

View는 하나의 Scene이며, Unity의 hierarchy이다. (하나의 개체 혹은 객체?)

View는 초기화시 Unity 엔진에 의해 Presenter와 연결된다.

XxxAsObservable 메서드를 사용하면 오버 헤드없이 이벤트 신호를 간단하게 생성 할 수 있습니다. SubscribeToText 및 SubscribeToInteractable은 간단한 바인딩 처럼 사용할 수 있게 하는 helper 클래스 입니다. 이것은 단순한 도구 일 수 있지만 매우 강력합니다. Unity 환경에서 자연스럽게 느껴지며 고성능과 깨끗한 아키텍처를 제공합니다.

MV(R)P

  • V-> RP-> M-> RP-> V가 완전히 Reactive(반응적인)한 방법으로 연결되었다.

  • GUI 프로그래밍은 ObservableTrigger의 이점도 제공합니다. ObservableTrigger는 Unity 이벤트를 Observable로 변환하므로이를 사용하여 MV(R)P 패턴을 구성 할 수 있습니다. 예를 들어 ObservableEventTrigger는 uGUI 이벤트를 Observable로 변환합니다.

var eventTrigger = this.gameObject.AddComponent<ObservableEventTrigger>();
eventTrigger.OnBeginDragAsObservable()
.SelectMany(_ => eventTrigger.OnDragAsObservable(), (start, current) => UniRx.Tuple.Create(start, current))
.TakeUntil(eventTrigger.OnEndDragAsObservable())
.RepeatUntilDestroy(this)
.Subscribe(x => Debug.Log(x));

설계 방향

  • MVP 패턴을 보면서 헷갈리거나 정립되지 않는 부분은 과감히 내 방식으로 정립하고, 구현 후 문제점 발생시 개선하는 방향으로 진행.
  • 완전히 디자인 패턴을 따르지는 않을 예정 (클린 코드가 되는 대신 생산성이 저하되는 부분은 과감히 생산성을 따르는 방향)
  • 하나의 Presenter에 여러개의 Model이 존재할 수 있다.
    • 각 모델의 경우 역할별로 클래스화 작업.
  • 하나의 Presenter에 여러개의 View가 존재할 수 있다.
  • Presenter는 각 팝업, 각 오브젝트 별로 존재한다. (컴포넌트 개념으로 생각)
    • 팝업의 아이템이 존재한다면 그 아이템도 각각의 Presenter가 존재. 구조가 복잡하지 않는다면 없어도 무방.
  • 간단한 예제에서는 항상 View-Presenter-Model은 1개씩 존재 했기 때문에, 각 Presetenr 1개에 2개이상의 view와 model이 존재해도 문제 없는지에 대한 고민을 함.
  • 그리고 Model의 구현시 거의 모든 역할을 Model에서 한다고 생각하면 될것으로 보임 (Presenter는 Model의 메서드를 호출하는 정도의 역할)
    • 보통의 예제에서는 간단한 메서드 구현 정도는 Presenter에서 해주는 부분도 있지만, Model이 전부 해주는게 더 일반적인 구조인것으로 보임.

결론

  • MV(R)P를 사용한 설계 진행
  • MV(R)P 아키텍처에 대해 다양한 자료 조사를 진행하였음. Github에서 UniRx 개발자가 예시로든 방법이 가장 깔끔하고, 생산성 있게 구조화 할 수 있는 방법으로 판단 되었음.
  • MV(R)P 시행 착오 (현재로서는 잘못됬다고 생각하는 방법)
    • 인터페이스를 사용하여 Presenter의 의존성을 제거하는 방법.
    • View 컴포넌트를 따로 뺀다던가, View 자체를 여러개 둔다 던가 하는 방법.
    • Model을 하나만 두어서 제어하는 방법.
    • MV(R)P는 UI에만 적용하는게 더 좋겠다라고 생각한 부분
  • View의 경우 복잡해지는 경우 커스텀 View를 만드는 방식으로 해결 가능.

참고

· 약 2분
karais89

book image

기간

한달

목적

go 언어에 대한 이해를 이해 구입

리뷰

Go 언어를 익히는 가장 확실하고 효율적인 방법

Go 관련 책이 잘 나오지 않는 상황에서 Go 관련 서적이 출간되어 바로 구입 하였다.

1~16장으로 구성되어 있으며, 기본적인 Go 언어의 특징 및 문법에 대해 설명하고 간단한 예제 프로그램을 만들고, 마지막엔 간단한 웹앱을 만든다.

거의 모든 프로그래밍 서적이 그렇지만 조금은 더 깊게 파고드는 부분은 없는 것 같아 좀 아쉽긴 하다.

처음 Go에 대해서 알아보고 어떤 언어 인지 파악하고 싶은 분들 이라면 한번 쯤은 읽어볼 만 할 수도 있겠다.

Go를 이미 접해본 분이라면 딱히 추천하고 싶지는 않다.

평점 및 한줄평

go를 처음 접하시는 분이라면 읽어볼 만한 책

3.5/5

· 약 5분
karais89
  • 1~2년 게임 전공 학과 졸업
  • 3~4년 (편입) 컴퓨터과학과 전공 졸업 예정
  • 7년차 현업 개발자

목적

  • 방송통신대학교 졸업 (졸업논문대체)

공부한 책

  • 수제비 (필기에서는 여러개의 책 및 강의를 들었지만, 실기는 이 책 한권으로 공부 진행)

책을 산 이유

  • 필기 시험에서 수제비 책의 적중률이 높았다고 하여 구입.
  • 책이 얇고, 정리가 잘 된 느낌이다.

솔직히 말해서 책 자체는 3사 (시나공, 이기적, 수제비)에서 나온 책 모두 ncs로 변경 된 이 후 갈피를 못잡는 느낌이다. 어떤 책을 고르던 별 다른 문제 없을 것 같다.

공부 방법

  • 하루에 1~2시간 정도 1달 공부
  • 수제비 책 1회 독 완료
  • 수제비 데일리 문제 풀이 및 오답 노트 정리
  • 수제비 족보 문제 풀이 및 오답 노트 정리
  • 수제비 퀴즐렛 두음 법칙 학습 및 정보처리기사(단답형) 퀴즐렛 학습

수제비 책 평가

  • 책 자체는 3사 책 중 가장 얇다. 내용 정리는 잘 되있고 깔끔하다.
  • 오탈자가 좀 많은 편이었다.
  • 까페 활성화가 잘 되어 있다. (데일리 문제, 족보, 예상 문제 등)

문제 적중률

수제비 실기 책만 봤을 때는 적중률은 40~50% 정도로 보면 될 것 같다. 하지만 수제비 필기 + 수제비 데일리 문제 + 수제비 족보 문제 까지 모두 합치면 적중률은 70~80 정도는 되는 것으로 보이고, 아마 전부 공부했을 때는 충분히 합격할 수 있을 것 같다. (아마 다른 책도 이것 저것 전부 합치면 이정도 적중률은 나올 것으로 보인다)

최종 점수

20문제 중 15문제 정답 = 75점 합격.

시험 리뷰

정보처리기사의 경우 20문제 약술형으로 출제되고, 60점 이상을 맞으면 합격한다 (과락은 없다).

1회 시험의 합격률은 5%이고, 2회 시험의 합격률은 20% 정도로 ncs로 변경된 이 후 확실히 합격률이 떨어진 것으로 보이고, 비 전공자가 취득하기가 확실히 힘들어 진 것으로 보인다.

문제를 봤을때 우선 3문제 정도는 우선 기본으로 틀리도록 문제를 구성한 것으로 보인다. (ncs로 변경 된 이후 범위 자체가 굉장히 넓어 졌다. 해당 범위에서 응시자가 보지 않을 법한 범위의 문제를 냈다고 생각하면 될 것 같다.)

2회 시험의 경우 채점 기준을 상당히 후하게 준 걸로 보인다.

요약

  • 합격률 5%, 20%로 이전 시험에 비해서 확실히 어려워졌다.
  • 시험 범위가 상당히 넓어졌다. (실기 시험이지만 필기에서도 몇 문제 출제 된다)
  • 전공자는 합격 못 할 정도의 난이도는 아니고, 비 전공자의 경우 열심히 하지 않으면 합격하기 좀 힘 들 것 같다.

참고 링크

· 약 5분
karais89
  • 1~2년 게임 전공 대학 졸업 3~4년(편입) 컴퓨터과학과 전공
  • 7년차 개발자

목적

  • 방송통신대학교 졸업 (졸업논문대체)

공부한 책 및 강의

  • 시나공, 이기적, 두목넷, 패스트캠퍼스

책 및 강의를 산 이유

  • 시나공: 그냥 예전 정보처리산업기사 땃을때 시나공으로 공부했었음 (10년전)
  • 두목넷: 정보처리기사를 꼭 따야되는 상황이었다.
  • 이기적: 두목넷 교재로 구입.
  • 패스트캠퍼스: 가격이 싸서 구입

공부 방법

  • 하루에 1시간 정도 공부 2달 (코로나로 시험 연기로 강제 2달 공부 진행)
  • 두목넷: 강의 전부 완료 (출퇴근 시간)
  • 시나공: 1.5 회독 (1~3과목 2회독, 4~5과목 1회독)
  • 이기적: 200 페이지까지 보다가 포기
  • 패스트캠퍼스: 강의 보다 포기

각 강의 및 책 평가

  • 시나공: 분철 서비스 받아서 2권으로 분리해서 봄. (책이 두꺼워서 확실히 분철된건 넘기기도 편하고 보기도 편했음). 각 섹션별로 나누어져 있어서 틈틈히 공부하기 편했다.
  • 이기적: 이 책은 다음 부터 살일은 없을 것 같다. (책 내용 오타 및 정리가 제대로 되지 않은 느낌)
  • 두목넷: 그냥 책 읽어주는 느낌임. 방송대 교수님들이 얼마나 대단한 분들인지 다시 한번 느낌. 오디오북 서비스 결제한다고 생각하고 출퇴근 시간에 들으면 괜찮을 수도 있을 것 같다. 사실 모든 기사 강의들이 이정도 수준일 것 같긴 하다.
  • 패스트캠퍼스: 오디오가 너무 이상해서 도저히 들을 수가 없었다. 중간 포기

최종 점수

  • 13/20, 13/20, 18/20, 17/20, 16/20 = 77점

시험리뷰

  • 원래 계획은 시험 전날과 당일 모의고사 문제를 풀고 가려고 했는데, 개인 사정으로 풀지 못함.
  • A형으로 시험 진행했고, 1과목 소프트웨어설계는 아예 처음 들어보는 것들이 많아서 상당히 당황스러웠고, 2과목도 상당히 많이 틀릴거라고 예상. 체감상 3과목 데이터베이스 부분이 가장 쉬었고, 4과목, 5과목도 암기 했으면 더 좋은 점수를 맞았을 것 같았다.

요약

  • 그냥 필기는 시나공으로 공부하면 충분할 것 같다.
  • 출퇴근 시간에 강의를 들어야 되는 상황이면, 동영상 강의도 나쁘지는 않을 것 같다.
  • 실기의 경우 현재 시나공 및 수제비 2개 중 하나를 선택할 예정.
  • 실기 강의의 경우 현재는 구입을 해야 될지 잘 모르겠다. (보험용으로 구입하는 느낌)
  • 시험 문제 적중률 기준으로는 수제비가 상당히 높았고 (1과목 기준) 그 이외에는 비슷비슷, 강의의 경우 기사퍼스트가 높았다고 한다.

참고 링크

· 약 21분
karais89

환경

  • macOS Catalina v10.15
  • Unity 2019.2.10f1
  • Github Desktop
  • Rider 2019.2
  • UniRx v7.1.0

원문 : https://qiita.com/toRisouP/items/c4b9c5701dd6c991b481

이 포스팅은 원문을 단순히 구글 번역을 하여 정리한 내용입니다. 일본어를 잘하시는 분은 원문을 보시는게 더 좋으실 것 같습니다.

[UniRx 입문 시리즈 목차는 이쪽]({% post_url 2019-11-17-UniRx-Getting-Started-List %})

0. 이전 복습

[지난번]({% post_url 2020-02-23-Getting-Started-4 %})에는 Update()를 어떻게 Observable로 변환하여 이용할 수 있는지 설명 하였습니다.

이번에는 한 걸음 더 나아가 코루틴과 함께 UniRx를 사용하는 방법을 소개하겠습니다.

1. 코루틴과 UniRx

Unity는 기본적으로 "코루틴"이라는 기능이 포함되어 있습니다. 이것은 본래는 이터레이터 처리를 구현하는데 사용하는 IEnumeratoryield 키워드를 활용하여 Unity의 주 스레드에서 비동기 처리 같은 것을 실현하는 기능 입니다.

(간혹 착각하고 있는 사람이 있지만, 코루틴에 실행되는 작업은 Unity의 메인 스레드에서 실행됩니다. Update()와 비슷한 처리로 실행 타이밍도 대부분 같습니다. 참고

그리고 지금까지 소개해온 UniRx는 코루틴과 병행하여 적용하면 한층 더 표현의 폭을 넓힐 수 있습니다. 왜냐하면 UniRx는 선언적으로 기술한 스트림은 if 분기할수 없거나, 스트림의 결과를 이용하여 그대로 절차적 처리에 연결하는 등의 처리가 어려웠습니다. 하지만 코루틴과 UniRx를 병행함으로써 이러한 문제를 해결 할 수 있게 됩니다. 또한 코루틴의 절차적 처리의 이점을 살리면서, UniRx의 유연한 예외 처리를 사용하는 것도 가능 합니다.

UniRx와 코루틴의 조합은 정말 편리해서 꼭 사용법을 기억하고 활용했으면 좋겠습니다.

용어 해설

  • 선언적: 부작용이 없는 함수를 메소드 체인으로 연결해 일련의 동작을 설명하는 방식
    • 장점: 필요한 처리를 차례로 연결해 쓰는 것만으로 구현되어 가독성이 높다.
    • 단점: 필요한 작업이 너무 복작하면 기존 함수만으로는 구현할 수 없는 경우가 있다.
  • 절차적: 상태 변수나 for와 if문을 사용하여 동작을 전부 설명하는 방식
    • 장점: 내 마음대로 사용할 수 있어, 어떤 처리도 가능하다.
    • 단점: 복잡한 기술이 증가하고 가독성이 낮아진다.

[역주]

  • 절차적 프로그래밍: "루틴", "서브루틴", "메소드", "함수" 등 "프로시저"를 이용한 프로그래밍 패러다임.
  • 이터레이터: 컬렉션에 대해 사용자 지정 반복을 수행, yield return 문을 사용하고 각 요소를 한 번에 하나씩 반환한다. 이터레이터는 현재 위치를 기억하고 다음 반복에서는 다음 요소를 반환한다.

2. 코루틴에서 IObservable로 변환

우선 코루틴에서 IObservable로 변환하는 방법을 소개 합니다.

코루틴을 스트림으로 변환하면 코루틴의 결과로 그대로 UniRx 오퍼레이터 체인에 연결하여 주는 것이 가능합니다.

또한 복잡한 행동을 하는 스트림을 생성 할 때는 코루틴에서 구현하고 스트림으로 변환하는 방법을 취하는 것이 UniRx 오퍼레이터 체인만으로 스트림을 구축하는 것보다 간단하게 처리되는 경우도 있습니다.

Ⅰ. 코루찐 종료 시간을 스트림으로 기다린다.

사용하는 것: Observable.FromCoroutine

결과: IObservable<Unit>

첫 번째 인수: Func<IEnumerator> coroutine 코루틴 본체

두 번째 인수: bool publishEveryYield = false yield 한 시간에 OnNext를 발행 하는가?

(false는 OnCompleted 직전에 1번만 발급 default = false)

Observable.FromCoroutine을 이용하면 코루틴 종료 시간을 스트림으로 처리 할 수 있습니다.

코루틴 종료 타이밍의 통지를 필요로 할 때 사용할 수 있습니다.

using System.Collections;
using UniRx;
using UnityEngine;

public class ConvertFromCoroutine : MonoBehaviour
{
private void Start() =>
Observable.FromCoroutine(NantokaCoroutine, publishEveryYield: false)
.Subscribe(
_ => Debug.Log("OnNext"),
() => Debug.Log("OnCompleted")
).AddTo(gameObject);

private IEnumerator NantokaCoroutine()
{
Debug.Log("Coroutine started");

// 어떤 처리를 하고 기다리고 있는 예
yield return new WaitForSeconds(3);

Debug.Log("Coroutine finished.");
}
}

실행 결과

Coroutine started.
Coroutine finished.
OnNext
OnComplted

Observable.FromCoroutineSubscribe 될 때마다 새롭게 코루틴을 생성하고 시작하게 된다는 것에 주의하십시오. 코루틴 하나만 시작 스트림을 공유하고 이용하고 싶다면 [스트림의 Hot 변환]({% post_url 2019-10-13-UniRx-When-is-a-Hot-Conversion %})이 필요 합니다.

덧붙여 Observable.FromCoroutine 에서 시작한 코루틴은 Subscribe를 Dispose하면 자동으로 중지 됩니다.

만약 코루틴에서 자신의 스트림이 Dispose 된 것을 감지하려면 코루틴의 인수 CancellationToken을 전달하여 Dispose를 감지 할 수 있습니다. 이때 CancellationToken은 Observable.FromCoroutine 에서 얻을 수 있습니다.

Observable.FromCoroutine(token => NantokaCoroutine(token))  // token이 CancellationToken

Ⅱ. 코루틴의 yield return 결과를 추출

사용하는 것: Observable.FromCoroutineValue<T>

결과: IObservable<T>

첫 번째 인수: Func<IEnumerator> coroutine 코루틴 본체

두 번째 인수: bool nullAsNextUpdate = true null일 때 OnNext를 발행하지 않는다. default = true

Observable.FromCoroutineValue<T>를 이용하면 코루틴의 yield return으로 반환 된 값을 꺼내 스트림으로 사용할 수 있습니다.

yield return는 호출 될 때마다 1 프레임 정지하는 성질이 있기 때문에 이를 이용하여 한 프레임 씩 값을 발행 할 수 있습니다.

using System.Collections;
using System.Collections.Generic;
using UniRx;
using UnityEngine;

public class ConvertFromCoroutine2 : MonoBehaviour
{
// 이동 좌표 목록
[SerializeField] private List<Vector2> moveList;

private void Start() =>
Observable.FromCoroutineValue<Vector2>(MovePositionCoroutine)
.Subscribe(x => Debug.Log(x));

/// <summary>
/// 목록에서 값을 1 프레임씩 꺼내는 코루틴
/// </summary>
/// <returns></returns>
private IEnumerator MovePositionCoroutine()
{
foreach (var v in moveList)
{
yield return v;
}

// ↑의 foreach 문은 통째로
// "return moveList.GetEnumerator ();"
// 로 고쳐 써도 된다.
}
}

실행 결과

Ⅲ. 코루틴 내부에서 OnNext를 직접 발행하기

사용하는 것: Observable.FromCoroutine<T>

결과: IObservable<T>

첫 번째 인수: Func<IObserver<T>, IEnumerator> coroutine IObserver<T>를 인수로 취하는 코루틴

Observable.FromCoroutine<T>IObserver<T>를 제공하는 구현도 존재 합니다. 이 IObserver<T>를 코루틴에 전달하여 코루틴의 특정 타이밍에 OnNext를 발행 할 수 있습니다.

이 기능을 이용하면 내부 구현은 절차적 비동기 처리로 쓰고 외부에서는 스트림으로 취급하는 것 처럼 코루틴과 UniRx 모두의 장점을 취할 수 있습니다.

매우 편리하고 범용적인 기능이므로 꼭 기억하세요.

또한 OnCompleted는 자동으로 발급되지 않기 때문에, 코루틴 종료 시점에서 스스로 OnCompleted를 발금해 줄 필요가 있습니다.

using System;
using System.Collections;
using UniRx;
using UnityEngine;

public class ConvertFromCoroutine3 : MonoBehaviour
{
// 일시 정지 플래그, true인 경우 타이머 중지
public bool IsPaused;

private void Start() =>
Observable.FromCoroutine<long>(observer => CountCoroutine(observer))
.Subscribe(x => Debug.Log(x))
.AddTo(gameObject);

/// <summary>
/// 일시 정지 플래그가 지나지 않은 상태의 시간(초)를 계산하여 알려준다.
/// </summary>
/// <param name="observer">알림 IObserver</param>
/// <returns></returns>
IEnumerator CountCoroutine(IObserver<long> observer)
{
long current = 0;
float deltaTime = 0;

// Dispose하면 코루틴이 멈추니까 while(true) 해도 문제없이 움직인다.
// 기분 나쁘다면 CancellationToken을 받아 이용하면 된다.
while (true)
{
if (!IsPaused)
{
// 일시 플래그가 지나지 않은 사이 시간을 측정한다.
deltaTime += Time.deltaTime;
if (deltaTime >= 1.0f)
{
// 차이가 1초를 초과한 경우 정수 부분을 꺼내 집계 통지한다.
var integerPart = (int) Mathf.Floor(deltaTime);
current += integerPart;
deltaTime -= integerPart;

// 시간(초) 통지
observer.OnNext(current);
}
}
yield return null;
}
}
}

실행 결과

(일시 정지 플래그가 true 동안 카운트를 정지 해, false가 되면 중단된 이전의 카운트에서 측정을 재개)

"상태에 의존한 처리" 나 "중간에 처리가 크게 분기되는 처리" 같은 것은 UniRx 오퍼레이터 체인만으로 구현하기 어렵고, 경우에 따라서는 구현 불가능한 경우도 있습니다. 그런 경우 이렇게 코루틴에서 내부 구현을 실시하고 스트림으로 변환 해버리는 방법을 취하는 것을 권장합니다.

Ⅳ.보다 저렴한 비용으로 가벼운 코루틴을 실행

사용하는 것: Observable.FromMicroCoroutine / Observable.FromMicroCoroutine<T>

반환 값: IObservable<Unit> / IObservable<T>

첫번째 인수: Func<IEnumerator> coroutine / Func<IObserver<T>, IEnumerator> coroutine

인수: FrameCountType frameCountType = frameCountType.Update Update, FixedUpdate, EndOfFrame 어느 타이밍을 이용할것인지

Observable.FromMicroCoroutine 그리고 Observable.FromMicroCoroutine<T>는 각각 이전에 설명했습니다. Observable.FromCoroutine / Observable.FromCoroutine<T>와 거의 같은 동작을 합니다.

그러나 내부 구현은 크게 다르며, 코루틴에서 yield return null 만 사용할 수 있는 제약이 있는 대신 Unity 표준 코루틴에 비해 매우 고속으로 동작하는 구조로 되어 있습니다. 이 구조의 코루틴을 "마이크로코루틴"이라 부르며 UniRx의 독자 구현으로 되어 있습니다.

yield return null만 구현되어 있는 코루틴을 만들고 시작하려면 Unity 표준의 StartCoroutine 보다 이 Observable.FromMicroCoroutine를 사용하면 보다 더 저렴한 비용으로 코루틴을 사용할 수 있습니다.

private void Start() =>
Observable.FromMicroCoroutine<long>(observer => CountCoroutine(observer))
.Subscribe(x => Debug.Log(x))
.AddTo(gameObject);

코루틴에서 IObservable로 변환하는 방법 정리

  • 코루틴에서 IObservable로 변환 할 수 있다.
  • Observable.FromCoroutine 등으로 실행한 코루틴은 MainThreadDispatcher에 관리가 위임되므로 수명 관리에 주의 할 필요가 있다 (AddTo 기억)
  • Observable.FromCoroutine 등은 Subscribe 된 시점에서 새롭게 코루틴을 생성하고 시작되어 버리기 때문에, 1개의 코루틴을 공유하고 여러 번 Subscribe 할 때는 Hot 변환이 필요하다.

3. IObservable에서 코루틴으로 변환

두 번째 방법으로 UniRx 스트림을 코루틴으로 변환하는 방법을 소개 하겠습니다.

이 스트림을 코루틴으로 변환하는 기술을 이용하여 코루틴에서 스트림의 실행 결과를 기다리고 그대로 계속 진행하는 등의 기술 방법이 가능합니다.

"C# Task와 await에 해당한다"라고 간략하게 생각 해두면 좋을 것 같습니다.

스트림을 코루틴으로 변환 (Unity 5.3)

사용하는 것: ToYieldInstruction() (IObservable<T>에 대한 확장 메서드)

결과: ObservableYieldInstruction<T>

인수: CancellationToken cancel 처리를 중단한 경우는 인수에 전달 한다 (생략가능)

인수: bool throwOnError = false OnError가 발생했을 때 예외 내용을 throw 할 것인가?

ToYieldInstruction 를 이용하여 스트림을 코루틴으로 실행 한 다음 스트림을 기다리게 할 수 있습니다.

using System;
using System.Collections;
using UniRx;
using UniRx.Triggers;
using UnityEngine;

public class ConvertToCoroutine : MonoBehaviour
{
private void Start()
{
StartCoroutine(WaitCoroutine());
}

private IEnumerator WaitCoroutine()
{
// Subscribe 대신 ToYieldInstruction()을 이용하여
// 코루틴으로 스트림을 처리 할 수 있게 된다

// 1초 기다린다
Debug.Log("Wait for 1 second.");
yield return Observable.Timer(TimeSpan.FromSeconds(1)).ToYieldInstruction();

// ToYieldInstruction()은 OnCompleted가 발행되어 코루틴 종료
// 따라서 OnCompleted가 반드시 발행되는 스트림에서만 사용할 수 있다.
// 무한으로 이어지는 스트림의 경우 First나 FirstOrDefault를 사용하면 좋겠다.
Debug.Log("Press any key");

// 아무 키나 누를 때까지 기다린다
yield return this.UpdateAsObservable()
.FirstOrDefault(_ => Input.anyKeyDown)
.ToYieldInstruction();

// FirstOrDefault 조건을 충족하면 OnNext와 OnCompleted를 모두 발행한다.
Debug.Log("Pressed");
}
}

ToYieldInstructionOnCompleted 메시지를 받으면 yield return을 종료합니다. 따라서 OnCompleted를 발행하지 않느 끝없는 스트림을 ToYieldInstruction 해 버리면 영원히 끝없는 상태가 되어 버리기 때문에 주의가 필요합니다.

또한 스트림에서 발행된 OnNext 메시지를 이용하는 경우 ToYieldInstruction 가 반환하는 ObservableYieldInstruction<T>로 변수에 저장한 결과를 가져올 수 있습니다.

using System;
using System.Collections;
using UniRx;
using UniRx.Triggers;
using UnityEngine;

public class ConvertToCoroutine2 : MonoBehaviour
{
private void Start() => StartCoroutine(DetectCoroutine());

private IEnumerator DetectCoroutine()
{
Debug.Log("Coroutine start!");

// 코루틴이 시작되고 나서
// 3초 이내에 먼저 자신을 건드린 객체를 얻는다.
var o = this.OnCollisionEnterAsObservable()
.FirstOrDefault()
.Select(x => x.gameObject)
.Timeout(TimeSpan.FromSeconds(3))
.ToYieldInstruction(throwOnError: false);

// Timeout은 지정 시간 이내에 스트림이 완료되지 않는 경우
// OnError를 발행하는 오퍼레이터

// 결과를 기다린다.
yield return o;

if (o.HasError || !o.HasResult)
{
// 아무것도 치지 않았다.
Debug.Log("hit object is nothing.");
}
else
{
// 뭔가에 맞았다.
var hitObject = o.Result;
Debug.Log(hitObject.name);
}
}
}

스트림을 코루틴으로 변환 (Unity 5.2 이전)

Unity 5.2 이전에는 ToYieldInstruction을 사용할 수 없습니다. 대신 StartAscoroutine를 사용하여 동일한 작업을 수행 할 수 있습니다.

IEnumerator DetectCoroutine()
{
GameObject result = null;
bool isTimeout = false;

// 코루틴이 시작되고 나서
// 3초 이내에 먼저 자신에 닿은 오브젝트를 취득하는
yield return this.OnCollisionEnterAsObservable()
.FirstOrDefault()
.Select(x => x.gameObject)
.Timeout(TimeSpan.FromSeconds(3))
.StartAsCoroutine(x => result = x, error => isTimeout = true);

// StartAsCoroutine는 첫 번째 인수의 함수 결과가 전달되기 때문에
// 그래서 사전에 정의 된 변수에 결과를 대입하여 결과를 얻을 수
// 두 번째 인수는 OnError
if (isTimeout || result == null)
{
Debug.Log("hit object is nothing.");
}
else
{
var hitObject = result;
Debug.Log(hitObject.name);
}
}

IObservable에서 코루틴으로 변환하는 방법 정리

  • ToYieldInstruction 또는 StartAsCoroutine를 이용하여 스트림을 코루틴으로 변환 할 수 있다.
  • 응용하면 "코루틴 도중 특정 이벤트의 발행을 기다린다" 같은 처리가 가능하게 된다.

응용 예

코루틴을 직렬로 실행하고 기다린다

CoroutineA 실행 → CoroutineA의 종료를 받고 CoroutineB 시작

private void Start() =>
Observable.FromCoroutine(CoroutineA)
.SelectMany(CoroutineB) // SelectMany에서 합성
.Subscribe(_ => Debug.Log("All Coroutine Finished"));

private IEnumerator CoroutineA()
{
Debug.Log("CoroutineA start");
yield return new WaitForSeconds(3);
Debug.Log("CoroutineA finished");
}

private IEnumerator CoroutineB()
{
Debug.Log("CoroutineB start");
yield return new WaitForSeconds(3);
Debug.Log("CoroutineB finished");
}

실행 결과

CoroutineA start
CoroutineA finished
CoroutineB start
CoroutineB finished
All coroutine finished

여러 코루틴을 동시에 시작하고 결과를 기다린다

CoroutineA와 CoroutineB를 동시에 시작하고 모두 종료하고 정리해 처리

private void Start() =>
Observable.WhenAll(
Observable.FromCoroutine<string>(o => CoroutineA(o))
, Observable.FromCoroutine<string>(o => CoroutineB(o))
).Subscribe(xs =>
{
foreach (var x in xs)
{
Debug.Log("result: " + x);
}
});

private IEnumerator CoroutineA(IObserver<string> observer)
{
Debug.Log("CoroutineA start");
yield return new WaitForSeconds(3);
observer.OnNext("CoroutineA done!");
observer.OnCompleted();
}

private IEnumerator CoroutineB(IObserver<string> observer)
{
Debug.Log("CoroutineB start");
yield return new WaitForSeconds(1);
observer.OnNext("CoroutineB done!");
observer.OnCompleted();
}

실행 결과

CoroutineB start
CoroutineA start
result : CoroutineA done!
result : CoroutineB done!

무거운 처리를 다른 스레드에 하면서 결과를 코루틴에서 얻는다.

코루틴에서 일부 처리를 다른 스레드에서 실행하고 결과가 돌아 오면 처리를 코루틴에서 재개하도록 구현.

Observable.Start() 을 이용한다.

private void Start() => StartCoroutine(GetEnemyDataFromServerCoroutine());

/// <summary>
/// 서버에서 적의 정보를 당겨오는 코루틴
/// </summary>
/// <returns></returns>
private IEnumerator GetEnemyDataFromServerCoroutine()
{
// 서버에서 xml 다운로드
var www = new WWW ("http://api.hogehoge.com/resouces/enemey.xml");

yield return www;

if (!string.IsNullOrEmpty(www.error))
{
Debug.Log(www.error);
}

var xmlText = www.text;

// ParseXml 함수를 다른 스레드에서 실행
// Observable.Start는 인수의 함수를 ThreadPool에서 실행하는 기능
var o = Observable.Start(() => ParseXml(xmlText)).ToYieldInstruction();

// 파스 종료 대기
yield return o;

if (o.HasError)
{
// 파스 실패
Debug.LogError(o.Error);
yield break;
}

// 파스 결과
var result = o.Result;
Debug.Log(result);

// 이 후 처리 계속
}

private Dictionary<string, EnemyParameter> ParseXml(string xml)
{
// 여기에 xml 파싱을 Dictinonary에 넣는다는 가정
return new Dictionary<string, EnemyParameter>();
}

/// <summary>
/// 적 매개 변수
/// </summary>
private struct EnemyParameter
{
public string Name { get; set; }
public string Health { get; set; }
public string Power { get; set; }
}

(↑ 구현 보다 ↓ 작성 한 것이 간단하게 작성되지만, 어디 까지나 코루틴을 사용하면 어떻게 될까 설명 이므로 용서 바랍니다.)

ObservableWWW.Get("http://api.hogehoge.com/resouces/enemey.xml") 
.SelectMany(x => Observable.Start(() => ParseXml(x)))
.ObserveOnMainThread() // 처리를 메인 스레드 취소
.Subscribe(result =>
{
// 여기에 퍼스 결과를 사용한 처리
}
ex => Debug.LogError(ex)
);

정리

  • 스트림과 코루틴은 상호 변환 할 수 있다.
  • 코루틴을 이용하여 오퍼레이터 체인으로는 만들 수 없는 스트림을 구축하는 것이 가능하게 된다.
  • UniRx의 독자 코루틴을 사용하는 것으로, Unity 표준 코루틴보다 사용이나 성능이 향상 될 수 있다.
  • 스트림을 코루틴으로 변환하여 async/await 같은 기능이 가능하다 (어디 까지나 비슷한 기능)