Skip to content

Latest commit

 

History

History
307 lines (227 loc) · 13.2 KB

File metadata and controls

307 lines (227 loc) · 13.2 KB

Dart 훑어보기 下

지난 시간에 이어 Dart 언어에 대해 좀 더 알아봅시다.

함수

함수 function 는 기본적으로 다음과 같은 형태를 띕니다.

반환자료형 함수식별자(매개변수자료형 매개변수) {
    //함수본문
    return 반환값;
}

이 때, 반환값이 없을 경우 반환자료형과 return 반환값; 부분을 생략할 수 있습니다.
이런 함수를 function 과는 별개로 procedure 라고 따로 정의하는 언어도 있다는 건 여담
간단한 함수의 경우 함수본문이 return 반환값; 만으로 이루어져 있기도 하죠.
만약 함수 본문이 return 문을 포함하여 한 문장으로 이루어져 있다면 그 함수는 다음과 같이 작성할 수도 있습니다.

반환자료형 함수식별자(매개변수자료형 매개변수) => 한문장;

이런 표현 방식을 람다식이라고 합니다.

매개변수의 경우 없을 수도 있고 여러 개일 수도 있는데, 없으면 괄호를 비워두고 여러 개일 경우 , 를 기준으로 열거하면 됩니다.

반환자료형 함수식별자(매개변수자료형1 매개변수1, 매개변수자료형2 매개변수2) 함수에 대하여 이 함수를 실행시키는 것을 함수를 호출 call 한다고 하며, 함수를 호출할 땐 반환값을저장할자료 = 함수식별자(인자1, 인자2); 형태로 사용합니다.
매개변수 parameter 는 함수 밖에서 값이 전달되어 함수 내에서만 사용되는 변수이며 인자 argument 는 매개변수에 대입되는 구체적인 값이죠.
기본적으로 인자는 전달된 순서대로 매개변수에 대입되지만, 함수식별자(매개변수1: 인자1, 매개변수2: 인자2); 와 같이 직접적으로 명시해줄 수도 있습니다.
그리고 함수를 정의할 때 반환자료형 함수식별자(매개변수자료형1 매개변수1=기본값, 매개변수자료형2 매개변수2) 와 같이 매개변수의 기본값을 설정할 수 있는데, 이 경우 함수식별자(매개변수2: 인자2); 와 같이 함수 호출 시 해당 매개변수에 대한 인자를 생략하면 기본값을 사용할 수 있습니다.

Dart 언어의 특징에서 Dart는 모든 자료를 객체로 취급한다고 했습니다.
따라서 함수도 객체입니다. 어떤 자료에 대입하는 게 가능하죠.

때로는 함수를 인자로 받는 함수도 있는데, 만약 어떤 함수가 단 한 번 다른 함수의 인자로만 전달되고 다른 곳에서는 쓰이지 않는다면 익명 함수로 생성할 수 있습니다.
익명 함수는 따로 식별자를 갖지 않으며 (인자) {함수본문} 형태로 작성되죠.

클래스와 메서드

재차 말하지만 Dart는 모든 자료를 객체로 취급한다고 했습니다.
기본 자료형의 자료와 함수 등을 제외한 일반적인 객체는 클래스 class 를 통해 만들 수 있는데, 클래스는 객체를 만들기 위한 설계도라고 볼 수 있습니다.

클래스에는 자료와 기능이 포함되는데, 클래스의 자료는 필드 field 라고 부르고, 클래스의 기능은 함수 형태로 구현되며, 이를 메서드 method 라고 합니다.
클래스식별자 객체식별자; 형태로 선언되어 객체식별자.메서드식별자(); 형태로 메서드를 호출하죠.

그리고 클래스 식별자과 같은 이름을 가진 특별한 메서드가 있는데 이를 생성자 constructor 라고 하며, 클래스의 객체를 생성할 때 사용됩니다.
Java와 같은 언어는 클래스식별자 객체식별자 = new 생성자(); 와 같이 객체를 생성하지만 Dart는 new 키워드를 생략하고 클래스식별자 객체식별자 = 생성자(); 로 생성할 수 있습니다.

class 클래스식별자 {
    // 필드
    자료형 필드식별자;
    자료형 필드식별자;

    // 생성자
    클래스식별자(매개변수) {
        // 초기화코드;
    }

    // 메서드
    반환자료형 메서드식별자(매개변수) {
        // 메서드본문코드;
    }
}

라이브러리

때로는 처음부터 모든 걸 새로 구현하는 게 아니라, 다른 사람이 만들어놓은 것을 가져다 사용할 수도 있습니다.
그렇게 가져다 쓸 수 있는 것들을 라이브러리 library 라고 하며, Dart의 표준 라이브러리는 Dart API 문서 에서 확인하실 수 있습니다.
라이브러리는 import 키워드로 불러올 수 있는데, 이 키워드는 라이브러리뿐만 아니라 프로젝트 내 다른 Dart 파일을 불러오는 데에도 사용됩니다.

예를 들어, 여러 가지 수학적인 함수를 가진 dart:math 라이브러리를 불러온다면,

import 'dart:math';

혹은, 이를 math 라는 이름으로 불러오겠다 하여

import 'dart:math' as math;

와 같이 작성할 수 있습니다.
그러고나면 코드 상에서 다음과 같이 dart:math 라이브러리의 클래스, 가령 Random 같은 녀석을 사용할 수 있죠.

var rand = math.Random();
int number = rand.nextInt(100);

어떤 라이브러리가 존재하는지는 다 기억하고 있을 필요는 없고, 필요한 클래스가 있을 때 그것이 어느 라이브러리에 정의되어 있는지 찾아보면 됩니다.

비동기 처리

기본적으로 프로그램은 동기 synchronous 식으로 진행됩니다.
이는 한 작업이 시작되면 그 작업이 끝날 때까지 기다렸다가 다음 작업을 수행하는 것입니다.
그런데 얼마나 걸릴지 알 수 없는 작업의 경우 마냥 기다리기 보다는, 그것이 수행되는 동안 다른 작업을 병행하는 것이 나을 수도 있습니다.
서버로부터 응답을 기다리는 동안 클라이언트에서 할 수 있는 다른 작업을 하는 식이죠.
이와 같은 작업 방식을 비동기 asynchronous 처리라고 합니다.
데이터베이스나 파일에 정보를 읽고 쓰는 경우에도 비동기 처리를 하는 편이 유리합니다.

Dart 언어에서 비동기 처리를 하기 위해서는 asyncawait 키워드가 사용됩니다.

먼저 비동기 처리가 포함된 함수의 식별자와 본문 코드블럭 사이에 async 키워드를 붙입니다.

자료형 함수식별자 async {
    // 본문
}

그리고 비동기 처리를 할 작업 앞에 await 키워드를 붙입니다.

자료형 함수식별자 async {
    // 본문
    await 비동기함수();
    // 본문
}

함수의 반환값이 비동기함수로부터 비롯되었을 경우 비동기 처리 결과물을 받기 위해 함수의 반환 자료형을 Future 로 설정합니다.

Future<자료형> 함수식별자 async {
    // 본문
    await 비동기함수();
    // 본문
}

비동기함수의 반환값을 함수 밖으로 꺼내지 않고 함수 내에서 처리할 경우 then 함수를 사용합니다.

자료형 함수식별자 async {
    // 본문
    await 비동기함수().then((value) {
        // 비동기함수의 반환값을 value로 사용
    });
    // 본문
}

비동기로 받아오는 값이 여러 개면 Future 가 아닌 Stream 을 사용해야 하지만 일단 이 정도로 알고 넘어갑시다.

실습

자, 이번에도 배웠으니 실습을 해야겠죠?

먼저 함수에 대한 예제입니다.
가장 단순한, return 으로만 이루어진 함수입니다.
본인 디렉토리에 chapter03 하위 디렉토리를 생성하고 add.dart 라는 이름의 파일을 생성해 다음과 같이 작성합니다.

main() {
  int result = add(7, 8);

  print('result: $result');
}

int add(int x, int y) {
  return x + y;
}
result: 15

함수에서 어떤 일을 수행하기 위해서는 return 위에 그 작업을 작성하며, 함수의 결과값을 받아올 필요 없다면 return 을 생략할 수 있다는 것, 그리고 함수도 객체로서 다른 함수의 인자로 전달될 수 있다는 걸 알아보기 위한 해괴한(?) 예제를 살펴보겠습니다.

funcExample.dart 파일을 생성해 다음과 같이 작성합니다.

main() {
  callFunction(printAndReturn);
}

callFunction(var function) {
  print('result: ${function("TEST")}');
}

String printAndReturn(String name) {
  print('Hello, $name!');
  return name;
}
Hello, TEST!
result: TEST

main 함수에서 callFunction 함수를 호출하였으며, callFunction 함수에서는 인자로 전달된 printAndReturn 함수에 "TEST" 를 인자로 전달하여 호출하였습니다.
그리고 printAndReturn 의 반환값은 callFunction 함수로 전달되었으며 callFunction 함수는 main 함수에 그 무엇도 전달하지 않죠.

다음으로는 라이브러리에 대한 예제입니다.
앞서 언급된 dart:mathRandom 을 사용해보도록 하겠습니다.
무작위 정수 6개를 출력하는 로또 생성을 할 것인데, 일반적으로 이런 상황에서는 동일한 자료형을 묶어 놓은 배열을 사용하지만, Dart 언어는 배열을 지원하지 않습니다.
List, Map, Set 등의 컬렉션을 사용할 수 있죠.
HashSet 등의 특별한 컬렉션을 사용하기 위해서는 dart:collection 라이브러리가 필요하지만 기본 컬렉션들은 그냥 사용할 수 있습니다.

  • List : 순서가 있는 자료를 담는 컬렉션
  • Map : 순서가 없고 key-value 쌍으로 이루어져 탐색이 빠른 컬렉션 (존재하지 않는 키는 null 반환)
  • Set : 순서가 없고 중복을 허용하지 않는 컬렉션

사용할 땐 컬렉션<자료형> 컬렉션식별자 = 컬렉션(); 으로 생성하여 사용합니다.

여기선 순서가 없고 중복을 허용하지 않는 Set 을 사용하도록 하겠습니다.
Set 에는 add 를 통해 값을 추가할 수 있으며, 기존에 있는 값을 add 하려고 할 경우 이를 무시합니다.
따라서 기존 컬렉션에 현재의 랜덤값이 들어있는지 여부를 확인하지 않아도 중복된 값에 대한 걱정이 없죠.

lottery.dart 라는 이름의 파일을 생성하고 다음과 같이 작성합니다.

import 'dart:math' as math;

main() {
  var rand = math.Random();
  Set<int> lottery = Set();

  while (lottery.length < 6) {
    lottery.add(rand.nextInt(45));
  }

  print('result: $lottery');
}

직접적으로 명시해주려면 math.Random rand 로 선언해도 되지만 타입 추론이 가능한 상황이므로 굳이 길게 적지 않고 var rand 로 선언했습니다.
그리고 while 반복문을 통해 로또 번호 범위 내의 정수가 6개 모일 때까지 반복하도록 하였죠.
중복된 랜덤값이 나왔는지와 관계 없이 중복되지 않은 6개가 나올 때까지 반복하고 반복문을 벗어납니다.

실행해보면 실행할 때마다 다른 값이 들어 있는 것을 확인할 수 있습니다.

result: {41, 10, 44, 37, 11, 2}
result: {42, 44, 23, 40, 8, 10}
result: {8, 21, 30, 42, 19, 43}

마지막으로 비동기 처리에 대한 예제입니다.

비동기 작업은 오래 걸리는 작업에 대해 기다리지 않고 병렬적으로 수행하고자 하는 것이니 오래 걸리는 작업을 가정하고 진행하도록 하겠습니다.
asyncExample.dart 파일을 생성하고 다음과 같이 작성합니다.

main() {
  getResult();

  print('end process');
}

Future getResult() async {
  print('before call');

  var result = await veryLongTask();
  print('result: $result');
}

String veryLongTask() {
  print('do very long task');

  return 'the result from the very long task';
}

여기서 veryLongTask 함수는 오래 걸리는 작업을 가정합니다.
이를 실행해보면 다음과 같은 결과를 얻을 수 있습니다.

before call
do very long task
end process
result: the result from the very long task

main 함수에서 getResult 를 호출하고 "before call" 이 출력됩니다.
그리고 getResult 에서 veryLongTask 를 호출하고 "do very long task" 가 출력되죠.
await 이 붙은 veryLongTask 가 실행 및 반환되고 나면 이제 async 가 붙은 getResult 함수와 이를 호출한 main 함수는 병렬적으로 실행해도 되는 상태가 됩니다.
그 과정에서 main"end process"getResult"result: the result from the very long task" 보다 먼저 출력된 것이죠.

과제

이번 과제도 지난 과제와 동일합니다.
실습 코드와 그 출력 화면 스크린샷을 제출하면 됩니다.
어렵지 않죠?
복사-붙여넣기를 하면 학습효과가 떨어지므로 반드시! 직접 적어보시길...ㅎㅎ

과제를 성공적으로 완료하였다고 판단되면 merge 해드리며, 그렇지 않을 경우 comment로 피드백 드리겠습니다.
다음 시간부터는 본격적인! 플러터 학습을 시작하도록 하겠습니다.