์ฑ๋ฅ ์ต์ ํ - const, RepaintBoundary, DevTools
์ฑ์ด ์ปค์ง๋ฉด ์คํฌ๋กค์ด ๋๋ ๋๊ธฐ๊ฑฐ๋ ๋ฒํผ์ ๋๋ ์ ๋ ๋ฐ์์ด ๋ฆ๋ ์ผ์ด ์๊ธด๋ค. "๋๋ฆฐ ๊ฒ ๊ฐ์ผ๋ ์ด๊ฒ์ ๊ฒ ๊ณ ์ณ๋ณด์"๋ ๊ฐ์ฅ ์ํํ ์์์ด๋ค. ๊ณ ์น๋ค๊ณ ์ ๋ง ๋นจ๋ผ์ง๋์ง๋, ์ด๋๊ฐ ๋ฌธ์ ์ธ์ง๋ ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ด๋ค. ์ด ๊ธ์ ์ฒซ ๊ท์น์ ๋จผ์ ์ธก์ ํ๊ณ , ๊ทธ๋ค์์ ๊ณ ์น๋ค์ด๋ค.
ํํ ์ด๋ฐ ์ฝ๋๊ฐ ๋๋ ค์ง๋ ์์์ ์ด๋ค. ์นด์ดํฐ ์ซ์ ํ๋๊ฐ ๋ฐ๋ ๋, ๊ฐ์ ํ๋ฉด์ ๋ฌด๊ฑฐ์ด ๋ถ๋ถ๊น์ง ๊ฐ์ด ๋ค์ ๊ทธ๋ ค์ง๋ ๋ชจ์ต์ด๋ค.
class _HomePageState extends State<HomePage> {
int count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Text('$count'),
ElevatedButton(
onPressed: () => setState(() => count++),
child: const Text('+1'),
),
HeavyChart(), // ๊ทธ๋ํ๋ฅผ ๊ทธ๋ฆฌ๋ ๋ฌด๊ฑฐ์ด ์์ ฏ
ProductList(), // ์ํ ์๋ฐฑ ๊ฐ์ง๋ฆฌ ๋ชฉ๋ก
],
),
);
}
}๋ฒํผ ํ๋๋ฅผ ๋๋ฅผ ๋๋ง๋ค ํ๋ฉด์ด ์ด์ง ๋ฒ๋ฒ ์ธ๋ค. ๋ฐ๋ ๊ฑด ์ซ์ ํ๋๋ฟ์ธ๋ฐ ์์ผ๊น?
์์ธ: setState๊ฐ ์ด State์ build ์ ์ฒด๋ฅผ ๋ค์ ์คํํ๋ค
setState๋ ๊ฐ์ด ๋ฐ๋ Text๋ง ๋ค์ ๊ทธ๋ฆฌ๋ ๊ฒ ์๋๋ผ ์ด State์ build๋ฅผ ํต์งธ๋ก ๋ค์ ์คํํ๋ค. ๊ทธ๋ฌ๋ฉด ์์ ์ ํ HeavyChart(), ProductList()๋ ์๋ก ๋ง๋ค์ด์ง๋ค. ๋ถ๋ชจ๊ฐ ๋ค์ ๊ทธ๋ ค์ง๋ฉด ์์ ์์ ฏ์ const๋ก ํ์ํ๊ฑฐ๋ ๋ณ๋๋ก ๋ถ๋ฆฌํ์ง ์์ ์ด์ ๊ฐ์ด ๋ค์ ๋ง๋ค์ด์ง๋ค. ์์ ฏ์ ๋ง๋๋ ๊ฒ ์์ฒด๋ ๊ฐ๋ณ์ง๋ง, ๋ฌด๊ฑฐ์ด ์์ ฏ์ด ๋ค์ ๊ทธ๋ ค์ง๋ ์ผ์ด ๋ฐ๋ณต๋๋ฉด ๋์ ๋ณด์ด๋ ๋๊น์ด ๋๋ค.
1๋จ๊ณ: ์ธก์ ๋ถํฐ ํ๋ค - profile ๋ชจ๋์ DevTools
์ฒ์์ ๋ฐ๋์ ์์์ผ ํ ํจ์ ์ด ์๋ค. ๋๋ฒ๊ทธ ๋ชจ๋๋ ์ผ๋ถ๋ฌ ๋๋ฆฌ๊ฒ ๋์๊ฐ๋ค. ์๋ฌ๋ฅผ ์ก์์ฃผ๋ ๊ฒ์ฌ๊ฐ ์๋ฉ ์ผ์ ธ ์์ด์, ๋๋ฒ๊ทธ๋ก ์คํํ ์ฑ์ ์๋๋ฅผ ๊ทธ๋๋ก ๋ฏฟ์ผ๋ฉด ์ ๋๋ค. ์ฑ๋ฅ์ ์ค์ ๊ธฐ๊ธฐ์์ profile ๋ชจ๋๋ก ์ฐ๋ค.
flutter run --profile๊ทธ๋ค์ Flutter DevTools๋ฅผ ์ฐ๋ค. ์ธ ๋งํ ๋๊ตฌ๋ ์ด ์ ๋๋ค.
- Performance(ํ๋ ์ ์ฐจํธ): ํ๋ ์๋ง๋ค ๊ทธ๋ฆฌ๋ ๋ฐ ๊ฑธ๋ฆฐ ์๊ฐ์ ๋ง๋๋ก ๋ณด์ฌ์ค๋ค. ๋ง๋๊ฐ ํ๊ณ์ ์ ๋์ ํ๋ ์์ด ๋๊น์ ์์ธ์ด๋ค
- Rebuild Stats(๋ค์ ๊ทธ๋ ค์ง ํ์): ์ด๋ค ์์ ฏ์ด ๋ช ๋ฒ ๋ค์ ๋ง๋ค์ด์ก๋์ง ์๋ ค์ค๋ค
- CPU Profiler: ์ด๋ค ํจ์๊ฐ ์๊ฐ์ ์ฐ๋์ง ๋ณธ๋ค
- Memory: ๋ฉ๋ชจ๋ฆฌ๊ฐ ๊ณ์ ๋์ด๋๋์ง ๋ณธ๋ค
๊ณ ์น๊ธฐ ์ ์ "์คํฌ๋กคํ๋ ๋์ ํ๋ ์ ์๊ฐ" ๊ฐ์ ๊ธฐ์ค ํ๋๋ฅผ ์ ํด์ ์ฌ๋๊ณ , ๊ณ ์น ๋ค ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ๋ค์ ์ฐ๋ค. ์ซ์๊ฐ ์ค์๋์ง๋ก ํ๋จํด์ผ ํ๋ค.
์ฝ๋์์ ๋ฐ๋ก ํ์ธํ๋ ค๋ฉด ๋ค์ ๋ง๋ค์ด์ง ์์ ฏ์ ์ฝ์์ ์ฐ๊ฒ ํ ์๋ ์๋ค.
import 'package:flutter/widgets.dart';
void main() {
debugPrintRebuildDirtyWidgets = true; // ๋ค์ ๊ทธ๋ ค์ง๋ ์์ ฏ์ ์ฝ์์ ์ถ๋ ฅ
runApp(const MyApp());
}2๋จ๊ณ: const๋ก ๋ค์ ๋ง๋ค์ด์ง๋ ๊ฑธ ๋ง๋๋ค
๊ฐ์ฅ ์ฝ๊ณ ํจ๊ณผ๊ฐ ํฐ ๋ฐฉ๋ฒ์ด๋ค. ์ฒซ ๊ธ์์ const๋ "์ปดํ์ผ ๋ ๊ฐ์ด ๊ณ ์ ๋๋ ๊ฒ"์ด๋ผ๊ณ ํ๋ค. ์์ ฏ์ const๋ฅผ ๋ถ์ด๋ฉด Flutter๊ฐ ์ด๋ฏธ ๋ง๋ ๊ฐ์ ๊ฐ์ฒด๋ฅผ ์ฌ์ฌ์ฉํ๊ณ , ๋ถ๋ชจ๊ฐ ๋ค์ ๊ทธ๋ ค์ ธ๋ ์ด ์์ ฏ์ ๊ฑด๋๋ฆฌ์ง ์๋๋ค.
Column(
children: [
Text('$count'), // count๊ฐ ๋ฐ๋๋ const ๋ถ๊ฐ
ElevatedButton(
onPressed: () => setState(() => count++),
child: const Text('+1'), // ์ ๋ฐ๋๋ const
),
const HeavyChart(), // const๋ก ์ฌ์ฌ์ฉ
const ProductList(),
],
)const๋ฅผ ๋ถ์ด๋ ค๋ฉด ์์ ฏ ํด๋์ค์ ์์ฑ์๋ const์ฌ์ผ ํ๋ค.
class HeavyChart extends StatelessWidget {
const HeavyChart({super.key}); // const ์์ฑ์
@override
Widget build(BuildContext context) { /* ... */ }
}const๋ ์ง์ ์ธ์ฐ๊ธฐ๋ณด๋ค ๋ฆฐํธ์ ๋์์ ๋ฐ๋ ๊ฒ ๋ซ๋ค. flutter_lints์๋ prefer_const_constructors ๊ท์น์ด ์์ด์, ๋ถ์ผ ์ ์๋ ๊ณณ์ const๋ฅผ ์ ๋ถ์ด๋ฉด ์๋ํฐ๊ฐ ์๋ ค์ค๋ค. analysis_options.yaml์ ๋ฆฐํธ๊ฐ ์ผ์ ธ ์๋์ง ํ์ธํด ๋๋ค.
3๋จ๊ณ: setState์ ๋ฒ์๋ฅผ ์ขํ๋ค
const๋ฅผ ๋ถ์ผ ์ ์๋ ๊ฒฝ์ฐ(๊ฐ์ด ๋ฐ๋๋ ์์ ฏ)์๋ ๋ฐ๋๋ ๋ถ๋ถ๋ง ๋ณ๋ ์์ ฏ์ผ๋ก ๋ถ๋ฆฌํ๋ค.
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Column(
children: [
CounterSection(), // ์ซ์์ ๋ฒํผ๋ง ์ํ๋ฅผ ๊ฐ์ง๋ค
HeavyChart(),
ProductList(),
],
),
);
}
}
class CounterSection extends StatefulWidget {
const CounterSection({super.key});
@override
State<CounterSection> createState() => _CounterSectionState();
}
class _CounterSectionState extends State<CounterSection> {
int count = 0;
@override
Widget build(BuildContext context) {
return Row(
children: [
Text('$count'),
ElevatedButton(
onPressed: () => setState(() => count++),
child: const Text('+1'),
),
],
);
}
}์ด์ setState๊ฐ CounterSection์ build๋ง ๋ค์ ์คํํ๋ค. HeavyChart์ ProductList๋ ๊ฑด๋๋ฆฌ์ง ์๋๋ค. ์ํ ๊ด๋ฆฌ ๊ธ๋ค์์ Consumer, BlocBuilder, ref.watch๋ฅผ ํ์ํ ์์ ฏ ๊ฐ๊น์ด์ ๋๋ผ๊ณ ํ ๊ฒ๋ ๊ฐ์ ์ด์ ๋ค. ์ํ ๊ด๋ฆฌ ๋๊ตฌ์์๋ ์ผ๋ถ๋ง ๊ตฌ๋
ํ๋ ๋ฐฉ๋ฒ์ด ์๋ค.
// Provider: ์ด ๊ฐ์ด ๋ฐ๋ ๋๋ง ๋ค์ ๊ทธ๋ฆผ
final name = context.select<UserModel, String>((m) => m.name);
// Riverpod
final name = ref.watch(userProvider.select((u) => u.name));
// Bloc: ์กฐ๊ฑด์ด ๋ง์ ๋๋ง ๋ค์ ๊ทธ๋ฆผ
BlocBuilder<UserCubit, UserState>(
buildWhen: (prev, curr) => prev.name != curr.name,
builder: (context, state) => Text(state.name),
)4๋จ๊ณ: ๋ชฉ๋ก์ builder๋ก
์คํฌ๋กค ๋ชฉ๋ก ๊ธ์์ ๋ณธ ๊ท์น์ด๋ค. ListView(children: [...])๋ ํญ๋ชฉ์ ์ ๋ถ ํ๊บผ๋ฒ์ ๋ง๋ ๋ค. ์๋ฐฑ ๊ฐ๋ฉด ์ฒ์ ์ด ๋ ๋๋ฆฌ๊ณ ๋ฉ๋ชจ๋ฆฌ๋ ๋ง์ด ์ด๋ค. ListView.builder๋ ํ๋ฉด์ ๋ณด์ด๋ ๊ฒ๋ง ๋ง๋ ๋ค.
ListView.builder(
itemCount: products.length,
itemBuilder: (context, i) => ProductTile(products[i]),
)ํญ๋ชฉ์ด ์ถ๊ฐ, ์ญ์ , ์ฌ์ ๋ ฌ๋๋ ๋ชฉ๋ก์์๋ ํญ๋ชฉ๋ง๋ค key: ValueKey(product.id)๋ก ์์ ์ ์ธ ํค๋ฅผ ์ค์, ์์น๊ฐ ์๋๋ผ id๋ก ๊ตฌ๋ถํ๊ฒ ํ๋ค. ์ด๋ฏธ์ง๊ฐ ๋ง์ ๋ชฉ๋ก์ ์๋ณธ์ ์ค์ฌ์ ๋ถ๋ฌ์ค๊ณ (Image.network(url, cacheWidth: 300)), ๋คํธ์ํฌ ์ด๋ฏธ์ง๋ ์บ์ํด์ ๊ฐ์ ๊ฑธ ๋ฐ๋ณตํด์ ๋ฐ์ง ์๊ฒ ํ๋ค.
5๋จ๊ณ: RepaintBoundary๋ก ๋ค์ ์น ํ๋ ๋ฒ์๋ฅผ ๊ฐ๋๋ค
Flutter๋ ์์ ฏ์ ๋ค์ ๋ง๋๋ ์ผ(build) ๊ณผ ๋ค์ ์น ํ๋ ์ผ(paint) ์ ๋ฐ๋ก ํ๋ค. ๊ณ์ ์์ง์ด๋ ์์ ์์ ฏ(ํ์ ํ๋ ๋ก๊ณ , ํ๋ฅด๋ ์๊ณ)์ด ์์ผ๋ฉด, ๊ทธ ์ฃผ๋ณ ๋์ ์์ญ์ด ๊ฐ์ด ๋ค์ ์น ํด์ง๋ ์ผ์ด ์๊ธด๋ค. RepaintBoundary๋ก ๊ฐ์ธ๋ฉด ๊ทธ ์์ชฝ๋ง ๋ฐ๋ก ์น ํ๋ ์ธต์ด ์๊ฒจ์ ๋ฐ๊นฅ์ผ๋ก ๋ฒ์ง์ง ์๋๋ค.
Column(
children: [
const RepaintBoundary(
child: Spinner(), // ๊ณ์ ๋์๊ฐ๋ ์์ ฏ
),
const ProductList(), // ์คํผ๋๊ฐ ๋ ๋๋ง๋ค ๊ฐ์ด ๋ค์ ์น ํด์ง์ง ์๋๋ค
],
)ํจ๊ณผ๊ฐ ์๋์ง๋ DevTools์ Flutter Inspector์์ "Highlight Repaints"๋ฅผ ์ผ์ ํ์ธํ๋ค. ๋ค์ ์น ํด์ง๋ ์์ญ์ ์ ํ
๋๋ฆฌ๊ฐ ๊น๋นก์ธ๋ค. ๋ค๋ง ์๋ฌด ๋ฐ๋ ๊ฐ์ธ๋ฉด ์ ๋๋ค. RepaintBoundary๋ ์ธต์ ํ๋ ๋ ๋ง๋ค์ด์ GPU ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ด๋ค. ํ๋กํ์ผ๋ง์ผ๋ก ์์ฃผ ๋ค์ ์น ํด์ง๋ ๋ถ๋ถ์ ํ์ธํ ๊ณณ์๋ง ์ด๋ค.
6๋จ๊ณ: ๋ฌด๊ฑฐ์ด ๊ณ์ฐ์ UI ์ค๋ ๋ ๋ฐ์ผ๋ก
๋น๋๊ธฐ ๊ธ์์ Dart๋ ํ ์ค๋ก ์ผํ๋ ์ด๋ฒคํธ ๋ฃจํ๋ผ๊ณ ํ๋ค. await๋ ๊ธฐ๋ค๋ฆฌ๋ ๋์ ๋ค๋ฅธ ์ผ์ ํ๊ฒ ํด์ฃผ์ง๋ง, CPU๋ฅผ ์ค๋ ์ฐ๋ ๊ณ์ฐ์ await ์์ด ๊ทธ๋๋ก ํ๋ฉด์ ๋ฉ์ถ๋ค. ์ MB์ง๋ฆฌ JSON์ ํ์ฑํ๊ฑฐ๋ ์ด๋ฏธ์ง๋ฅผ ์ฒ๋ฆฌํ๋ ์ผ์ด ๋ํ์ ์ด๋ค.
import 'dart:isolate';
Future<List<User>> parseUsers(String text) {
return Isolate.run(() {
final list = jsonDecode(text) as List;
return list.map((e) => User.fromJson(e as Map<String, dynamic>)).toList();
});
}Isolate.run์ ๊ณ์ฐ์ ๋ณ๋์ ์ค๋ ๋(์์ด์๋ ์ดํธ) ์์ ์คํํ๊ณ ๊ฒฐ๊ณผ๋ง ๋๋ ค์ค๋ค. ๊ทธ๋์ UI๋ ๋ฉ์ถ์ง ์๋๋ค. ๊ฐ๋ณ๊ฒ ์ฐ๋ ค๋ฉด compute๋ ์๋ค. ์์ ์ผ์ ์ฐ๋ฉด ์คํ๋ ค ๋๋ฆฌ๋, ํ๋ ์ ์ฐจํธ์์ ํ ํ๋ ์์ด ๊ธธ๊ฒ ๋งํ๋ ์ง์ ์ด ํ์ธ๋ ๊ณ์ฐ์๋ง ์ด๋ค.
์์๋ฅผ ์ ๋ฆฌํ๋ฉด
- ์ธก์ :
--profile๊ณผ DevTools๋ก ์ด๋๊ฐ ๋ฌธ์ ์ธ์ง ํ์ธํ๋ค - ์ธ๊ณ ํจ๊ณผ ํฐ ๊ฒ๋ถํฐ:
const,setState๋ฒ์ ์ขํ๊ธฐ,builder๋ชฉ๋ก - ๊ทธ๋ค์ ํ์ํ๋ฉด:
RepaintBoundary, ์ด๋ฏธ์ง ์ค์ด๊ธฐ,Isolate - ๋ค์ ์ธก์ : ์ฒ์ ์ฐ ์ซ์์ ๋น๊ตํ๋ค
"์ผ๋จ RepaintBoundary๋ถํฐ ๋๋ฌ๋ณธ๋ค"๋ ์์๋ ๊ฑฐ๊พธ๋ก๋ค. ์ธก์ ์์ด ๊ณ ์น๋ฉด ํจ๊ณผ๊ฐ ์์๋์ง์กฐ์ฐจ ์ ์ ์๋ค.
Compose, SwiftUI์ ๋น๊ตํ๋ฉด
// Compose - ๋ฆฌ์ปดํฌ์ง์
๋ฒ์ ์ขํ๊ธฐ
@Composable
fun HomePage() {
Column {
CounterSection() // ์ํ๋ฅผ ์์ ๋
HeavyChart() // ์
๋ ฅ์ด ์ ๋ฐ๋๋ฉด ๊ฑด๋๋(skip)
}
}- ์ ๋ฐ๋๋ ๋ถ๋ถ ๊ฑด๋๋ฐ๊ธฐ: Flutter๋
const์ ์์ ฏ ๋ถ๋ฆฌ / Compose๋ ์ ๋ ฅ์ด ์ ๋ฐ๋@Composable์ ๋ฆฌ์ปดํฌ์ง์ ์์ ์๋์ผ๋ก ๊ฑด๋๋(์์ ์ฑ์ด ํ์) / SwiftUI๋Equatable๋ทฐ๋ ์์ ๋ทฐ๋ก ๋ถ๋ฆฌ - ์ํ ๋ฒ์: Flutter
setState์select/ ComposederivedStateOf, ์ํ๋ฅผ ์๋๋ก ๋ด๋ฆฌ๊ธฐ / SwiftUI@State๋ฅผ ํ์ํ ๋ทฐ์๋ง - ๋ชฉ๋ก: Flutter
ListView.builder/ ComposeLazyColumn/ SwiftUIList,LazyVStack - ๋๊ตฌ: Flutter DevTools / Android Studio Layout Inspector, Profiler / Xcode Instruments
Compose์ "์
๋ ฅ์ด ์ ๋ฐ๋๋ฉด ๊ฑด๋๋ด๋ค"๋ฅผ Flutter๋ const๋ก ๊ฐ๋ฐ์๊ฐ ์ง์ ํ์ํ๋ค. ์๋์ผ๋ก ๊ฑด๋๋ฐ๋ Compose์ ๋ฌ๋ฆฌ, Flutter์์๋ const๋ฅผ ์ ๋ถ์ด๋ฉด ๊ฑด๋๋ฐ์ง ์๋๋ค๋ ์ ์ด ๊ฐ์ฅ ํฐ ์ฐจ์ด๋ค.
์ ๋ฆฌ
- ์ฑ๋ฅ์ ๊ฐ์ผ๋ก ๊ณ ์น์ง ์๊ณ , ์ค์ ๊ธฐ๊ธฐ์์
flutter run --profile๋ก DevTools์ ํ๋ ์ ์ฐจํธ์ ๋ค์ ๊ทธ๋ ค์ง ํ์๋ฅผ ๋จผ์ ์ธก์ ํ๋ค. ๋๋ฒ๊ทธ ๋ชจ๋๋ ์ผ๋ถ๋ฌ ๋๋ฆฌ๋ค setState๋ ๊ทธ State์build์ ์ฒด๋ฅผ ๋ค์ ์คํํ๋ฏ๋ก, ์ ๋ฐ๋๋ ์์ ฏ์๋const๋ฅผ ๋ถ์ด๊ณprefer_const_constructors๋ฆฐํธ๋ก ๋์น ๊ณณ์ ์ฐพ๋๋คconst๋ฅผ ๋ชป ๋ถ์ด๋ ๋ฐ๋๋ ๋ถ๋ถ์ ๋ณ๋ ์์ ฏ์ผ๋ก ๋ถ๋ฆฌํด์setState์ ๋ฒ์๋ฅผ ์ขํ๊ณ , ์ํ ๊ด๋ฆฌ์์๋select,buildWhen์ผ๋ก ํ์ํ ๊ฐ๋ง ๊ตฌ๋ ํ๋ค- ๊ธด ๋ชฉ๋ก์
ListView.builder๋ก ๋ณด์ด๋ ๊ฒ๋ง ๋ง๋ค๊ณ , ์ด๋ฏธ์ง๋ ํฌ๊ธฐ๋ฅผ ์ค์ฌ์ ๋ถ๋ฌ์จ๋ค - ๊ณ์ ์์ง์ด๋ ์์ ์์ ฏ์ด ์ฃผ๋ณ์ ๊ฐ์ด ๋ค์ ์น ํ๋ฉด
RepaintBoundary๋ก ๊ฐ์ธ๋, ์ธต์ด ๋์ด ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฐ๋ ์ธก์ ์ผ๋ก ํ์ธํ ๊ณณ์๋ง ์ด๋ค - CPU๋ฅผ ์ค๋ ์ฐ๋ ๊ณ์ฐ์
Isolate.run์ด๋compute๋ก UI ์ค๋ ๋ ๋ฐ์์ ํ๊ณ , ๊ณ ์น ๋ค์๋ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ๋ค์ ์ฌ์ ํจ๊ณผ๋ฅผ ํ์ธํ๋ค
๋ค์ ๊ธ๋ถํฐ๋ ๋ง์ง๋ง 6๋จ๊ณ๋ก ๋์ด๊ฐ์, ์ฑ์ด ์๋๋๋ก ๋์ํ๋์ง ์ฝ๋๋ก ํ์ธํ๋ ํ ์คํธ(๋จ์, ์์ ฏ, ํตํฉ)๋ฅผ ๋ค๋ค๋ณด๊ฒ ๋ค.
