UI5 메모리 누수는 왜 발생하는가
SAP UI5는 자바스크립트 기반 SPA(Single Page Application) 프레임워크입니다. 사용자가 앱을 장시간 사용하면서 페이지 이동을 반복할 때, 사용하지 않는 컨트롤이 메모리에 계속 남아있으면 점점 응답이 느려지고 결국 브라우저가 멈출 수 있습니다. UI5에서 메모리 누수가 발생하는 주요 패턴과 해결책을 코드로 설명합니다.
누수 패턴 1: Dialog를 매번 새로 생성하고 destroy 안 함
// 잘못된 패턴: 클릭할 때마다 새 Dialog 생성
onDetailPress: function(oEvent) {
const oDialog = new sap.m.Dialog({
title: "상세 정보",
content: new sap.m.Text({
text: oEvent.getSource().getTitle()
}),
endButton: new sap.m.Button({
text: "닫기",
press: function() { oDialog.close(); }
})
});
oDialog.open();
// 닫아도 DOM에서만 사라지고 메모리에 남음
// 100번 클릭 = 100개의 Dialog 인스턴스
}
// 올바른 패턴 A: 싱글톤 + addDependent
onDetailPress: function(oEvent) {
if (!this._oDetailDialog) {
this._oDetailDialog = new sap.m.Dialog({
title: "상세 정보",
endButton: new sap.m.Button({
text: "닫기",
press: () => this._oDetailDialog.close()
})
});
// 뷰에 종속 등록 — 뷰 destroy 시 함께 정리
this.getView().addDependent(this._oDetailDialog);
}
const sTitle = oEvent.getSource().getTitle();
this._oDetailDialog.getContent()[0]?.setText(sTitle);
this._oDetailDialog.open();
},
// onExit: 명시적 destroy (addDependent로 자동 처리되지만 확실히)
onExit: function() {
if (this._oDetailDialog) {
this._oDetailDialog.destroy();
this._oDetailDialog = null;
}
}
올바른 패턴 B: Fragment로 Dialog 분리
// Fragment 파일: DetailDialog.fragment.xml
// <core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core">
// <Dialog id="detailDialog" title="상세 정보">
// <Text id="detailText" />
// <endButton>
// <Button text="닫기" press=".onDialogClose" />
// </endButton>
// </Dialog>
// </core:FragmentDefinition>
// Controller
onDetailPress: async function(oEvent) {
if (!this._oFragment) {
this._oFragment = await Fragment.load({
id: this.getView().getId(),
name: "com.myapp.view.DetailDialog",
controller: this
});
this.getView().addDependent(this._oFragment);
}
this.byId("detailText").setText(oEvent.getSource().getTitle());
this._oFragment.open();
},
onDialogClose: function() {
this._oFragment.close();
}
누수 패턴 2: 이벤트 리스너를 수동으로 detach하지 않음
// 잘못된 패턴: attachEvent 후 detach 없음
onInit: function() {
const oEventBus = sap.ui.getCore().getEventBus();
// onInit 때마다 핸들러가 추가됨 (라우팅 반복 시 중복 등록)
oEventBus.subscribe("MyApp", "orderUpdated", this._onOrderUpdated, this);
},
// 올바른 패턴: onExit에서 반드시 detach
onExit: function() {
const oEventBus = sap.ui.getCore().getEventBus();
oEventBus.unsubscribe("MyApp", "orderUpdated", this._onOrderUpdated, this);
}
누수 패턴 3: setInterval/setTimeout 정리 안 함
// 잘못된 패턴
onInit: function() {
this._iTimerId = setInterval(() => {
this._refreshData();
}, 5000);
// 뷰가 이동되어도 타이머가 계속 실행됨
},
// 올바른 패턴
onExit: function() {
if (this._iTimerId) {
clearInterval(this._iTimerId);
this._iTimerId = null;
}
}
누수 패턴 4: List Item 바인딩 후 모델 미해제
// 뷰를 동적으로 생성/제거하는 경우
// 올바른 패턴: 뷰 제거 시 모델 바인딩 해제
destroyDynamicView: function(oView) {
// 바인딩 해제
oView.unbindElement();
oView.setModel(null);
// 뷰 destroy
oView.destroy();
}
메모리 누수 진단 도구
// UI5 진단 도구 활성화 (개발 중)
// URL에 ?sap-ui-debug=true 추가
// Chrome DevTools → Memory 탭 → Heap Snapshot 촬영
// 작업 전/후 스냅샷 비교로 누수 확인
// UI5 자체 통계
sap.ui.getCore().getUIDirty(); // 렌더링 대기 상태
// Chrome DevTools Console
// sap.ui.getCore().byId("myDialog") // null이어야 함
메모리 누수 방지 체크리스트
- Dialog/Popover는 싱글톤 패턴 + addDependent
- onInit에서 등록한 EventBus 핸들러는 onExit에서 반드시 unsubscribe
- setInterval/setTimeout은 onExit에서 clear
- 동적으로 생성한 컨트롤은 사용 후 destroy
- Fragment.load는 캐시 활용 (같은 Fragment를 여러 번 load하지 않음)
공식 문서
UI5 메모리 관리 가이드는 UI5 Best Practices — Memory Management에서 확인하세요.
댓글 0
아직 댓글이 없습니다.