Flutter 캠프 12일차

은성·2025년 12월 9일

Flutter 본캠프

목록 보기
13/57

오늘의 내용

모닝스터디 , 동기 프로그래밍 , 비동기 프로그래밍 , 과제 풀이

모닝스터디

  // 2단 부터 9단까지
  for (int j = 1; j <= 9; j++) {
    mulTable(j);
  }

  mulTable(3);
  mulTable(7);
}

/// 특정 단수의 구구단을 출력하는 함수!
///
/// [number] : 출력할 단 번호
///
void mulTable(int number) {
  // ===== 함수 시작 =====
  for (int i = 1; i <= 9; i++) {
    print('$number * $i = ${number * i}');
  }
  // ===== 함수 끝 =====

  /// 회문 판별
///
/// 회문 : 글자를 앞에서 읽으나 뒤에서 읽으나 같은 글자
///
/// ex) level, eye, kayak, noon, reviver
///
void main() {
  // print(isPalindrome("level"));
  print(isPalindrome("reviver"));
  // print(isPalindrome("test"));
}

/// 문자열을 입력받아서
///
/// 회문(Palinderome)이면 true
///
/// 아니면 false
///
/// 반환하는 함수
bool isPalindrome(String str) {
  // ====== 함수 시작 =====
  // "level"
  // 1. 문자열을 한글자씩 새로운 리스트에 뒤집어서 담기 -> 뒤집은 문자의 리스트와 기존 리스트 비교
  //    => "level"이라는 문자열의 경우 5 + 5 번의 반복이 이루어짐
  //    => 한글자씩 담긴 리스트 (길이 5) + 뒤집은 문자열 담은 리스트 (길이 5) => 길이 10정도 필요

  // 2. 문자열 한글자씩 새로운 리스트에 담기 -> 리스트 반복하면서 첫글자와 끝글자 비교
  //    => 반복문에서 i 번째 글자와 (리스트 길이 - i - 1)  번째 글짜 비교
  //    => "level"이라는 문자열의 경우 = 2번의 반복이 이루어짐
  //    => 한글자씩 담긴 리스트 (길이 5) => 길이 5정도 필요
  //  공간복잡도 : 메모리 공간을 얼마나 차지하는지 (RAM과 관련)
  //  시간복잡도 : 얼마나 걸렸는지 (CPU와 관련)

  // 2번 방법으로 진행
  List<String> charList = str.split(''); // 한 글자씩 분리하고 싶을때는 빈 문자열 "split"
  int halfIndex = charList.length ~/ 2; // 2로 나눴을 때 나머지
  for (var i = 0; i < halfIndex; i++) {
    // i => 0
    // 첫글자, 끝글자
    // reviver
    String char1 = charList[i];
    int endIndex = charList.length - 1 - i;
    String char2 = charList[endIndex];
    if (char1 != char2) {
      return false;
    }
  }
  return true; // 끝까지 비교하기 위해 if문 밖에 true 씀
  // ===== 함수 끝 =====
}

동기 프로그래밍

  • 작업이 완료될 때까지 기다렸다가 결과가 나오면 값을 반환한다.
  • 결과값이 나올 때까지 다른 모든 연산이 멈추기 때문에 다른 작업을 할 수 없음
    파일로부터 데이터를 읽을 때, 데이터 베이스에 데이터를 쓸 때, 네트워크를 통해 데이터를 불러올 때
void introduce(String name) {
	print('자기소개 시작 !');
	print('안녕 ? 나는 $name ~');
	print('자기소개 완료 !');
}

void main() {
	introduce('강미래');
	introduce('강현재');
}

/*
자기소개 시작 !
안녕 ? 나는 강미래 ~
자기소개 완료 !
자기소개 시작 !
안녕 ? 나는 강현재 ~
자기소개 완료 !
*/

무조건 introduce(’강미래’);introduce(’강현재’);introduce(’강과거’);
순서로 실행된답니다 !

비동기 프로그래밍

  • 작업이 완료될 때까지 기다리지 않고, 미래의 특정 시점에 값을 반환

  • 결과값이 나올 때까지 멈춰 있지 않고, 수행할 수 있는 다른 작업을 찾아서 수행하기 때문에 작업 진행 중 다른 작업이 가능하다.

  • 시간이 오래 걸리는 작업을 처리할 때 사용

  • Dart로도 라이브러리를 사용해서 비동기 할 수 있음
    dart:async에 있는 Future,Stream 같은 클래스를 통해 비동기 프로그래밍 지원

Future

  • 제너릭 클래스
  • 비동기 프로그래밍 중 대표적인 클래스
    (dart:async에 포함되어 있음)
  • 원래는 Future 클래스를 사용하려면 impor 'dart:async'; 를 해야 하지만, dart:coreFuture 클래스를 사용할 수 있도록 해주기 때문에 따로 dart:asyncimport 하지 않아도 됨

Future<int> number = Future.value(1);
Future<String> name = Future.value('강미래');
Future<bool> isOddNumber = Future.value(1.isOdd);

Future<int> 는 우리가 알고 있는 int,
Future<String> 은 우리가 알고 있는 String,
Future<bool> 은 우리가 알고 있는 bool 이랑 같은데
단지 Future 가 감싸고 있을 뿐이라고 생각하면 돼요

사용

  • 하나의 작업에 대해 값이나 이벤트가 한 번 발생하는 단일 비동기 작업에서 사용

Future.delayed(Duration(seconds: [지연 시간]));

void main() {
	int seconds = 2;
	print('실행 시작 !');
	Future.delayed(Duration(seconds: seconds));
  print('실행 끝 !');
}

/*
실행 시작 !
실행 끝 !
*/

Future.delayed(Duration(seconds: [지연 시간]), () {[지연 시간 후의 동작]});

void main() {
	int seconds = 2;
	print('실행 시작 !');
	Future.delayed(Duration(seconds: seconds), () {
    print('$seconds초 다 기다림 !');
  });
  print('실행 끝 !');
}

/*
실행 시작 !
2초 다 기다림 !
실행 끝 !
*/

기본적인 비동기 코드

Future.delayed()

void introduce(String name) {
	print('$name의 자기소개 시작 !');
  
  Future.delayed(Duration(seconds: 2), () {
    print('안녕 ? 나는 $name ~');
  });
	
	print('$name의 자기소개 끝 !');
}

void main() {
  introduce('강미래');
}

/*
강미래의 자기소개 시작 !
강미래의 자기소개 끝 !
안녕 ? 나는 강미래 ~
*/

2초 기다리는 동안 강미래의 자기소개 끝 ! 이 출력되는 것을 통해
2초 기다리는 동안 멈춰 있지 않고 다른 연산이 실행되는 것을 알 수 있어요 👀
만약 이 코드가 동기적으로 동작했다면 2초동안 멈춰 있었겠죠 ?

응용 작업

void introduce(String name) {
	print('$name의 자기소개 시작 !');
  
  Future.delayed(Duration(seconds: 2), () {
    print('안녕 ? 나는 $name ~');
  });
	
	print('$name의 자기소개 끝 !');
}

void main() {
  introduce('강미래');
  introduce('강현재');
}

/*
강미래의 자기소개 시작 !
강미래의 자기소개 끝 !
강현재의 자기소개 시작 !
강현재의 자기소개 끝 !
안녕 ? 나는 강미래 ~
안녕 ? 나는 강현재 ~
*/

2초 기다리는 동안 수행할 수 있는 다른 작업을 찾아서 수행하는군요 🤭 (제법 부지런해 ~)

void introduce(String name) async {
	print('$name의 자기소개 시작 !');
  
  await Future.delayed(Duration(seconds: 2), () {
    print('안녕 ? 나는 $name ~');
  });
	
	print('$name의 자기소개 끝 !');
}

void main() {
  introduce('강미래');
}

/*
강미래의 자기소개 시작 !
안녕 ? 나는 강미래 ~
강미래의 자기소개 끝 !
*/

가능해요 !!
비동기적인 코드를 동기적인 코드처럼 순서대로 실행되도록 하고 싶은 경우에는
비동기적인 코드 앞에 await 를 붙이고,
await 를 붙인 코드를 포함하는 함수에 async 를 붙이면 돼요 🙂
참고로, await 는 Future 류의 코드 앞에만 붙일 수 있어요.
asyncawait 는 비동기적인 코드를 동기적인 코드처럼 보이게 만든답니다.

void introduce(String name) async {
	print('$name의 자기소개 시작 !');
  
  await Future.delayed(Duration(seconds: 2), () {
    print('안녕 ? 나는 $name ~');
  });
	
	print('$name의 자기소개 끝 !');
}

void main() {
  introduce('강미래');
  introduce('강현재');
}

/*
강미래의 자기소개 시작 !
강현재의 자기소개 시작 !
안녕 ? 나는 강미래 ~
강미래의 자기소개 끝 !
안녕 ? 나는 강현재 ~
강현재의 자기소개 끝 !
*/

아뇨 !! 그러면 비동기 프로그래밍 이 아니라 동기 프로그래밍 이었겠죠 !!
이 코드의 실행 결과를 보면 2초 기다리는 동안 멈춰 있지 않다는 것을 알 수 있어요 🤭
2초 기다리는 동안 다음 작업인 introduce('강현재'); 가 실행되고 있거든요 ~~

Future<void> introduce(String name) async {
	print('$name의 자기소개 시작 !');
  
  await Future.delayed(Duration(seconds: 2), () {
    print('안녕 ? 나는 $name ~');
  });
	
	print('$name의 자기소개 끝 !');
}

void main() async {
  await introduce('강미래');
  await introduce('강현재');
}

/*
강미래의 자기소개 시작 !
안녕 ? 나는 강미래 ~
강미래의 자기소개 끝 !
강현재의 자기소개 시작 !
안녕 ? 나는 강현재 ~
강현재의 자기소개 끝 !
*/

이렇게 하면 작성한 순서대로 실행된답니다 !
마찬가지로, 2초 기다리는 동안 멈춰 있지 않고 수행할 수 있는 다른 작업이 있다면
그 작업을 수행해요. 왜냐 ? 비동기적으로 동작하니까 ~~
await 는 Future 류의 코드 앞에만 사용할 수 있다고 했기 때문에
introduce() 의 반환 타입을 Future<void> 로 바꿔 주어야 하고,
await 를 포함하는 함수에는 반드시 async 를 붙여야 한다고 했기 때문에
main()async 를 붙여 주어야 해요.
introduce() 는 반환값이 없기 때문에 (return 구문이 없기 때문에)
Future<void> 타입으로 해주는 것이겠죠 🙂
async 함수 안에 await 가 여러 번 있어도 돼요.

변환값이 있는 형태

Future<String> introduce(String name) async {
	print('$name의 자기소개 시작 !');
  
  await Future.delayed(Duration(seconds: 2), () {
    print('안녕 ? 나는 $name ~');
  });
	
	print('$name의 자기소개 끝 !');
  
  return '이름은 $name ~';
}

void main() async {
  var riverfuture = await introduce('강미래');
  var riverpresent = await introduce('강현재');
  print('나의 $riverfuture 너의 $riverpresent');
}

/*
강미래의 자기소개 시작 !
안녕 ? 나는 강미래 ~
강미래의 자기소개 끝 !
강현재의 자기소개 시작 !
안녕 ? 나는 강현재 ~
강현재의 자기소개 끝 !
나의 이름은 강미래 ~ 너의 이름은 강현재 ~
*/

그럼요 !!
Future<타입> 으로 반환 타입을 바꾸고, 그 반환 타입에 맞는 return 구문만 써주면 끝 !!

Future에 한계

  • 하나의 작업당 결과값을 1번만 받을 수 있음

Stream

  • 제네릭 클래스
  • 비동기 프로그래밍에서 사용되는 대표적인 클래스
  • Dart SDK 표준 라이브러리인 dart:async에 포함된 클래스
  • 원래는 Stream 클래스를 사용하려면 import 'dart:async'; 를 해야 하지만,dart:core 가 Stream 클래스를 사용할 수 있도록 해주기 때문에
    따로 dart:async 를 import 하지 않아도 됨
  • 시간에 따라 연속적인 데이터 흐름을 제공
  • 한번에 하나의 값이 아닌 여러 값을 비동기적으로 받을 수 있음
  • 결과값을 한 번 반환하고 나면 실행이 종료되는 Future 와 다르게
    직접 실행을 종료해 주어야 실행이 종료되고, 실행을 종료해 주지 않으면 계속 실행돼요

결과값의 타입은 Stream

Steam<int> number = Steam.value(1);
Steam<String> name = Steam.value('강미래');
Steam<bool> isOddNumber = Steam.value(1.isOdd);

Steam<int> 는 우리가 알고 있는 int,
Steam<String> 은 우리가 알고 있는 String,
Steam<bool> 은 우리가 알고 있는 bool 이랑 같은데
단지 Steam 이 감싸고 있을 뿐이라고 생각하면 돼요 🙂

사용할 때?

-하나의 작업에 대해 값이나 이벤트가 여러번 발생하는 경우
사용자가 버튼 누를때마다 그 이벤트를 받아오는 경우

  • 비동기 연산의 결과값이 여러 번 반환되는 경우 값을 순차적으로 받기 위함

yield

  • 값을 방출하도록 하는 키워드
Stream<String> emitNames() async* {
  yield '강미래';
  yield '강현재';
  yield '강과거';
}

Future 를 반환 타입으로 갖는 함수는 async 를 사용했지만,
Stream 을 반환 타입으로 갖는 함수는 async* 을 사용해요.
이때 yield 옆에 있는 값의 타입이랑 Stream<타입> 에 있는 타입 이 같아야겠죠 ?
참고로, Stream 에서는 반환 대신 방출 이라는 단어를 사용한답니다 !

Stream<String> emitNames(List<String> names) async* {
  for (var i = 0; i < names.length; i++) {
    yield '${i + 1}번째는 ${names[i]} ~';
  }
}

매개변수나 반복문을 사용해서 방출할 수도 있어요 🤩

  • 함수와 메서드에서 사용하는 return과 같은 개념

listen()

  • 방출되는 소리를 듣고 있다는 뜻
  • yald를 통해 방출되는 값을 받기 위해 사용하는 메서드
Stream<String> emitNames() async* {
  yield '강미래';
  yield '강현재';
  yield '강과거';
}

void main() {
  int number = 1;
  
  emitNames().listen((name) {
    print('$number번째는 $name ~');
    number += 1;
  });
}

/*
1번째는 강미래 ~
2번째는 강현재 ~
3번째는 강과거 ~
*/

emitName() 에서 yield 를 통해 방출되는 값들이
순서대로 하나씩 listen 의 매개변수인 name 에 들어가요.

Stream<String> emitNames(List<String> names) async* {
  for (var i = 0; i < names.length; i++) {
    yield '${i + 1}번째는 ${names[i]} ~';
  }
}

void main() {
	List<String> names = ['강미래', '강현재', '강과거'];
  emitNames(names).listen((element) {
    print(element);
  });
}

/*
1번째는 강미래 ~
2번째는 강현재 ~
3번째는 강과거 ~
*/

10에서 0 까지의 정수를 출력하는 기본적인 비동기 코드

Stream<int> emitNumbers(int first) async* {
  for (var i = first; i >= 0; i--) {
    yield i;
  }
}

void main() {
  emitNumbers(10).listen((number) {
    print(number);
  });
}

/*
10
9
8
7
6
5
4
3
2
1
0
*/

main()await 가 포함되어 있지 않으니까 async 를 써줄 필요가 없겠죠 ?
지금까지는 for 문의 증감식에 i++ 만 썼지만 .. 응용해 보았습니다 ! 🧐

Stream<int> emitNumbers(int first) async* {
  for (var i = first; i >= 0; i--) {
    yield i;
  }
}

void main() {
  print('숫자 놀이 시작 !');
  emitNumbers(10).listen((number) {
    print(number);
  });
  print('숫자 놀이 끝 !');
}

/*
숫자 놀이 시작 !
숫자 놀이 끝 !
10
9
8
7
6
5
4
3
2
1
0
*/

No !! 비동기적으로 동작한다구요 ~~
emitNumbers() 가 실행되기 전에 숫자 놀이 끝 ! 이 출력되는 것을 보면
그렇지 않다는 것을 알 수 있죠 😅

  • 1초에 한개씩 출력하고 싶을때
Stream<int> emitNumbers(int first) async* {
  for (var i = first; i >= 0; i--) {
    yield i;
    
    await Future.delayed(Duration(seconds: 1));
  }
}

void main() {
  emitNumbers(10).listen((number) {
    print(number);
  });
}

/*
10
9
8
7
6
5
4
3
2
1
0
*/

물론이죠 ~~ 아까 배웠던 Future.delayed() 를 사용하면 된답니다 👍🏼
yield i; 를 통해 i 를 방출하고 나서 1초를 기다리겠네요 !
emitNumbers() 옆에 async* 이 있기 때문에 함수 안에 await 가 있을 수 있겠네요 !! 여러분은 방금 타이머를 만드셨습니다 ㅋㅋ 1초에 숫자가 1씩 감소하고 있으니까 ~~

Stream<int> emitNumbers(int first) async* {
  for (var i = first; i >= 0; i--) {
    yield i;
    
    await Future.delayed(Duration(seconds: 1));
  }
}

void main() {
  emitNumbers(10).listen((number) {
    print('[타이머 1] $number');
  });
  
  emitNumbers(10).listen((number) {
    print('[타이머 2] $number');
  });
}

/*
[타이머 1] 10
[타이머 2] 10
[타이머 1] 9
[타이머 2] 9
[타이머 1] 8
[타이머 2] 8
[타이머 1] 7
[타이머 2] 7
[타이머 1] 6
[타이머 2] 6
[타이머 1] 5
[타이머 2] 5
[타이머 1] 4
[타이머 2] 4
[타이머 1] 3
[타이머 2] 3
[타이머 1] 2
[타이머 2] 2
[타이머 1] 1
[타이머 2] 1
[타이머 1] 0
[타이머 2] 0
*/


실행시키면 타이머 1 이랑 타이머 2 가 번갈아 가면서 출력돼요 !
이것을 통해 비동기적으로 동작하고 있다는 것을 알 수 있어요 👀

Stream<int> emitNumbers(int first) async* {
  for (var i = first; i >= 0; i--) {
    yield i;
    
    await Future.delayed(Duration(seconds: 1));
  }
}

void main() {
  emitNumbers(10).where((number) => number.isOdd).listen((number) {
    print('[홀수 타이머] $number');
  });

  emitNumbers(10).where((number) => number.isEven).listen((number) {
    print('[짝수 타이머] $number');
  });
}

/*
[짝수 타이머] 10
[홀수 타이머] 9
[짝수 타이머] 8
[홀수 타이머] 7
[짝수 타이머] 6
[홀수 타이머] 5
[짝수 타이머] 4
[홀수 타이머] 3
[짝수 타이머] 2
[홀수 타이머] 1
[짝수 타이머] 0
*/

방출되는 값에 고차 함수 를 적용할 수도 있답니다 !

과제 시작

과제가 어려워서 제미나이한테 요약해서 포스터로 만들어달라해서 다시 이해하는 시간을 가졌다...

0개의 댓글