AI는 이번 주도 사기다.
부자들이 AI에 돈을 왜 이렇게 투자하는지 이제야 알 것 같다.
claude code를 100달러를 주고 결제했다.
1달 결제인데 이제 앞으로 매 달 결제할 것 같다.
이유는 그냥 개 잘하는 개발자가 나랑 같이 일하는 것 같은 느낌이다.
요즘 코딩을 2가지 방법으로만 한다.
Case1. 회사에서 Cursor로 미친듯이 질문하면서 엄청난 생산성으로 작업
Case2. 퇴근하고 claude code를 활성화해놓고 계속 질문 > 디벨롭 > 디버깅의 반복 / 걍 게임하면서 해도 되고 책 읽으면서 해도됨. 길면 5분동안 돌아가니까.

4~5월에는 여의도의 회사에서 gitlab으로만 일하고.. 거의 퇴근하고 코딩을 안했다. 다 핑계고 놀기만 했기 때문이다..
그러다가 5월에 나오게 되면서 마지막 날 거기 있던 선임 개발자분이 claude code라는 걸로 매일 바이브 코딩한다고 하는데 추천해주신다고 써보라고 하셨다. 바로 앞에서 결제했다. ㅋㅋ
지금 중고차 직거래 + AI 를 만들고있는데 매일 하면 될 것 같다는 확신이 든다.
그리고 써보니 개사기다.. 퇴근하고 코딩이 하고 싶어진다...
공짜로 사업을 하는 느낌이다.
일단 이제 스터디 시작!!
먼저 팰 놈이다.
얘만 상속하면 보이지 않는 애니메이션일 때 작동하지 않아서 불필요하게 리소스를 사용하지 않는다고 한다.
원리가 뭘까?
Ticker는 Flutter의 애니메이션 엔진에서 프레임마다 콜백을 호출하는 객체다.
TickerProviderStateMixin은 이 Ticker들을 생성하고 관리하는 역할을 해요.
프레임마다 ping을 쏜다고 생각하면 된다.
dart
// 이런 식으로 동작한다고 보면 됨
Ticker ticker = Ticker((elapsed) {
print('Ping! 경과시간: $elapsed'); // 매 프레임마다 호출
});
ticker.start(); // ping 시작!
// 16.67ms마다 (60fps) 또는 11.11ms마다 (90fps) ping!
dart
// TickerProviderStateMixin 내부 동작 원리
class _MyWidgetState extends State<MyWidget>
with TickerProviderStateMixin {
@override
Ticker createTicker(TickerCallback onTick) {
return Ticker(onTick, debugLabel: 'created by $this')
..muted = !mounted; // 핵심: mounted 상태에 따라 음소거
}
}
_controller.dispose(); 안해주면 memory leak가 발생한다고함. Ticker가 계속 살아있어서.
왜 자동으로 메모리 관리 안해주는데?
Dart는 수동 메모리 관리 언어가 아니지만, 네이티브 리소스(Ticker는 엔진과 연결됨)는 명시적으로 해제해야 합니다.
class PageA extends StatefulWidget {
@override
_PageAState createState() => _PageAState();
}
class _PageAState extends State<PageA> with TickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
_controller.repeat(); // 계속 돌아가는 애니메이션
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
// 다른 페이지로 이동 → 현재 페이지 dispose() 호출됨
Navigator.push(context, MaterialPageRoute(
builder: (context) => PageB(),
));
},
child: Text('다른 페이지로 이동'),
),
),
);
}
@override
void dispose() {
print('PageA dispose! 애니메이션 중단됨');
_controller.dispose(); // 여기서 Ticker도 자동으로 정리
super.dispose();
}
}
class BackgroundAwareWidget extends StatefulWidget {
@override
_BackgroundAwareWidgetState createState() => _BackgroundAwareWidgetState();
}
class _BackgroundAwareWidgetState extends State<BackgroundAwareWidget>
with TickerProviderStateMixin, WidgetsBindingObserver {
late AnimationController _controller;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this); // 앱 생명주기 관찰
_controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
_controller.repeat();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.paused:
print('앱이 백그라운드로! 애니메이션 자동 중단');
// TickerProviderStateMixin이 알아서 ticker 중단
break;
case AppLifecycleState.resumed:
print('앱이 다시 포그라운드로! 애니메이션 재시작');
// ticker 자동으로 다시 시작
break;
default:
break;
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_controller.dispose();
super.dispose();
}
}
class ConditionalAnimationWidget extends StatefulWidget {
@override
_ConditionalAnimationWidgetState createState() =>
_ConditionalAnimationWidgetState();
}
class _ConditionalAnimationWidgetState extends State<ConditionalAnimationWidget>
with TickerProviderStateMixin {
bool showAnimation = true;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
_controller.repeat();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
ElevatedButton(
onPressed: () {
setState(() {
showAnimation = !showAnimation; // 위젯 숨기기/보이기
});
},
child: Text(showAnimation ? '애니메이션 숨기기' : '애니메이션 보이기'),
),
// 조건부 렌더링 - false일 때 위젯트리에서 완전 제거
if (showAnimation)
AnimatedWidgetChild()
else
Text('애니메이션이 숨겨짐 (위젯트리에서 제거됨)'),
],
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
class AnimatedWidgetChild extends StatefulWidget {
@override
_AnimatedWidgetChildState createState() => _AnimatedWidgetChildState();
}
class _AnimatedWidgetChildState extends State<AnimatedWidgetChild>
with TickerProviderStateMixin {
late AnimationController _childController;
@override
void initState() {
super.initState();
_childController = AnimationController(
duration: Duration(milliseconds: 500),
vsync: this,
);
_childController.repeat(reverse: true);
print('자식 위젯 생성! 애니메이션 시작');
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _childController,
builder: (context, child) {
return Container(
width: 100 + (_childController.value * 50),
height: 100 + (_childController.value * 50),
color: Colors.blue,
);
},
);
}
@override
void dispose() {
print('자식 위젯 dispose! mounted = false, 애니메이션 중단');
_childController.dispose();
super.dispose();
}
}
애니메이션
// 플랫폼 vsync와 연결된 리소스들
AnimationController controller; // dispose() 필요
Ticker ticker; // dispose() 필요
VideoPlayerController videoController; // dispose() 필요
스트림 & 구독
// 네이티브 이벤트 스트림들
StreamSubscription subscription; // cancel() 필요
Timer timer; // cancel() 필요
// 예시
StreamSubscription? _subscription;
@override
void initState() {
_subscription = someStream.listen((data) {
// 처리
});
}
@override
void dispose() {
_subscription?.cancel(); // 필수!
super.dispose();
}
카메라 & 미디어
CameraController cameraController; // dispose() 필요
VideoPlayerController videoPlayer; // dispose() 필요
AudioPlayer audioPlayer; // dispose() 필요
ImagePicker picker; // 자동 정리되지만 캐시는 수동 정리
파일 & 네트워크
// 파일 핸들러들
File file; // 대부분 자동이지만 스트림은 수동
HttpClient client; // close() 필요
WebSocket webSocket; // close() 필요
// 예시
HttpClient? _client;
void makeRequest() {
_client = HttpClient();
// 사용...
}
@override
void dispose() {
_client?.close(); // 필수!
super.dispose();
}
플랫폼 채널
// 네이티브와 통신하는 채널들
MethodChannel channel;
EventChannel eventChannel;
BasicMessageChannel messageChannel;
// 예시 - 배터리 정보 채널
static const platform = MethodChannel('com.example/battery');
@override
void dispose() {
// MethodChannel은 보통 자동 정리되지만
// EventChannel의 경우 구독 취소 필요
super.dispose();
}
센서 & 하드웨어
// accelerometer, gyroscope 등
StreamSubscription? _accelerometerSubscription;
@override
void initState() {
_accelerometerSubscription = accelerometerEvents.listen((event) {
// 센서 데이터 처리
});
}
@override
void dispose() {
_accelerometerSubscription?.cancel(); // 센서 연결 해제
super.dispose();
}
텍스처 & 그래픽
// OpenGL 텍스처들
TextureController textureController; // dispose() 필요
CustomPainter painter; // 보통 자동이지만 복잡한 리소스는 수동
class MyCustomPainter extends CustomPainter {
Paint? _paint;
@override
void paint(Canvas canvas, Size size) {
_paint ??= Paint()..color = Colors.blue;
// 그리기
}
// dispose 패턴은 없지만 큰 리소스는 관리 필요
}
데이터베이스
// SQLite, Hive 등
Database database; // close() 필요
Box hiveBox; // close() 필요
@override
void dispose() async {
await database.close();
await hiveBox.close();
super.dispose();
}
// Flutter 구조
┌─────────────────┐
│ Dart Code │ ← 우리가 작성하는 코드
├─────────────────┤
│ Flutter Engine │ ← C++로 작성된 엔진
├─────────────────┤
│Platform Channel │ ← 네이티브와 통신
├─────────────────┤
│ Native Code │ ← Android(Java/Kotlin) / iOS(Swift/ObjC)
└─────────────────┘
네이티브 리소스들은 Flutter Engine이나 플랫폼 레벨에서 관리되기 때문에 Dart의 가비지 컬렉터가 자동으로 정리해주지 못함!