[Flutter] Isolate 완전 정복 - UI 멈춤 없는 앱 만들기

Noah·2025년 5월 7일
0

Flutter

목록 보기
11/11

Flutter 앱을 개발하다 보면 무거운 연산(예: JSON 파싱, 이미지 처리) 때문에 UI가 멈추는 현상을 겪을 수 있다. 이럴 때 사용하는 것이 바로 Isolate이다.

이 글에서는 Isolate무엇인지, 언제 사용하는지, 어떻게 사용하는지, 그리고 실제 예시까지 정리한다.


🧠 Isolate란?

Flutter는 기본적으로 단일 스레드(UI Thread)에서 실행된다. 따라서 무거운 작업을 하게 되면 UI가 멈추거나 버벅일 수 있다.

이 문제를 해결하기 위해 사용하는 것이 바로 Isolate이다.
Isolate는 Dart의 멀티스레딩 방식이며, 일반적인 스레드와 다르게 작동한다.

🔹 각 Isolate는 독립적인 메모리 공간을 가지며
🔹 서로 데이터를 직접 공유하지 않고, 메시지를 통해 통신한다.


🛠️ Isolate 사용 방법

✅ Isolate.spawn 사용법

복잡하거나 장기 실행 작업에 적합

import 'dart:isolate';

void isolateEntryPoint(SendPort sendPort) {
  // 무거운 작업 실행
  int result = heavyCalculation();
  sendPort.send(result);
}

Future<void> runIsolate() async {
  ReceivePort receivePort = ReceivePort();

  await Isolate.spawn(isolateEntryPoint, receivePort.sendPort);

  // isolate로부터 결과 받기
  int result = await receivePort.first;
  print("결과: $result");
}

✅ Isolate.run 사용법

간단한 비동기 작업 실행에 적합

import 'dart:isolate';

// 무거운 작업 함수
int heavyCalculation() {
  // 예: 연산이 많은 계산
  int sum = 0;
  for (int i = 0; i < 100000000; i++) {
    sum += i;
  }
  return sum;
}

Future<void> runIsolate() async {
  // Isolate.run을 사용하여 작업 실행
  int result = await Isolate.run(() => heavyCalculation());
  print("결과: $result");
}

⚠️ Isolate.run 은 Dart 3.0 이상부터 사용가능

✅ compute 함수 사용법

간단한 연산 처리 (JSON 파싱 등)

import 'package:flutter/foundation.dart';

int heavyTask(int value) {
  return value * 2;
}

Future<void> useCompute() async {
  int result = await compute(heavyTask, 10);
  print("결과: $result");
}

⚠️ compute 는 함수와 파라미터 모두 직렬화 가능해야 하며, 파라미터는 하나만 받을 수 있다.

사용법 비교

항목Isolate.spawnIsolate.runcompute (Flutter 전용)
도입 시기오래전부터 존재Dart 3.0부터 도입Flutter에서 기본 제공
용도복잡하거나 장기 실행 작업에 적합간단한 비동기 작업 실행에 적합간단한 연산 처리 (JSON 파싱 등)
코드 구조ReceivePort / SendPort 필요async/await로 결과 처리 용이Future 기반으로 간단히 처리 가능
리턴 타입직접 반환 없음 (포트로 수동 전달)Future<T>로 직접 결과 반환Future<R> 형태로 결과 반환
코드 위치반드시 top-level 함수 or static 함수 필요클로저, 익명 함수 사용 가능top-level 함수만 허용
리소스 관리수동으로 종료 필요 (kill(), exit())자동으로 isolate 종료내부적으로 리소스 자동 관리
플랫폼 제한Dart 전반에서 사용 가능Dart (콘솔, 서버, Flutter 등)Flutter 프레임워크에서만 사용 가능

📦 사용 예제

1. 대용량 JSON 파싱

Future<List<User>> parseJson(String jsonString) async {
  return compute(_parseAndDecode, jsonString);
}

List<User> _parseAndDecode(String responseBody) {
  final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
  return parsed.map<User>((json) => User.fromJson(json)).toList();
}

2. 이미지 처리

이미지를 필터링하거나 리사이징하는 작업에도 Isolate를 사용할 수 있다.

import 'dart:isolate';
import 'dart:io';
import 'dart:typed_data';

void imageProcessingIsolate(SendPort sendPort) async {
  // 메인 Isolate에서 작업 요청을 받을 포트
  final port = ReceivePort();

  // 메인 Isolate에게 자신의 포트를 전달
  sendPort.send(port.sendPort);

  // 요청 대기
  await for (final message in port) {
    // message 구조: [Uint8List imageBytes, SendPort replyPort]
    final Uint8List imageBytes = message[0];
    final SendPort replyPort = message[1];

    // 예시: 이미지의 크기를 단순히 구하는 작업 (실제 처리 로직은 더 복잡할 수 있음)
    final imageSize = imageBytes.length;

    // 결과 전송
    replyPort.send('이미지 크기: $imageSize bytes');

    // 작업이 한 번만 필요한 경우 아래로 종료
    // port.close(); // 반복 수신이 필요 없다면 사용
  }
}
  • ReceivePort를 생성하여 메인 Isolate로부터 메시지를 받을 준비를 함.
  • 메인 Isolate에게 자신의 SendPort를 전달.
  • 메인 Isolate가 보내는 메시지(보통은 [작업 데이터, 응답용 SendPort] 형태)를 기다림.
  • 메시지를 처리한 뒤, 결과를 replyPort를 통해 메인으로 다시 보냄.
  • 이 구조는 양방향 통신이 필요한 Isolate 패턴에서 많이 사용됨.

⚠️ 주의할 점

  • Isolate 간 메모리는 공유되지 않는다. 데이터를 보낼 때는 직렬화 가능한 형태로 전송해야 한다.
  • compute 는 간단한 작업에만 적합하다. 복잡한 작업에는 직접 Isolate를 생성하여 사용해야 한다.
  • Isolate 는 리소스를 소비하므로 남용하지 않아야 하며, 재사용 혹은 적절한 관리가 필요하다.

✅ 정리

항목내용
Isolate란?독립된 메모리 공간에서 실행되는 Dart의 멀티스레드 방식
왜 사용하나?UI 스레드를 차단하지 않기 위해
언제 사용하나?JSON 파싱, 이미지 처리, 암호화 등 무거운 연산이 필요한 경우
어떻게 사용하나?Isolate.spawn, ReceivePort, 혹은 Isolate.run 또는 compute() 활용

Flutter에서 Isolate 를 적절히 활용하면 앱의 부드러운 UX와 성능 향상을 모두 달성할 수 있다. 무조건 사용하는 것이 아니라, 적절한 시점과 상황에서 사용하는 것이 중요하다.

🔗 참고 링크

profile
Flutter Specialist

0개의 댓글