변수
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
final
과 const
를 var
대신 사용하거나 타입에 추가하여 상수를 선언할 수 있습니다. 상수는 단 한 번만 값을 할당할 수 있습니다.
인스턴스 변수에서 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 리터럴로 true
와 false
가 있으며 두 가지 모두 컴파일 타임 상수입니다.
Dart의 type safety로 인해 if (nonbooleanValue)
또는 assert (nonbooleaValue)
와 같은 코드는 사용할 수 없습니다.
Lists
Dart의 List
는 다음과 같이 사용합니다.
var list = [1, 2, 3];
list
는 List<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', };
halogens
는 Set<String>
으로 추론됩니다.
비어있는 set을 만들기 위해서는 {}
앞에 타입 인자
를 적어주거나 Set
타입 변수에 {}
를 할당합니다.
var names = <String>{};
// or
Set<String> names = {};
set에 항목을 추가하려면 add()
또는 addAll()
메서드를 사용합니다..length
를 사용하면 set에 있는 항목의 개수를 가져옵니다.
set 리터럴 전에 const
를 추가하면 컴파일 타임 상수 set을 만들 수 있습니다.
리스트와 동일하게 ...
, ...?
과 같은 스프레드 연산자와 컬렉션 if
, 컬렉션 for
를 지원합니다.
Maps
맵은 key
와 value
를 이루어진 객체입니다. 키와 값은 모든 타입의 객체가 될 수 있습니다.
맵에서 키는 유일하며, 값은 중복을 허용합니다.
Dart의 Map
은 다음과 같이 사용합니다.
var gifts = {
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings',
};
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
gifts
는 Map<String, String>
으로 추론되고, nobleGases
는 Map<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
의 결과는 obj
가 T
를 구현할 경우 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= b
는 a = 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());
위 코드의 출력은 예상대로 0
과 1
입니다. 하지만 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();
}
list
나 set
과 같은 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에서 선택 사항이 되었습니다.
객체 타입 가져오기
Object
의 runtimeType
속성을 사용하면 런타임에 객체의 타입을 가져올 수 있습니다.
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;
}
기본이 아닌 수퍼 클래스의 생성자 호출
생성자의 실행 순서는 다음과 같습니다.
- 초기화 목록 (initializer list)
- 수퍼 클래스의 인수가 없는 생성자
- 메인 클래스의 인수가 없는 생성자
만약 수퍼 클래스가 이름이 없고 인수가 없는 생성자가 없으면 수퍼 클래스의 생성자 중 하나를 수동으로 호출해야 합니다. 콜론(:)
뒤, 생성자 본문(있는 경우) 바로 앞에 수퍼 클래스 생성자를 지정합니다.
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)
는 객체 속성에 대한 읽기 및 쓰기를 제공하는 특수 메서드입니다.
각 인스턴스 변수는 암시적으로 접근자와 적절한 경우 설정자를 생성합니다.get
및 set
키워드를 사용하여 접근자 및 설정자 구현하여 추가 속성을 만들 수 있습니다.
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)
import
및 library
는 모듈식의 공유 가능한 코드 기반을 만드는 데 도움이 될 수 있습니다.
라이브러리는 API를 제공할 뿐만 아니라 개인 정보 보호 단위입니다: 밑줄(_)
로 시작하는 식별자는 라이브러리 내에서만 볼 수 있습니다.
모든 Dart 앱은 library
지시문을 사용하지 않아도 라이브러리입니다.
라이브러리 사용 (Using libraries)
import
를 사용하여 한 라이브러리의 네임스페이스가 다른 라이브러리의 범위에서 사용되는 방식을 지정합니다.
import 'dart:html';
import
에 필요한 인수는 라이브러리를 지정하는 URI
뿐입니다.
내장 라이브러의 경우 URI에는 특별한 dart:
체계가 있습니다.
다른 라이브러리의 경우 파일 시스템 경로
또는 package:
체계를 사용할 수 있습니다.
라이브러리 접두사 지정 (Specifying a library prefix)
충돌하는 식별자가 있는 두 개의 라이브러리를 가져오는 경우 하나 또는 두 라이브러리 모두에 대해 접두사를 지정할 수 있습니다.
예를 들어, library1
과 library2
모두에 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 VM
및 dartdevc
는 지연 로딩을 지원하지 않습니다.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)을 설정한 후 해당 작업이 완료될 때까지 기다리지 않고 반환됩니다.async
및 await
키워드는 비동기 프로그래밍을 지원하므로 동기 코드와 유사한 비동기 코드를 작성할 수 있습니다.
Future 처리 (Handling Futures)
완료된 Future
의 결과가 필요한 경우 두 가지 옵션이 있습니다.
async
와await
사용Future API
사용
async
및 await
를 사용하는 코드는 비동기식이지만 동기식 코드와 유사합니다.
// await를 비동기의 결과를 기다리는 함수
await lookUpVersion();
await
를 사용하려면 코드가 async
함수에 있어야 합니다.
Future<void> checkVersion() async {
var version = await lookUpVersion();
}
async
함수는 시간이 많이 걸리는 연산을 수행할 수 있지만 작업을 기다리지 않습니다. 대신 async
함수는 첫 번째 await
가 나타날 때까지만 실행됩니다. 그런 다음 Future
객체를 반환하고 await
식이 완료된 후에 실행을 재개합니다.
try
, catch
및 finally
를 사용하여 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를 반복합니다.
break
나 return
문을 사용하여 스트림 수신을 중지할 수 있습니다.
제네레이터 (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, 매개 변수 또는 변수 선언 및 가져오기 또는 내보내기 지시문 앞에 표시될 수 있습니다.
'App > Flutter' 카테고리의 다른 글
[Dart] Dart Language sample - Dart부터 시작하는 Flutter 앱 개발 1 (0) | 2021.06.03 |
---|---|
[Flutter] flutter doctor - CocoaPods 1.9.3 out of date (1.10.0 is recommended) 에러 해결하기 (0) | 2021.05.26 |