[TIL] 비동기 코드 작성 중 만난 클로저(Closure) 이해하기: @escaping, 강한 참조, 메모리 누수 방지

Eden·2025년 1월 31일
0

TIL

목록 보기
109/132
post-thumbnail

🧑‍🧑‍🧒‍🧒 FamilyControlsManager 구현

[🌟] Screen Time API 권한 설정 도전하기 (5) - FamilyControls 권한 설정하기에서 작성한 코드다.

struct FamilyControlsManager {

    let center = AuthorizationCenter.shared

    func requestAuthorization(completionHandler: @escaping (() -> Void)) {
        Task {
            do {
                try await center.requestAuthorization(for: FamilyControlsMember.individual)
                print("스크린타임 권한 설정 성공")
                completionHandler() // ⬅️ 비동기 실행
            } catch {
                print("권한 설정 실패 \(error.localizedDescription)")
                completionHandler() // ⬅️ 비동기 실행
            }
        }
    }
}

🔍 이 코드에서 클로저의 역할

  • completionHandler는 @escaping 클로저다.
  • Task 내부에서 비동기 처리를 하고, 권한 요청이 끝나면 completionHandler()를 실행한다.
  • completionHandler를 사용함으로써 비동기 작업이 끝난 후 원하는 작업을 수행할 수 있도록 한다.

🧠 왜 클로저를 사용했을까? (메모리 관점에서)

이 코드를 비동기 처리 없이 동기적으로 실행하면, 권한 요청이 완료될 때까지 앱이 멈추게 된다. 하지만 비동기 작업을 사용하면 UI가 멈추지 않고 실행된다.

이때, 비동기 코드가 완료된 후 특정 작업을 실행하려면 클로저가 필요하다.
즉, 비동기 작업(Task)이 끝날 때 실행될 로직을 미리 저장해 두었다가, 작업이 끝나면 실행하는 것이 클로저의 역할이다.


🔥 @escaping 클로저를 사용한 이유

Swift에서 함수가 종료되면, 기본적으로 함수 내부의 클로저는 사라진다. 하지만 비동기 작업이 포함된 경우 함수가 종료된 후에도 클로저가 실행될 수 있다.

func requestAuthorization(completionHandler: () -> Void) {
    Task {
        completionHandler()  // 에러 발생
    }
}

@escaping을 붙이지 않으면 Task 내부에서 클로저를 실행하는 순간 에러가 발생한다.

because!!!!!!!!!!!!

  • requestAuthorization 함수가 종료되면 completionHandler가 자동으로 해제됨.
  • Task는 비동기적으로 실행되므로, completionHandler가 해제된 후 실행될 수도 있음.
  • 따라서, 함수가 종료된 후에도 실행될 가능성이 있는 클로저는 @escaping으로 선언해야 한다.

🛑 클로저가 강한 참조를 방지해야 하는 이유

🔗 메모리 누수와 강한 참조(Strong Reference)의 관계

메모리 누수(Memory Leak)는 강한 참조(Strong Reference)로 인해 객체가 해제되지 않을 때 발생한다.

클로저가 self를 강하게 참조하면, 해당 인스턴스가 해제되지 않고 메모리에서 계속 남아 있게 된다.


🧐 강한 참조가 발생하는 경우


class FamilyControlsManager {

    let center = AuthorizationCenter.shared

    func requestAuthorization(completionHandler: @escaping (() -> Void)) {
        Task {
            do {
                try await self.center.requestAuthorization(for: FamilyControlsMember.individual)  // ❌ self 강한 참조
                print("스크린타임 권한 설정 성공")
                completionHandler()
            } catch {
                print("권한 설정 실패 \(error.localizedDescription)")
                completionHandler()
            }
        }
    }
}

위 코드에서 self를 직접 사용하면 클로저가 self를 캡처하면서 강한 참조가 발생한다.

이 경우, requestAuthorization을 호출한 후에도 FamilyControlsManager 인스턴스가 해제되지 않는다.

결과: FamilyControlsManager가 메모리에 계속 남아 있는 메모리 누수 발생!


💪🏻 강한 참조 방지 방법

[weak self] 사용 (약한 참조)

func requestAuthorization(completionHandler: @escaping (() -> Void)) {
    Task { [weak self] in
        guard let self = self else {
            print("self가 nil이므로 실행하지 않음")
            return
        }
        do {
            try await self.center.requestAuthorization(for: FamilyControlsMember.individual)
            print("스크린타임 권한 설정 성공")
            completionHandler()
        } catch {
            print("권한 설정 실패 \(error.localizedDescription)")
            completionHandler()
        }
    }
}

[weak self]를 사용하면?

  • 클로저가 self를 강한 참조하지 않고, 약한 참조(weak reference)로 유지.
  • self가 더 이상 필요하지 않으면 메모리에서 해제됨.
  • self가 nil이 될 가능성이 있으므로 guard let self = self else { return }을 사용하여 안전하게 처리.

메모리 누수 방지 + 안전한 실행 가능!


📌 메모리 누수와 캡처(참조)의 상관관계

🔍 캡처(참조)가 어떻게 메모리 누수를 유발하는가?

  1. 클로저는 실행될 때 참조하는 모든 변수를 캡처한다.
  2. 클로저가 self를 강한 참조하면, self의 참조 카운트가 증가한다.
  3. 클로저가 실행되지 않는 한, self는 해제되지 않는다.
  4. 이 상태에서 self가 해제되지 못하고 남아 있으면 메모리 누수가 발생한다.

🧹 정리

개념설명
클로저 사용 이유비동기 작업 후 특정 작업을 실행하기 위해 사용
@escaping 사용 이유클로저가 함수 종료 후에도 실행될 가능성이 있기 때문
메모리 누수 발생 조건클로저가 self를 강한 참조할 경우
캡처와 메모리 누수의 관계클로저는 실행될 때 캡처한 객체를 참조하고, 강한 참조 시 해제되지 않아 메모리 누수 발생
강한 참조 방지 방법[weak self] 사용하여 약한 참조로 유지

📌 결론

  • 클로저 내부에서 self를 직접 사용하면 강한 참조(Strong Reference) 가 발생하고, self가 메모리에서 해제되지 않아 메모리 누수가 발생할 수 있다.
  • 이를 방지하려면 [weak self]를 사용하여 약한 참조(Weak Reference) 로 만들고, self가 필요할 때 guard let self = self로 안전하게 처리하면 된다.
  • 비동기 작업을 사용할 때는 @escaping을 사용해야 하며, 클로저가 self를 캡처하는 경우 강한 참조 여부를 항상 고려해야 한다.
profile
Frontend 🌐 and iOS  🫶🏻

0개의 댓글

관련 채용 정보