우리 앱에서 메모리 관리에서 가장 중요한 것이 deinit이다
deinit {
subscription.removeAll()
}
우리는 deinit에서 옵저빙을 해제시켜준다.
그렇다면 모델에서 deinit이 불리지 않았다는 것은
계속 Init으로 쌓이기만하고 메모리는 해제되지 않았다는 것이다.
ARC란 Auto Refernece Counting라는 개념인데 Reference Count가 0이 되면 자연스럽게 메모리에서 해제시켜주는 Swift의 메모리 관리 모델이다.
View가 Disappear 되면서 Reference Count는 0이 되었을텐데 메모리가 계속 쌓인다는 것으로 볼 때
의심할 수 있는 것은 한 가지다. 'Retain Cycle'
강한 참조가 일어나는 부분이 어디일까 하고 우리 앱에 전체에 [weak self]와 [unowned self]를
검색해보았다. 놀랍게도 그 어떤 뷰모델도 weak self와 unowned self를 사용한 곳이 없었다.
Combine을 사용한 부분에서 @published를 참조하고 있고, sink 있는 곳에서 fix가 뜨니깐
무의식적으로 self를 붙이라니깐 fix 버튼을 눌러 모든 곳에 강한 참조를 발생시킨 것이다.
/// Mark: - setObserver
/// feature : 유저가 4자리를 입력하면 비밀번호가 맞는지 여부를 체크한다.
/// 비밀번호가 맞으면 appScreenLockMode의 상태를 바꿔 잠금을 해제하고
/// 비밀번호가 틀리면 비밀번호를 리셋하고 비밀번호가 흔들리는 shake애니메이션을 실행시킨다.
func setObserver(appScreenLockOffAction: @escaping () -> Void) {
let appScreenLockNumberSubscriber = $plainText
.removeDuplicates()
.subscribe(on: DispatchQueue.main)
.sink { [unowned self] value in
// 비밀번호 4자리를 입력했을 경우
if value.count == 4 {
// 비밀번호를 해쉬한다.
let hashedText = value.sha256()
print("APP SCREEN LOCK :: PLAINTEXT HASH & PASSWORD CHECK")
// 해쉬된 값이 저장되어있는 비밀번호와 같을 경우
if hashedText == getUD(key: self.APP_SCREEN_LOCK_PASSWORD) {
DispatchQueue.main.async {
print("APP SCREEN LOCK :: USER ENTER CORRECT PASSWORD")
// Nfilter Data 초기화
isNfilterNeedClear.toggle()
// 비밀번호 상태 값 초기화
resetPasswordString()
print("APP SCREEN LOCK :: PROGRAMMATICALLY OPERATE PRESS KEY PAD RELOAD BUTTON ")
appScreenLockMode = .OffScreenLock
appScreenLockMode?.getMessageAppScreenLockMode()
appScreenLockOffAction()
}
}
// 해쉬된 값이 저장되어있는 비밀번호와 다른 경우
else {
DispatchQueue.main.async {
print("APP SCREEN LOCK :: USER ENTER WRONG PASSWORD")
// 비밀번호가 틀렸을 경우 Shake Animation 효과
isWrongPassword = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
isWrongPassword = false
}
// Nfilter Data 초기화
isNfilterNeedClear.toggle()
// 비밀번호 상태 값 초기화
resetPasswordString()
print("APP SCREEN LOCK :: PROGRAMMATICALLY OPERATE PRESS KEY PAD RELOAD BUTTON ")
}
}
}
}
.store(in: &subscription)
}
상기의 테스트 코드에서 [unowned self]를 적용하자 @stateObject의 deinit이 불렸고
Observing을 해제했다.