App/Flutter

[Dart] Dart Language tour - Dart부터 시작하는 Flutter 앱 개발 2

메바동 2021. 7. 27. 23:08
728x90

변수

var name = value;

Dart에서 변수는 참조(reference)를 저장한다.

var name = 'mebadong';

name 변수의 타입은 String으로 추론된다.
객체가 단일 타입으로 제한되지 않을 경우 Object 타입이나 dynamic을 지정한다.

기본값

초기화 되지 않은 nullable 타입의 값은 null로 초기화 된다. (null safety를 사용하지 않을 경우, 모든 변수는 nullable 타입이다.)

int? lineCount;  // null safety 모드에서 ? 키워드를 사용하면 nullable 타입이 된다.

/*
assert()는 개발 모드의 debug 중에만 작동하며, 배포(production) 코드에서는 무시된다.
assert(조건)이 'fase'일 경우 예외를 발생시킨다.
*/
assert(lineCount == null);

null safety를 활성화하면 nullable이 아닌 변수를 사용하기 전에 반드시 초기화해야 한다.
지역 변수를 선언할 때 초기화할 필요는 없지만, 사용하기 전에 값을 할당해야 한다. 예를 들어, 다음의 코드는 print()에 전달될 때 intCount가 null이 아님을 감지할 수 있으므로 유효한 코드이다.

int lineCount;

if (weLikeToCount) {
  lineCount = countLines();
} else {
  lineCount = 0;
}

print(lineCount);  // 호출되기 전 lineCount는 반드시 null이 아니게 된다.

late 변수

Dart 2.12에서 두 가지 사용사례가 있는 late 제어자가 추가되었습니다.

  • 선언 후 초기화되는 nullable이 아닌 변수를 선언할 때
  • 초기화 지연 변수

Dart의 제어 흐름 분석은 nullable이 아닌 변수가 사용되기 전에 null이 아닌 값으로 설정되는 경우를 감지할 수 있지만, 때로는 분석이 실패합니다. (일반적으로 최상위 변수인스턴스 변수)

만약 변수가 사용되기 전에 값이 할당되었다고 확신하지만 Dart에서 이를 허용하지 않는 경우 late 제어자를 사용하여 오류를 수정할 수 있습니다.

late String description;

void main() {
  description = '값이 할당 됨';
  print(description);
}

late 제어자를 표시한 변수를 선언시 초기화 하여도 변수를 처음 사용할 때 이니셜라이저(initializer)가 실행됩니다. 이 지연된 초기화는 다음과 같은 경우에 유용합니다.

  • 변수가 필요하지 않을 수 있으며, 초기화하는데 많은 비용이 드는 경우
  • 인스턴스 변수를 초기화할 때 이니셜라이저가 this에 접근해야 하는 경우

다음의 예에서 temperature 변수가 사용되지 않으면 비용이 많이 드는 _readThermometer() 함수는 절대 호출되지 않습니다.

late String temperature = _readThermometer();

final과 const

finalconstvar 대신 사용하거나 타입에 추가하여 상수를 선언할 수 있습니다. 상수는 단 한 번만 값을 할당할 수 있습니다.
인스턴스 변수에서 final은 사용할 수 있지만, const는 사용할 수 없습니다.

final런타임 상수로 다음과 같이 사용합니다.

final name = 'mebaodng';  // 타입 어노테이션 없이 사용
final String blogName = 'mebadong\' habitat';

final 객체는 수정할 수 없지만, 필드는 수정할 수 있습니다.

final lists = [1, 2, 3, 4, 5];
lists[2] = 30;

print(lists);  // [1, 2, 30, 4, 5] 출력

const 변수는 컴파일 타임 상수로, const 변수가 클래스 수준을 경우 static const를 사용합니다. 숫자 또는 문자열 리터럴, const 변수 또는 상수 산술 연산의 결과와 같은 컴파일 타임 상수로 값을 설정할 수 있습니다.

const bar = 1000000;
const double atm = 1.01325 * bar;

const 키워드는 상수 변수를 선언하는 데만 사용하는 것이 아니라, 상수 값을 생성하는 생성자를 선언할 때도 사용할 수 있습니다. 모든 변수는 상수 값을 가질 수 있습니다.

var foo = const [];  // 리스트의 값을 추가, 삭제, 수정할 수 없음
final bar = const [];
const baz = [];  // const []와 동일

Built-in 타입

Numbers

num 타입이 있으며 하위 타입인 정수형 int와 실수형 double이 있다.
num 타입에는 기본 연산자인 +, -, /, *가 포함되어 있으며, abs(), ceil(), floor() 같은 메서드도 찾아볼 수 있다. (>>와 같은 비트 연산자는 int 클래스에 정의되어 있다.)

문자형을 숫자형으로, 또는 그 반대로 변환하는 방법은 다음과 같다.

// String -> int
var one = int.parse('1');

// String -> double
var onePointOne = double.parse('1.1');

// int -> String
String oneAsString = 1.toString();

// double -> String
String piAsString = 3.141592.toStringAsFixed(2);

Strings

작은 따옴표(') 혹은 큰 따옴표(")를 사용하여 문자열을 만들 수 있습니다.

var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";

${표현식}을 사용하여 문자열에 식의 값을 넣을 수 있습니다. 식별자일 경우 {}를 생략할 수 있습니다. Dart는 객체의 toString() 메서드를 호출합니다.

var s = 'string interpolation';

assert('Dart has $s, which is very handy.' ==
    'Dart has string interpolation, which is very handy.');

assert('That deserves all caps. ${s.toUpperCase()} is very handy!' ==
    'That deserves all caps. STRING INTERPOLATION is very handy!');

인접한 문자열 리터럴 또는 + 연산자를 사용하여 문자열을 연결할 수 있습니다.

var s1 = 'String '
    'concatenation'
    " works even over line breaks.";

var s2 = 'The + operator ' + "works, as well.";

세 개의 작은 따옴표 혹은 큰 따옴표를 사용하여 여러 줄의 문자열을 만들 수 있습니다.

var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";

r을 접두사로 붙여 raw 문자열을 만들 수 있습니다.

var s = r'In a raw string, not even \n get special treatment.';

Booleans

Dart에서 부울 값을 나타내기 위해 bool 타입을 사용합니다.
boolean 리터럴로 truefalse가 있으며 두 가지 모두 컴파일 타임 상수입니다.

Dart의 type safety로 인해 if (nonbooleanValue) 또는 assert (nonbooleaValue)와 같은 코드는 사용할 수 없습니다.

Lists

Dart의 List는 다음과 같이 사용합니다.

var list = [1, 2, 3];

listList<int>로 추론됩니다. int 외의 타입을 넣으려고 할 경우 분석기 또는 런타임에 에러가 발생합니다.

마지막 항목에 ,(쉼표)를 넣을 수 있습니다. trailing comma는 컬렉션에 영향을 주지 않지만 copy-paste 오류를 방지하는 데 도움이 될 수 있습니다.

var list = [
  'Car',
  'Boat',
  'Plane',
];

리스트의 인덱스는 0부터 시작하며 마지막 인덱스는 list.length - 1입니다. [index]를 사용하여 리스트의 값을 참조할 수 있습니다.
리스트 리터럴 전에 const를 추가하면 컴파일 타임 상수 리스트를 만들 수 있습니다.

var constantList = const [1, 2, 3, 4, ];
print(constantList.length);  // 4
print(constantList[constantList.length - 1]);  // 4
// constantList[3] = 0;  // 오류 발생

스프레드 연산자(...)를 사용하면 리스트의 모든 값을 다른 리스트에 추가할 수 있습니다.
오른쪽에 있는 식이 null일 수 있는 경우 null-aware 스프레드 연산자(...?)를 사용하여 예외를 피할 수 있습니다.

var list = [1, 2, 3, ];
var list2 = [0, ...list];
print(list2);  // [0, 1, 2, 3]

var nullList;
var list3 = [0, ...?nullList];
print(list3);  // [0]

Dart는 컬렉션 if컬렉션 for를 사용할 수 있습니다.
조건(if) 및 반복(for)를 사용하여 컬렉션을 생성할 수 있습니다.

컬렉션 if의 예

var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet',
];

컬렉션 for의 예

var listOfInts = [1, 2, 3, ];
var listOfStirngs = [
  '#0',
  for (var i in listOfInts) '#$i',
];
print(listOfStrings);  // ['#0', '#1', '#2', '#3']

Sets

Dart의 set은 순서가 없는 고유한 아이템의 컬렉션입니다.

Dart의 Set은 다음과 같이 사용합니다.

var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine', };

halogensSet<String>으로 추론됩니다.

비어있는 set을 만들기 위해서는 {} 앞에 타입 인자를 적어주거나 Set 타입 변수에 {}를 할당합니다.

var names = <String>{};
// or
Set<String> names = {};

set에 항목을 추가하려면 add()또는 addAll() 메서드를 사용합니다.
.length를 사용하면 set에 있는 항목의 개수를 가져옵니다.
set 리터럴 전에 const를 추가하면 컴파일 타임 상수 set을 만들 수 있습니다.
리스트와 동일하게 ..., ...?과 같은 스프레드 연산자와 컬렉션 if, 컬렉션 for를 지원합니다.

Maps

맵은 keyvalue를 이루어진 객체입니다. 키와 값은 모든 타입의 객체가 될 수 있습니다.
맵에서 키는 유일하며, 값은 중복을 허용합니다.

Dart의 Map은 다음과 같이 사용합니다.

var gifts = {
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings',
};

var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

giftsMap<String, String>으로 추론되고, nobleGasesMap<int, String>으로 추론됩니다.

같은 객체를 Map 생성자를 이용해서 만들 수 있습니다.

var gifts = Map<String, String>();
// var gifts = {};를 사용해도 Map을 만들 수 있음
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = Map<int, String>();
// var nobleGases = <int, String>{};를 사용해도 Map을 만들 수 있음
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';

식별자에 [$key]를 사용하면 값에 접근할 수 있습니다.
맵에 키가 존재하지 않을 경우 null이 반환됩니다.
.length를 사용하면 맵에 있는 항목의 개수를 가져옵니다.
맵 리터럴 전에 const를 추가하면 컴파일 타임 상수 맵을 만들 수 있습니다.
리스트와 동일하게 ..., ...?과 같은 스프레드 연산자와 컬렉션 if, 컬렉션 for를 지원합니다.

Symbols

Symbol 객체는 Dart 프로그램에 선언된 연산자 또는 식별자를 나타냅니다.

함수(Functions)

Dart는 객체 지향 언어로 함수또한 객체로 Function이라는 고유한 타입을 갖습니다.
이는 함수를 변수에 할당하거나 다른 함수의 인수로 전달할 수 있음을 의미합니다. 또한 클래스의 인스턴스를 함수처럼 호출할 수 있습니다.

다음은 함수를 정의하는 예제입니다.

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

함수의 반환 타입을 생략하여도 정상적으로 작동합니다.

isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

함수가 단 하나의 식만 포함한 경우 단축 구문을 사용할 수 있습니다.

// 반환 타입 생략 가능
bool isNoble(int atomicNumber) => _nobleGases[amtomicNumber] != null;

=> expr 구문은 { return expr; }의 단축입니다.

매개변수(Parameters)

함수의 매개변수를 정의할 때나 함수의 인수를 전달 할 때 trailing commas를 사용할 수 있습니다.

명명 된 매개변수 (Named parameters)

명명 된 매개변수는 required를 표시되지 않는 한 선택 사항입니다.
함수를 호출할 때 paramName: value로 명명 된 매개변수를 지정할 수 있습니다.

enableFlags(bold: true, hidden: false);

함수를 정의할 때 {param1, param2, ...}을 사용하여 명명 된 매개변수를 지정할 수 있습니다.

void enableFlags({bool? bold, bool? hidden}) { ... }

명명 된 매개변수는 일종의 선택 사항이지만 매개변수가 필수임을 나타내기 위해 required 키워드를 사용할 수 있습니다.

const Scrollbar({Key? key, required Widget child})

선택적 매개변수 (Optional positional parameters)

함수의 매개변수를 []로 감싸면 선택적 매개변수입니다.

String say(String from, String msg, [String? device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

선택적 매개변수가 없이 함수를 호출할 경우의 예입니다.

print(say('Bob', 'Howdy'));  // Bob says Howdy

세 번째 매개변수를 사용하여 함수를 호출할 경우의 예입니다.

print(say('Bob', 'Howdy', 'smoke signal'));  // Bob says Howdy with a smoke signal

매개변수 기본값

=를 사용하여 명명 된 매개변수와 선택적 매개변수의 기본값을 정의할 수 있습니다. 기본값은 반드시 컴파일 타임 상수여야합니다.
기본값을 지정하지 않으면, 기본값은 null 입니다.

void enableFlags({bool bold = false, bool hidden = false}) { ... }

// bold는 true, hidden은 false가 됩니다.
enableFlags(bold: true);

다음은 선택적 매개변수의 기본값을 설정하는 예입니다.

String say(String from, String msg, [String device = 'carrier pigeon']) {
  var result = '$from says $msg with a $device';
  return result;
}

print(say('Bob', 'Howdy'));  // Bob says Howdy with a carrier pigeon

일급 객체 함수 (Functions as first-class object)

함수를 다른 함수의 매개변수로 전달할 수 있습니다.

void printElement(int element) {
  print(element);
}

var list = [1, 2, 3, ];

list.forEach(printElement);

또한 함수를 변수에 할당할 수 있습니다.

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
print(loudify('hello'));  // !!! HELLO !!!

익명 함수 (Anonymous functions)

대부분의 함수들은 이름을 갖고 있지만, 이름이 없는 익명 함수(anonymous function), 때로는 람다(lambda) 또는 클로저(closure)를 만들 수 있습니다.

다음은 익명 함수를 이용해 리스트에서 항목을 호출해 지정된 인덱스와 값을 출력하는 예제입니다.

const list = ['apples', 'bananas', 'oranges', ];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});
// 단 하나의 식이나 return 문으로 이루어져있을 경우 화살표 표기법으로 바꿀 수 있음
list.forEach((item) => print('${list.indexOf(item)}: $item'));

렉시컬 스코프 (Lexical scope)

Dart는 렉시컬 스코프(lexical scope)를 따르는 언어입니다.
렉시컬 스코프는 함수가 선언되는 위치에 따라서 상위 스코프가 정적으로 결정됩니다.

렉시컬 클로저 (Lexical closures)

클로저(closure)는 함수가 원래 범위 밖에서 사용되는 경우에도 렉시컬 스코프의 변수에 접근할 수 있는 함수 객체입니다.
함수는 주변 스코프에 정의 된 변수를 통해 닫힐 수 있습니다.
다음의 예제에서 makeAdder()는 변수 addBy를 캡처합니다. 반환된 함수가 어디에서 사용되든 addBy를 기억합니다.

Function makeAdder(int addBy) {
  return (int i) => addBy + i;
}

void main() {
  var add2 = makeAdder(2);

  var add4 = makeAdder(4);

  print(add2(2));  // 4
  print(add4(3));  // 7
}

연산자

산술 연산자 (Arithmetic operators)

Dart는 다음 표와 같은 일반적인 산술 연산자를 지원합니다.

연산자 기능
+ 더하기
- 빼기
-expr 표현식의 부호 반전
* 곱하기
/ 나누기
~/ 정수 결과만 반환하는 나누기
% 나눗셈의 나머지

Dart는 또한 전후위 증감 연산자도 지원합니다.

연산자 기능
++var var = var + 1 (표현식의 값은 var + 1)
var++ var = var + 1 (표현식의 값은 var)
--var var = var - 1 (표현식의 값은 var - 1)
var-- var = var - 1 (표현식의 값은 var)

동등 및 관계 연산자 (Equality and relational operators)

Dart는 다음 표와 같은 동등 및 관계 연산자를 지원합니다.

연산자 기능
== 같음
!= 같지 않음
> 왼쪽의 피연산자가 오른쪽 피연산자보다 큼
< 왼쪽의 피연산자가 오른쪽의 피연산자보다 작음
>= 왼쪽의 피연산자가 오른쪽의 피연산자보다 크거나 같음
<= 왼쪽의 피연산자가 오른쪽의 피연산자보다 작거나 같음

드물게 두 객체가 완전히 같은 경우를 판단해야 할 때는 identical() 함수를 사용합니다.

타입 체크 연산자 (Type test operators)

as, is, is! 연산자는 런타임에 타입을 확인하는 데 편리합니다.

연산자 기능
as 형변환(라이브러리 접두사를 지정하는 데에도 사용)
is 객체가 해당 타입에 해당할 경우 참
is! 객체가 해당 타입에 해당하지 않을 경우 참

obj is T의 결과는 objT를 구현할 경우 true입니다.
예를 들어, obj is Object?의 결과는 항상 true입니다.

객체가 해당 타입인 것이 확실한 경우에 객체를 특정 타입으로 캐스트하려면 as 연산자를 사용합니다.

(employee as Person).firstName = 'Bob';

만약 객체의 타입이 T인지 확실하지 않을 경우 객체를 사용하기 전에 obj is T를 사용하여 타입을 확인합니다.

if (employee is Person) {
  // 타입 체크
  employee.firstName = 'Bob';
}

할당 연산자 (Assignment operators)

값을 할당하기 위해서 = 연산자를 사용합니다.
만약 값이 null일 경우에만 값을 할당하려면 ??= 연산자를 사용합니다.

// a에 value를 할당
a = value;
// b가 null일 경우에만 value를 할당, 만약 b에 값이 있을 경우 기존값 유지
b ??= value;

+=와 같은 복합 할당 연산자는 연산과 할당을 결합합니다.

= -= /= %= >>= ^=
+= *= ~/= <<= &= |=

복합 할당 연산자 a op= ba = a op b와 같이 동작합니다.

논리 연산자 (Logical operators)

논리 연산자를 사용하여 부울 식을 잔전하거나 결합할 수 있습니다.

연산자 기능
!expr 값을 반전(false를 true로 변경하거나 혹은 그 반대)
|| 논리적 OR
&& 논리적 AND

비트 및 시프트 연산자 (Bitwise and shift operators)

비트 및 시프트 연산자는 보통 정수와 함께 사용합니다.

연산자 의미
& AND
| OR
^ XOR
~expr 1의 보수(0은 1이 되고 1은 0이 됨)
<< 왼쪽으로 시프트
>> 오른쪽으로 시프트

다음은 비트 및 시프트 연산자의 예입니다.

final value = 0x22;
final bitmask = 0x0f;

print(value & bitmask);  // 0x02
print(value & ~bitmask);  // 0x20
print(value | bitmask);  // 0x2f
print(value ^ bitmask);  // 0x2d
print(value << 4);  // 0x220;
print(value >> 4); // 0x02

조건식

Dart에는 if-else를 간결하게 표현할 수 있는 두 개의 연산자가 있습니다.

condition ? expr1 : expr2
condition이 true일 경우 expr1을, 그렇지 않으면 expr2를 반환합니다.

expr1 ?? expr2
expr1이 null이 아닐 경우 해당 값을 반환하고, 그렇지 않으면 expr2를 평가하고 해당 값을 반환합니다.

캐스케이드 표기법 (Cascade notaion)

캐스케이드(.., ?..)를 사용하면 같은 객체에 일련의 작업을 수행할 수 있습니다. 캐스케이드는 임시 변수를 생성하는 단계를 절약하고 더 유동적인 코드를 작성할 수 있게 해줍니다.

var paint = Paint()
  ..color = Colors.black
  ..strokeCap = StrokeCap.round
  ..strokeWidth = 5.0;

// 동일 코드
var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0;

Paint() 생성자는 Paint 객체를 반환합니다.
캐스케이드 표기법 연산자를 사용하면 반환될 수 있는 값을 무시하고 해당 객체에서 작동합니다.


만약 객체가 null일 수 있는 경우 첫 번째 연산에 null-shorting 캐스케이드(?..)를 사용합니다. 객체가 null일 경우 작업이 실행되지 않습니다.

기타 연산자

연산자 이름 기능
() Function application 함수 호출
[] List access 리스트의 지정된 인덱스의 값 참조
. Member access 표현식의 속성을 참조
?. Conditional member access .와 비슷하지만, 피연산자가 null이 될 수 있음

제어흐름문 (Contol flow statements)

if, else

if (isRaining()) {
  you.bringRainCoat();
} else if (isSnowing()) {
  you.wearJacket();
} else {
  car.putTopDown();
}

JavaScript와는 다르게 조건은 무조건 boolean 값을 사용해야 합니다.

for 루프

var message = StringBuffer('Dart is fun');

for (var i = 0; i < 5; i++) {
  message.write('!');
}

Dart의 for 루프 내부의 클로저는 인덱스 값을 캡처하여 JavaScript에서 발생하는 일반적인 함정을 방지합니다.

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}

callbacks.forEach((c) => c());

위 코드의 출력은 예상대로 01입니다. 하지만 JavaScript에서는 2가 두 번 출력 됩니다.


반복하려는 대상이 Iterable이고 현재 반복 카운터를 알 필요가 없는 경우 for-in 형태의 반복을 사용할 수 있습니다.

for (var candidate in candidates) {
  candidate.interview();
}

Iterable 클래스에는 forEach() 메서드도 있습니다.

var collection = [1, 2, 3];
collection.forEach(print);  // 1 2 3

while과 do-while

while 루프는 루프 이전에 조건을 평가합니다.

while (!isDone()) {
  doSomething();
}

do-while 루프는 루프 이후에 조건을 평가합니다.

do {
  printLine();
} while (!atEndOfPage());

break와 continue

break는 루프를 멈춥니다.

while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}

continue를 사용하여 다음 루프 반복으로 건너뜁니다.

for (int i = 0; i < candidates.length; i++) {
  var canditate = candidates[i];

  if (candidate.yearsExperience < 5) {
    continue;
  }

  candidate.interview();
}

listset과 같은 Iterable을 사용하는 경우 위의 예제를 다르게 작성할 수 있습니다.

candidates
  .where((c) => c.yearsExperience >= 5)
  .forEach((c) => c.interview());

switch와 case

Dart의 switch 문은 정수, 문자열 또는 컴파일 타임 상수를 비교합니다.
비교 대상은 동일한 클래스의 인스턴스여야 하며 클래스는 ==를 재정의해서는 안 됩니다.


비어 있지 않은 case 절은 break로 끝납니다. continue, throw 또는 return문을 사용하여 종료할 수도 있습니다.


case 절이 일치하지 않을 경우 default 절을 사용하여 코드를 실행합니다.

var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}

Dart는 빈 case 절의 폴스루(fall-through)를 지원하지 않습니다.
폴스루(fall-through)를 원할 경우 continue 문과 lable을 사용할 수 있습니다.

var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;  // nowClosed 레이블에서 계속 실행

  nowClosed:
  case 'NOW_CLOSED':
    executeNowClosed();
    break;
}

Assert

개발을 하는 동안 assert- assert(condition, optionalMessage); -을 사용할 수 있습니다.


assert의 첫 번째 인수는 부울 값으로 해석되는 모든 표현식이 될 수 있습니다. 표현식의 값이 true이면 어설션(assertion)이 성공하고 실행이 계속 됩니다. false이면 어설션이 실패하고 예외(AssertionError)가 발생합니다.

예외 (Exceptions)

Throw

throw를 사용해서 예외를 던지거나 발생시킵니다.

throw FormatException('Expected at least 1 section');

// 임의의 객체를 던질 수 있음
throw 'Out of llamas!';

예외를 던지는 것은 표현식(expression)이기 때문에 => 문뿐만 아니라 표현식을 허용하는 다른 모든 곳에서 예외를 던질 수 있습니다.

void distanceTo(Point other) => throw UnimplementedError();

Catch

예외를 포착하거나 캡처하면 예외를 다시 발생시키지 않는 한 예외가 전파되는 것을 막습니다.

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

둘 이상의 예외 유형을 throw할 수 있는 코드를 처리하기 위해 여러 catch 절을 지정할 수 있습니다. 던져진 객체의 유형과 일치하는 첫 번째 catch 절이 예외를 처리합니다. catch 절이 유형을 지정하지 않으면 해당 절은 모든 유형의 throw된 객체를 처리할 수 있습니다.

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 특정 예외
  buyMoreLlamas();
} on Exception catch (e) {
  // 다른 예외
  print('Unknown exception: $e');
} catch (e) {
  // 지정된 유형이 없으며 모두 처리
  print('Something really unknown: $e');
}

on 또는 catch 또는 둘 다 사용할 수 있습니다.
on은 특정 예외 타입을 지정해야할 때 사용합니다.
catch는 예외 핸들러에 예외 객체가 필요할 때 사용합니다.


catch()에 하나 혹은 두 개의 매개변수를 지정할 수 있습니다.
첫 번째는 throw된 예외이고, 두 번째는 스택 추적(StackTrace) 객체입니다.

try {
  // ...
} on Exception catch (e) {
  print('Exception detatils:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

예외가 전파되도록 허용하면서 부분적으로 예외를 처리하려면 rethrow 키워드를 사용합니다.

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++);  // 런타임 에러 발생
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow;  // 호출자가 예외를 보도록 허용
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

Finally

예외가 발생했는지 여부에 관계 없이 코드를 실행되게 하려면 finally 절을 사용합니다. 만약 catch 절이 예외와 일치하지 않으면 finally 절이 실행된 후 예외가 전파됩니다.

try {
  breedMoreLlamas();
} finally {
  // 예외가 발생하더라도 항상 cleanLlamaStalls() 실행
  cleanLlamaStalls();
}

finally 절은 catch 절에 다음에 실행됩니다.

try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e');  // 예외를 먼저 처리
} finally {
  cleanLlamasStalls();  // 다음 cleanLlamasStalls() 실행
}

클래스 (Classes)

Dart는 클래스와 믹스인 기반 상속이 있는 객체 지향 언어입니다.
모든 객체는 클래스의 인스턴스이며 Null을 제외한 모든 클래스는 Object의 자손입니다.
믹스인 기반(Mixin-based) 상속은 모든 클래스 (상위 클래스, Object? 제외)가 정확히 하나의 수퍼 클래스를 갖지만 클래스 본문은 여러 클래스 계층에서 재사용될 수 있음을 의미합니다.
확장 메서드(extension methods)는 클래스를 변경하거나 하위 클래스를 만들지 않고 클래스에 기능을 추가하는 방법 입니다.

클래스 멤버 사용

.을 사용하여 클래스의 인스턴스 변수 혹은 메서드에 접근할 수 있습니다.

var p = Point(2, 2);

assert(p.y == 2);

double distance = p.distanceTo(Point(4, 4));

?.를 사용하여 왼쪽 피연산자가 null일 경우 발생하는 예외를 방지할 수 있습니다.

var a = p?.y;

생성자 사용

생성자(constructor)를 사용해 객체를 생성할 수 있습니다.
생성자의 이름은 ClassName 혹은 ClassName.identifier 입니다.
예를 들어 Point() 또는 Point.fromJson() 생성자를 사용하여 Point 객체를 생성할 수 있습니다.


💡 new 키워드는 Dart 2에서 선택 사항이 되었습니다.

객체 타입 가져오기

ObjectruntimeType 속성을 사용하면 런타임에 객체의 타입을 가져올 수 있습니다.

print('The type of a is ${a.runtimeType}');

인스턴스 변수 (Instance variables)

초기화 되지 않은 인스턴스 변수null 값을 가집니다.
모든 인스턴스 변수는 암시적으로 getter 메서드를 생성합니다. non-final 인스턴스 변수 또는 초기화 되지 않은 late final 인스턴스 변수는 암시적으로 setter 메서드를 생성합니다.


인스턴스 변수는 final일 수 있으며 이 경우 값을 단 한 번만 설정해야 합니다.
생성자 매개변수를 사용하거나, 생성자의 이니셜라이저 목록을 사용하여 선언 시 final 인스턴스 변수를 초기화 합니다.


생성자 본문이 시작된 후 final 인스턴스 변수의 값을 할당하려는 경우 late final을 사용할 수 있습니다.
⚠ 이니셜라이저가 없는 late final 인스턴스 변수는 setter가 정의 됩니다. 해당 필드가 public일 경우 setter 또한 public입니다. 사용시 유의하세요.

생성자 (Constructors)

클래스명과 같은 함수를 생성하여 생성자를 선언합니다.

class Point {
  double x = 0;
  double y = 0;

  Point(double x, double y) {
    this.x = x;
    this.y = y;
  }
}

this 키워드는 현재 인스턴스를 나타냅니다.


인스턴스 변수에 생성자 인수를 할당하는 패턴은 매우 일반적이므로 Dart에서는 이를 위한 syntactic sugar를 제공합니다.

class Point {
  double x = 0;
  double y = 0;

  Point(this.x, this.y);
}

기본 생성자 (Default constructors)

생성자를 선언하지 않았을 경우 기본 생성자가 제공됩니다.
기본 생성자는 인수가 없으며 수퍼 클래스의 인수가 없는 생성자를 호출합니다.

생성자는 상속되지 않습니다.

서브 클래스는 수퍼 클래스의 생성자를 상속하지 않습니다.
생성자를 선언하지 않은 하위 클래스에는 기본 생성자만 있습니다.

이름 있는 생성자

이름 있는 생성자 (Named constructors)를 사용하여 클래스에 대해 여러 생성자를 구현하거나 명확성을 제공합니다.

const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  double x = 0;
  double y = 0;

  Point(this.x, this.y);

  // 이름 있는 생성자
  Potin.origin()
    : x = xOrigin,
      y = yOrigin;
}

기본이 아닌 수퍼 클래스의 생성자 호출

생성자의 실행 순서는 다음과 같습니다.

  1. 초기화 목록 (initializer list)
  2. 수퍼 클래스의 인수가 없는 생성자
  3. 메인 클래스의 인수가 없는 생성자

만약 수퍼 클래스가 이름이 없고 인수가 없는 생성자가 없으면 수퍼 클래스의 생성자 중 하나를 수동으로 호출해야 합니다. 콜론(:) 뒤, 생성자 본문(있는 경우) 바로 앞에 수퍼 클래스 생성자를 지정합니다.

class Person {
  String? firstName;

  Person.fromJson(Map data) {
    print('in person');
  }
}

class Employee extends Person {
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

초기화 목록 (Initializer list)

생성자 본문이 실행되기 전에 초기화 목록을 사용해 인스턴스 변수를 초기화할 수도 있습니다.
이니셜라이저를 콤마로 구분합니다.

// 생성자 본문이 실행되기 전에 초기화 목록으로 인스턴스 변수 설정
Point.fromJson(Map<String, double> json)
  : x = json['x']!,
    y = json['y']! {
      print('In Point.fromJson(): ($x, $y)');
  }
}

⚠ 초기화 목록에서는 this에 접근할 수 없습니다.

초기화 목록final 필드를 설정할 때 편리합니다.

class Point {
  final double x;
  final double y;
  final double distanceFromOrigin;

  Point(double x, double y)
    : x = x,
      y = y,
      distanceFromOrigin = sqrt(x * x + y * y);
}

생성자 리디렉션 (Redirecting constructors)

리디렉션 생성자의 본문은 비어 있으며 생성자 호출은 콜론(:) 뒤에 나타납니다.

class Point {
  double x, y;

// 클래스의 메인 생성자
  Point(this.x, this.y);

  // 메인 생성자에게 위임
  Point.alongXAxis(double x) : this(x, 0);
}

상수 생성자 (Constant constructors)

클래스가 절대로 변하지 않는 객체를 생성하는 경우 이러한 객체를 const 생성자를 사용하여 컴파일 타임 상수로 만들 수 있습니다.

class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

상수 생성자를 사용하려면 호출 시 const 키워드를 사용해야 합니다.
상수 컨텍스트상수 생성자를 사용할 경우 const 키워드를 생략 가능합니다.


상수 컨텍스트가 외부에 상수 생성자const 없이 호출할 경우 상수가 아닌 객체를 생성합니다.

팩토리 생성자 (Factory constructors)

해당 클래스의 새 인스턴스를 생성하지 않는 생성자를 구현할 때 factory 키워드를 사용합니다.
예를 들어, 팩토리 생성자는 캐시에서 인스턴스를 반환하거나 서브타입의 인스턴스를 반환할 수 있습니다.
팩토리 생성자는 초기화 목록에서 처리할 수 없는 논리를 사용하여 최종 변수를 초기화할 때도 사용합니다.

class Logger {
  final String name;
  bool mute = false;

  static final Map<String, Logger> _cache = <String, Logger>{};

  factory Logger(String name) {
    return _cache.puIfAbsent(name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

팩토리 생성자this에 접근 권한이 없습니다.

메서드 (Methods)

메서드는 객체의 동작을 제공하는 함수입니다.

인스턴스 메서드 (Instance methods)

인스턴스 메서드는 객체의 인스턴스 변수this에 접근이 가능합니다.

연산자 (Operators)

연산자는 특별한 이름을 가진 인스턴스 메서드입니다.
Dart는 다음 이름으로 연산자를 정의할 수 있습니다.

< + | []
> / ^ []=
<= ~/ & ~
>= * << ==
- % >>

연산자 선언에는 operator 식별자를 사용합니다.

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector opertaor +(Vector v) => Vector(x + v.x, y + v.y);
  Vector opertaor -(Vector v) => Vector(x - v.x, y - v.y);

  // == 연산자와 hashCode 정의 생략
  // ...
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

접근자와 설정자

접근자 (getter)설정자 (setter)는 객체 속성에 대한 읽기 및 쓰기를 제공하는 특수 메서드입니다.
각 인스턴스 변수는 암시적으로 접근자와 적절한 경우 설정자를 생성합니다.


getset 키워드를 사용하여 접근자 및 설정자 구현하여 추가 속성을 만들 수 있습니다.

class Rectangle {
  double left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // 두 개의 계산된 속성 : right 및 bottom
  double get right => left + width;
  set right(double value) => left = value - width;
  double get bottom => top + height;
  set bottom(double value) => top = value - height;
}

추상 메서드 (Abstract methods)

추상 메서드는 인터페이스를 정의하지만 구현은 다른 클래스에 맡깁니다.
추상 메서드추상 클래스(abstract class)에만 존재할 수 있습니다.


메서드를 추상화하기 위해서는 본문 대신 세미콜론(;)을 사용합니다.

abstract class Doer {
  void doSomething();  // 추상 메서드 정의
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // 메서드 구현
  }
}

추상 클래스 (Abstract classes)

abstract 제어자를 사용하여 추상 클래스(abstract class) (인스턴스화할 수 없는 클래스)를 정의합니다.


추상 클래스에는 종종 추상 메서드가 있습니다.

abstract class AbstractContainer {
  void updateChildren();  // 추상 메서드
}

암묵적 인터페이스 (Implicit interfaces)

모든 클래스는 클래스의 모든 인스턴스 멤버와 구현하는 모든 인터페이스를 포함하는 인터페이스를 암묵적으로 정의합니다.

// Person 클래스. 암묵적 인터페이스에는 greet()가 포함 됨
class Person {
  // 인터페이스에 포함, 하지만 이 라이브러리에서만 볼 수 있음
  final String _name;

  // 인터페이스에 포함되지 않음
  Person(this._name);

  // 인터페이스에 포함
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// Person 인터페이스를 구현
class Impostor implements Person {
  String get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

하나의 클래스는 여러 인터페이스를 구현할 수 있습니다.

class Point implements Comparable, Loaction { ... }

클래스 확장 (Extending a class)

extneds를 사용하여 서브 클래스를 만들고, super를 사용하여 수퍼 클래스를 참조합니다.

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }

  // ...
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }

  // ...
}

멤버 오버라이딩 (Overriding members)

서브 클래스는 연산자(operators)를 포함한 인스턴스 메서드, 접근자(getter), 설정자(setter)를 재정의할 수 있습니다.
@override 어노테이션을 사용하여 의도적으로 멤버를 재정의하고 있음을 나타낼 수 있습니다.

class SmartTelevision extends Television {
  @override
  void turnOn() { ... }
  // ...
}

noSuchMethod()

noSuchMethod()를 재정의하면 코드가 존재하지 않는 메서드나 인스턴스 변수를 사용하려고 할 때의 작업을 지정할 수 있습니다.

class A {
  // noSuchMethod()를 재정의 하지 않으면 존재하지 않는
  // 멤버를 사용하려 할 때 NoSuchMethodError가 발생
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: '
      '${invocation.memberName}');
  }
}

확장 메서드 (Extension methods)

확장 메서드는 기존 라이브러리에 기능을 추가하는 방법입니다.
자세한 내용은 extension methods page를 참조하세요.

열거형 (Enumerated types)

enumerations 또는 enums라고도 하는 열거형은 고정된 상수 값을 나타내는 데 사용되는 특수한 종류의 클래스입니다.

열거형 사용

열거형은 enum 키워드를 사용하여 정의할 수 있습니다.

enum Color { red, green, blue }

열거형을 선언할 때 trailing comma를 사용할 수 있습니다.


열거형에는 0부터 시작하는 위치를 반환하는 index 접근자가 있습니다.
첫 번째 값의 인덱스는 0이고, 두 번째 값의 인덱스는 1 입니다.

print(Color.red.index);  // 0
print(Color.blue.index);  // 2

values 상수를 사용하면 열거형의 모든 값 리스트를 가져올 수 있습니다.

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

switch 문에서 열거형을 사용할 수 있으며, 열거형의 모든 값을 처리하지 않으면 경고가 표시됩니다.

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default:  // 이 부분을 지우면 경고 발생
    print(aColor);
}

열거형에는 다음과 같은 제한이 있습니다.

  • 열거형을 서브 클래스화 하거나, 믹스인하거나 구현할 수 없습니다.
  • 열거형을 명시적으로 인스턴스화할 수 없습니다.

클래스에 기능 추가: mixins

믹스인은 여러 클래스 계층에서 클래스 코드를 재사용하는 방법입니다.


믹스인을 사용하려면 with 키워드 뒤에 하나 이상의 믹스인 이름을 사용합니다.

class Musician extends Performer with Musical {
  // ...
}

class Maestro extends Person
  with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
   }
}

믹스인을 구현하려면, Object를 확장하고 생성자를 선언하지 않는 클래스를 만듭니다.
믹스인을 일반 클래스로 사용하지 않으려면 class 대신 mixin을 사용합니다.

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

on 키워드를 사용해 필요한 수퍼 클래스를 지정하여 mixin의 사용을 제한할 수 있습니다.

class Musician {
  // ...
}
mixin MusicalPerformer on Musician {
  // ...
}
class SingerDancer extends Musician with MusicalPerformer {
  // ...
}

위 코드에서 Musician 클래스를 확장하거나 구현하는 클래스만 MusicalPerformer 믹스인을 사용할 수 있습니다.

클래스 변수와 메서드 (Class variables and methods)

static 키워드를 사용하여 클래스 변수 및 메서드를 구현합니다.

정적 변수 (Static variables)

정적 변수 (클래스 변수)는 클래스 전체의 상태 또는 상수로 사용합니다.

class Queue {
  static const initialCapacity = 16;
  // ,,,
}
void main() {
  print(Queue.initialCapacity);  // 16
}

정적 변수는 사용될 때까지 초기화되지 않습니다.

정적 메서드 (Static methods)

정적 메서드 (클래스 메서드)는 인스턴스에서 작동하지 않으므로 this에 대한 접근 권한이 없습니다.
정적 메서드는 정적 변수에 접근할 수 있으며 ,클래스에서 직접 호출합니다.

import 'dart:math';

class Point {
  double x, y;
  Point(this.x, this.y);

  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);  // true
  print(distance);  // 2.8284271247461903
}

제네릭 (Generics)

제네릭을 사용하는 이유

제네릭은 종종 타입 안전성 (type safety)를 위해 필요하지만, 더 많은 이점이 있습니다.

  • 제네릭 유형을 올바르게 지정하면 더 나은 코드가 생성됩니다.
  • 제네릭을 사용하여 코드 중복을 줄일 수 있습니다.

매개변수화된 타입 제한 (Restricting the parameterized type)

제네릭 유형을 구현할 때 extends를 사용하여 해당 매개변수의 타입을 제한할 수 있습니다.

class Foo<T extends SomeBaseClass> {
  String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass { ... }

SomeBaseClass 또는 그 서브 클래스를 일반 인수로 사용할 수 있습니다.

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();

또한, 인수를 지정하지 않아도 됩니다.

var foo = Foo();
print(foo);  // Instance of 'Foo<SomeBaseClass>'

SomeBaseClass가 아닌 클래스를 지정하면 오류가 발생합니다.

제네릭 메서드 사용 (Using generic methods)

T first<T>(List<T> ts) {
  T tmp = ts[0];

  return tmp;
}

위 코드에서 first(<T>)의 제네릭 타입 매개 변수를 사용하면 여러 위치에서 제네릭 인수 T를 사용할 수 있습니다.

  • 함수의 반환 유형 (T)
  • 인수 유형 (List<T>)
  • 지역 변수의 유형(T tmp)

라이브러리 및 가시성 (Libraries and visibility)

importlibrary는 모듈식의 공유 가능한 코드 기반을 만드는 데 도움이 될 수 있습니다.
라이브러리는 API를 제공할 뿐만 아니라 개인 정보 보호 단위입니다: 밑줄(_)로 시작하는 식별자는 라이브러리 내에서만 볼 수 있습니다.
모든 Dart 앱은 library 지시문을 사용하지 않아도 라이브러리입니다.

라이브러리 사용 (Using libraries)

import를 사용하여 한 라이브러리의 네임스페이스가 다른 라이브러리의 범위에서 사용되는 방식을 지정합니다.

import 'dart:html';

import에 필요한 인수는 라이브러리를 지정하는 URI 뿐입니다.
내장 라이브러의 경우 URI에는 특별한 dart: 체계가 있습니다.
다른 라이브러리의 경우 파일 시스템 경로 또는 package: 체계를 사용할 수 있습니다.

라이브러리 접두사 지정 (Specifying a library prefix)

충돌하는 식별자가 있는 두 개의 라이브러리를 가져오는 경우 하나 또는 두 라이브러리 모두에 대해 접두사를 지정할 수 있습니다.
예를 들어, library1library2 모두에 Element 클래스가 있는 경우 다음과 같이 사용합니다.

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// lib1의 Element 사용
Element element1 = Element();

// lib2의 Element 사용
lib2.Element element2 = lib2.Element();

라이브러리의 일부만 가져오기 (Importing only part of library)

라이브러리를 일부만 사용하려는 경우 라이브러리를 선택적으로 가져올 수 있습니다.

// foo만 가져오기
import 'package:lib1/lib1.dart' show foo;

// foo를 제외한 모두 가져오기
import 'package:lib2/lib2.dart' hide foo;

Lazily loading a library

지연 로딩 (lazy loading)을 사용하면 라이브러리가 필요한 경우 웹 앱이 요청 시 라이브러리를 로드할 수 있습니다.

  • 웹 앱의 초기 시작 시간을 줄이기 위해
  • A/B 테스트 수행
  • 선택적 화면 및 대화 상자와 같이 거의 사용되지 않는 기능을 로드

dart2js만 지연 로딩을 지원합니다. Flutter, Dart VMdartdevc는 지연 로딩을 지원하지 않습니다.


deferred as를 사용하여 지연 로딩을 사용할 수 있습니다.

import 'package:greetings/hello.dart' deferred as hello;

라이브러리가 필요할 때 라이브러리의 식별자를 사용하여 loadLibrary()를 호출합니다.

Future<void> greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}

비동기 지원 (Asynchrony support)

Dart 라이브러리는 Future 또는 Stream 객체를 반환하는 함수들이 많습니다.
이러한 함수는 비동기식 입니다.
시간이 많이 소요될 수 있는 작업 (예: I/O)을 설정한 후 해당 작업이 완료될 때까지 기다리지 않고 반환됩니다.


asyncawait 키워드는 비동기 프로그래밍을 지원하므로 동기 코드와 유사한 비동기 코드를 작성할 수 있습니다.

Future 처리 (Handling Futures)

완료된 Future의 결과가 필요한 경우 두 가지 옵션이 있습니다.

  • asyncawait 사용
  • Future API 사용

asyncawait를 사용하는 코드는 비동기식이지만 동기식 코드와 유사합니다.

// await를 비동기의 결과를 기다리는 함수
await lookUpVersion();

await를 사용하려면 코드가 async 함수에 있어야 합니다.

Future<void> checkVersion() async {
  var version = await lookUpVersion();
}

async 함수는 시간이 많이 걸리는 연산을 수행할 수 있지만 작업을 기다리지 않습니다. 대신 async 함수는 첫 번째 await가 나타날 때까지만 실행됩니다. 그런 다음 Future 객체를 반환하고 await 식이 완료된 후에 실행을 재개합니다.

try, catchfinally를 사용하여 await를 사용하는 코드에서 오류 및 정리를 처리합니다.

try {
  version = await lookUpVersion();
} catch (e) {
  // 버전을 조회할 수 없을 경우의 처리
}

async 함수에서 await를 여러 번 사용할 수 있습니다.


await 표현식에서 표현식의 값은 일반적으로 Future입니다. 그렇지 않은 경우 값은 자동으로 Future에 래핑됩니다.
Future 객체는 객체를 반환하겠다는 약속을 나타냅니다. await 표현식의 값은 반환된 객체입니다. await 표현식은 해당 객체를 사용할 수 있을 때까지 실행을 일시 중지 합니다.

비동기 함수 선언 (Declaring async functions)

비동기 함수 (async function)는 본문이 async 제어자로 표시된 함수입니다.


async 키워드를 함수에 추가하면 Future를 반환합니다.
함수 본문에서는 Future API를 사용할 필요가 없습니다. 필요한 경우 Dart가 Future 객체를 생성합니다.
함수가 유용한 값을 반환하지 않으면 해당 반환 유형을 Future<void>로 설정합니다.

스트림 처리 (Handling Streams)

스트림(Stream)에서 값을 가져와야 하는 경우 두 가지 옵션이 있습니다.

  • aysnc비동기 for 루프(await for)를 사용
  • Stream API 사용

⚠ UI 프레임워크는 이벤트의 스트림을 계속해서 생성하기 때문에 await for를 사용해서는 안됩니다.


비동기 for 루프의 형식은 다음과 같습니다.

await for (varOrType identifier in expression) {
  // 스트림이 값을 낼때마다 실행
}

표현식(expression) 의 값 타입은 반드시 스트림이어야 합니다.
실행은 다음과 같이 진행됩니다.

  • 스트림이 값을 낼 때까지 기다립니다.
  • 변수가 방출된 값으로 설정된 상태에서 for 루프의 본문을 실행합니다.
  • 스트림이 닫힐 때까지 1과 2를 반복합니다.

breakreturn 문을 사용하여 스트림 수신을 중지할 수 있습니다.

제네레이터 (Generators)

값의 시퀀스를 느리게 생성해야 하는 경우 generator function 의 사용을 고려하세요.
Dart는 두 가지 종류의 generator function을 기본적으로 지원합니다.

  • Synchronous generator: Iterable 객체를 반환
  • Asynchronous generator: Stream 객체를 반환

synchronous generator 함수를 구현하려면 함수 본문을 sync*로 표시하고, yield 문을 사용하여 값을 전달합니다.

Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n ) yield k++;
}

asynchronous generator 함수를 구현하려면 함부 본문을 async*로 표시하고, yield 문을 사용하여 값을 전달합니다.

Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

제너레이터가 재귀적인 경우 yield*를 사용하여 성능을 향상시킬 수 있습니다.

Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

호출 가능한 클래스 (Callable classes)

클래스의 인스턴스를 함수처럼 호출하려면 call() 메서드를 구현합니다.


WannabeFunction 클래스는 세 개의 문자열을 가져와 연결하고 각각을 공백으로 구분한 뒤 느낌표를 추가하는 call() 함수를 정의합니다.

class WannabeFuntion {
  String call(String a, String b, String c) => '$a $b $c!';
}

var wf = WannabeFuntion();
var out  = wf('Hi', 'there', 'gang');

void main() => print(out);

Isolates

대부분의 컴퓨터는 멀티 코어 CPU가 있습니다.
이러한 모든 코어를 활용하기 위해 개발자는 일반적으로 동시에 실행되는 공유 메모리 스레드(shared-memory threads)를 사용합니다.
그러나 공유 상태 동시성(shared-state concurrency)은 오류가 발생하기 쉽고 복잡한 코드로 이어질 수 있습니다.


스레드 대신 모든 Dart 코드가 격리된 내부에서 실행됩니다.
각 격리는 자체 메모리 힙이 있어 다른 격리된 상태로 액세스할 수 없습니다.

Typedefs

type alias는 (typedef 키워드로 선언되기 때문에 typedef라고도 함) 타입을 참조하는 간단한 방법입니다.

typedef IntList = List<int>;
IntList il = [1, 2, 3];

type alias타입 매개 변수가 있을 수 있습니다.

typedef ListMapper<X> = Map<X, List<X>>;
// 두 개는 동일한 타입
Map<String, List<String>> m1 = {};
ListMapper<String> m2 = {};

메타 데이터 (Metadata)

메타데이터를 사용하여 코드에 대한 정보를 제공합니다.
메타데이터 어노테이션(metadata annotaion)은 @ 문자로 시작합니다.

class Television {
  // Use [turnOn] to turn the power on instead.
  @Deprecated('Use turnOn instead')
  void activate() {
    turnOn();
  }

  // Turns the TV's power on.
  void turnOn() { ... }
}

메타데이터 어노테이션 (metadata annotation)을 정의할 수 있습니다.

library todo;

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}

@Todo 어노테이션을 사용하는 예입니다.

import 'todo.dart';

@Todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}

메타데이터는 라이브러리, 클래스, typedef, 타입 매개 변수, 생성자, factory, 함수, field, 매개 변수 또는 변수 선언 및 가져오기 또는 내보내기 지시문 앞에 표시될 수 있습니다.

728x90