이번에는 플러터에서 사용되는 Dart 문법에 대해서 알아보자.
Dart 언어만을 다루기 때문에 위 링크를 통해 Dart 문법에 대해서만 확인하면서 진행하도록 하자.
var와 dynamic
Dart에서 Java와 비슷하게 변수를 선언하지만 Java와 다르게 타입 추론이 가능하다.
String name = '준장';
var friend = '준혁'; // 타입 추론
변수 name은 String으로 타입을 명시해주었지만 변수 friend 같은 경우, 타입을 var로 지정하여 타입 추론을 하게 했다. 여기서 주의할 점은 var 타입으로 변수를 선언할 때 초깃값을 참조하여 해당 변수의 타입을 추론하기 때문에 한번 var로 선언한 변수는 다른 타입의 값으로 넣어서는 안 된다는 것이다.
다음 코드를 확인해보자.
var friend = '준혁';
friend = 23; // Error: Compilation failed.
위 코드처럼 var 타입으로 선언한 변수 friend는 초깃값을 String 타입으로 추론되었기 때문에 그 후 int 타입의 값을 넣으려고 하면 컴파일 에러가 나오게 되는 것을 확인할 수 있다.
그런데 Dart에는 타입 변경이 가능한 dynamic 타입도 있다.
dynamic 같은 경우도 var와 같이 타입 추론이 가능하면서 타입 변경도 가능하다.또한 매개변수에서도 dynamic을 지원하기 때문에 타입을 명시할 필요 없다.
dynamic friend = '진우';
friend = 102;
코드를 보게되면 dynamic 타입으로 선언한 변수 friend의 초기값이 String 타입으로 추론되었지만 후에 friend에 int 타입의 값을 넣어 타입 변경 한 것을 확인할 수 있다.
타입 캐스팅과 num
Java는 큰 타입이 작은 타입을 명시적으로 타입 캐스팅하지 않아도 자동으로 타입 캐스팅이 되지만 Dart의 경우 타입 캐스팅을 해야 한다.
int n = 34;
double m = n; // Error: Compilation failed.
int n = 34;
double m = n as double;
int와 double 같은 경우는 상위 타입인 num으로 선언할 수도 있다.
num a = 34;
num b = 34.8;
num 같은 경우 int와 double 형식을 둘 다 받을 수 있기 때문에 온도 데이터, 키와 몸무게 데이터 등 int와 double 타입의 데이터를 구분 없이 받아야 할 때 사용한다.
final과 const
final과 const 모두 한번 선언하면 값을 변경할 수 없다.
이렇게 final과 const의 공통점이 있다면 차이점도 있다.
final로 정의한 값은 런타임에서 정의된 값으로 설정할 수 있지만 const의 경우 정의한 값은 런타임에서 정의된 값으로 설정할 수 없다. 런타임에서 호출될 때마다 결과 값이 달라지는 Date.now() 메서드를 사용하여 결과를 확인해보자.
// funal로 정의한 값은 런타임에서 정의된 값을 설정할 수 있다.
final c = DateTime.now();
// const로 정의한 값은 런타임에서 정의된 값을 설정할 수 없다.
const d = DateTime.now(); // Error: Compilation failed.
코드를 실행시켜보면 final의 경우 런타임에서 호출될 때마다 결과 값이 달라지는 것을 확인할 수 있지만 const의 경우 컴파일 에러가 나오는 것을 확인할 수 있다.
Collections
Dart에서는 List, Map, Set 등의 Collection을 제공하지만 배열의 자료구조는 제공하지 않는다.
// List
List<int> setNums = [1,2,3];
var nums2 = [1, 2, 3]; // 타입 추론
// set
Set<int> setNums = {1,2,3,4,4,4,4};
var setNums2 = {1,5,5,5,5,5,5,5,2,2,2}; // 타입 추론
// map
Map<String, int> mapNums = {
"key" : 1,
"key2" : 2
};
var mapNums2 = { //타입 추론
"key" : 1,
"key2" : 2
};
var nums = {
}
nums.addAll(['승준', '우용', '지석', '선재']); // Error: Compilation failed.
nums['세환'] = '민석';
nums['승주'] = '현수';
여기서 주의할 점은 var를 통해 set과 map을 선언할 때 {} 안에 아무것도 작성하지 않는다면 var는 map 타입으로 추론하게 된다. (Map<dynampic, dynamic> 으로 인식하기 때문.)
스프레드 연산자
스프레드 연산자 . . . 는 collection을 펼쳐주거나 다른 collection을 삽입할 때 사용된다.
코드를 작성해보면서 확인하면 좋을 것 같다.
// 스프레드 연산자(...)
var e = [1,2,3];
var f = [...e, 4, 5];
함수 선언
함수를 선언할 때 함수의 형태는 => 함수명 (매개변수){ retrun 반환 값; }으로 정의된다.
매개변수의 타입은 dynamic으로 추론되기 때문에 자료형을 명시할 필요 없다.
addNumber(num1, num2){
return num1 + num2;
}
print(addNumber(1, 3)); // 출력 : 4
print(addNuver("ab","cd")); // 출력 : abcd
선택 매개변수
선택 매개변수는 함수를 정의할 때 {}로 감싼 매개변수를 선택적으로 사용할 수 있게끔 하는 것이다. 이런 매개변수를 Named Parameter라고도 불린다.
addNumber(num1, {num2}){
return num1 + num2;
}
print(addNumber(1, num2 : 3)); // 출력 : 4
print(addNumber(num2 : 3)); // error : 필수 매개변수를 작성하지 않음.
print(addNumber(1)); // error : num2 = null 이기 때문에 연산을 하지 못함.
선택 매개변수를 작성하지 않을 때 null 값이 아닌 다른 값을 반환하고 싶으면 default 값을 주면 된다.
addNumber(num1, {num2 = 3}){
return num1 + num2;
}
print(addNumber(1)); // 츨력 : 4
이렇게 선택 매개변수는 호출할 때 매개변수명을 함께 써주기 때문에 가독성을 높여준다.
비동기 처리(Future, async, await)
개발을 하다 보면 기능을 백그라운드에서 실행해야 하는 경우가 생긴다. 그럴 때마다 쓰는 것이 비동기인데 비동기 처리는 동기 처리가 모두 다 끝난 뒤에 이루어진다. 간단한 프로그램을 구현할 때는 처리 시간이 짧아도 되므로 동기 처리 방식을 사용해도 되지만 프로그램이 무거워수록 비동기 처리 방식은 필수적으로 사용해야 한다.
Future는 어떤 작업 결과값을 나중에 받기로 약속하는 것으로 요청한 작업의 결과를 기다리지 않고 바로 다음 작업을 진행한다. 그 후 작업이 완료되면 결과값을 받는 방식으로 비동기 처리가 실행된다.
Future의 사용 예시)
void main(){
print("시작");
networkRequset();
print("끝");
}
Future networkRequset(){
return Future.delayed(Duration(seconds : 3));
}
위 코드를 보게 되면 Future를 통해 비동기를 요청했고 delayed를 통해 강제적으로 3초를 딜레이 시킨 것을 확인할 수 있다. 그러면 이 비동기를 어떻게 사용해야 할지 asnyc와 await을 통해 알아보자.
- asnyc 함수를 정의하려면 Future 함수에 async를 추가해야 한다.
- await 키워드는 asnyc 함수에서만 사용할 수 있다.
코드를 확인하면서 더 자세히 알아보도록 해보자.
void main(){
print("시작");
networkRequset();
print("끝");
}
Future networkRequset() async{
await Future.delayed(Duration(seconds : 1));
print("1");
await Future.delayed(Duration(seconds : 1));
print("2");
await Future.delayed(Duration(seconds : 1));
print("3");
await Future.delayed(Duration(seconds : 1));
print("4");
return;
}
위 코드를 보게 되면 async로 Future 함수를 감싼 것을 확인할 수 있고 await 키워드를 통해 Future를 다시 호출해 1초씩 딜레이 시키고 print()가 실행되는 것을 확인할 수 있다. await이 붙은 코드가 완료되기 전까지 함수는 진행되지 않는다.
위 코드를 실행시켜보면 1초씩 딜레이되어 숫자가 출력되는 것을 확인할 수 있다.
클래스
클래스를 선언할 때 클래스의 형태는 => class 클래스명{멤버 변수, 멤버 함수 }으로 정의된다.
void main(){
var student = Person();
student.name = "동현";
print(student.getName()); // 출력 : 동현
}
class Person {
var name;
var age;
getName(){
return name;
}
}
클래스를 사용하기 위해서는 객체를 생성해야 한다. Java에서는 객체 생성 시 new 키워드를 사용하지만 Dart에서는 기본적으로 생략 한다.
Java에서 Private과 같은 역할을 Dart에서는 _ 을 붙여 Private을 정의한다. 또 Java에서 Private은 클래스 안에서만 접근 가능하지만 Dart에서는 클래스가 정의된 파일에서 Private 변수에 접근할 수 있다.
void main(){
var student = Person();
student.name = "동현";
print(student.getName()); // 출력 : 동현종용
}
class Person {
var name;
var age;
var _teacher = "종용";
getName(){
return name + _teacher;
}
}
null safety
null safety란 null에게서 안전한 프로그램 코드를 작성하는 것을 의미한다. 쉽게 생각해서 코드가 실행되면서 예상치 못한 null을 대응하는 것이라고 생각하면 될 것 같다.
null safety는 nullable과 non- nullable로 나뉜다. 이렇게 구분되는 이유는 null 값을 대입할 수 있냐 없냐의 차이로 nullable은 null을 대입할 수 있는 것이고 non- nullable은 null을 대입할 수 없는 것이다. Dart 언어의 변수는 기본적으로 non- nullable로 선언되는 것이며 만약 nullable로 선언하고자 한다면 타입명 뒤에 ? 를 작성해주면 된다.
int g = 10;
g = null; // Error: Compilation failed.
int? h = 10;
h = null; // 성공
위 코드를 보게 되면 변수 g, h 모두 int 타입으로 선언되었지만 h의 경우 int 뒤에 ? 를 작성하였기 때문에 null 값으로도 대입이 가능한 nullable로 선언되었다. null safety는 int 타입뿐만 아니라 모든 타입에 적용된다.
nullable 변수를 non- nullable 변수에 에러 없이 값을 넣을 수 도 있다.
! 를 작성하여 절대 null 값을 넣지 않을 거라는 것을 명시해주면 된다.
var listNums = [1, null, 3];
int k = listNums.first; // Error: Compilation failed.
int k = listNums.first!; // 절때 null 값을 받지 않을거야!
null safety는 몇 가지 규칙이 있다.
- non- nullable 변수는 선언과 동시에 초기값을 주어야 한다. Dart 언어에서 변수를 선언할 때 초기값을 주지 않으면 null로 초기화된다. 하지만 non -nullable 은 null 받을 수 없기 때문에 에러가 나오게 된다. 따라서 non- nullable 변수는 선언과 동시에 초기값을 주어야 한다.
- var 타입은 nullable로 선언할 수 없다. 위에서 설명했듯이 var 타입은 타입 추론이 가능하므로 nullable과 non- nullable 도 자동으로 추론된다.
위 코드를 보게 되면 a1은 non- nullable의 int 타입을 추론된다. a2는 null로 초기화되었으므로 nullable의 dynamic 타입으로 추론된다. a3은 선언과 동시에 초기값을 주지 않았으므로 null로 초기화되고 a2와 같이 nullable의 dynamic 타입으로 추론된다. a4는 var 타입에 null safety를 넣었기 때문에 컴파일 에러가 뜬 것이다.var a1 = 10; // int + non- nullable var a2 = null; // dynamic + nullable var a3; // dynamic + nullable var? a4 = null; // Error: Compilation failed.
- dynamic 타입에서의 null safety는 의미가 없다. dynamic 타입은 대입되는 값을 한정하지 않겠다는 의미로 모든 타입의 데이터가 대입될 수 있다. 이 모든 타입에는 nullable도 포함되므로 dynamic 타입으로 선언되는 것 자체가 nullable로 선언되는 것이다.
dynamic b1 = null; dynamic b2; dynamic? b3; b1 = null; b2 = null; b3 = null;
Dart 문법에는 더 많은 내용이 있지만 기본적인 것과 중요할 것 같은 내용 위주로 줄여서 정리해보았다.
'Develop > Flutter' 카테고리의 다른 글
[Flutter] 플러터 비동기 Future 와 Stream (0) | 2021.12.20 |
---|---|
[Flutter] 플러터 FutureBuilder를 사용한 비동기 처리 (0) | 2021.11.16 |
[Flutter] 플러터 Bottom Navigation Bar 구현 (0) | 2021.11.05 |
[Flutter] 플러터 Stateless Widget 과 Stateful Widget (0) | 2021.11.05 |
[Flutter] 플러터 기본 틀과 메커니즘 (0) | 2021.11.04 |