[🌟] 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)이 끝날 때 실행될 로직을 미리 저장해 두었다가, 작업이 끝나면 실행하는 것이 클로저의 역할이다.
Swift에서 함수가 종료되면, 기본적으로 함수 내부의 클로저는 사라진다. 하지만 비동기 작업이 포함된 경우 함수가 종료된 후에도 클로저가 실행될 수 있다.
func requestAuthorization(completionHandler: () -> Void) {
Task {
completionHandler() // 에러 발생
}
}
@escaping
을 붙이지 않으면 Task 내부에서 클로저를 실행하는 순간 에러가 발생한다.
because!!!!!!!!!!!!
requestAuthorization
함수가 종료되면 completionHandler
가 자동으로 해제됨.Task
는 비동기적으로 실행되므로, completionHandler
가 해제된 후 실행될 수도 있음.메모리 누수(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 }
을 사용하여 안전하게 처리.➡ 메모리 누수 방지 + 안전한 실행 가능!
self
를 강한 참조하면, self
의 참조 카운트가 증가한다.self
는 해제되지 않는다.self
가 해제되지 못하고 남아 있으면 메모리 누수가 발생한다.개념 | 설명 |
---|---|
클로저 사용 이유 | 비동기 작업 후 특정 작업을 실행하기 위해 사용 |
@escaping 사용 이유 | 클로저가 함수 종료 후에도 실행될 가능성이 있기 때문 |
메모리 누수 발생 조건 | 클로저가 self 를 강한 참조할 경우 |
캡처와 메모리 누수의 관계 | 클로저는 실행될 때 캡처한 객체를 참조하고, 강한 참조 시 해제되지 않아 메모리 누수 발생 |
강한 참조 방지 방법 | [weak self] 사용하여 약한 참조로 유지 |
self
를 직접 사용하면 강한 참조(Strong Reference) 가 발생하고, self
가 메모리에서 해제되지 않아 메모리 누수가 발생할 수 있다.[weak self]
를 사용하여 약한 참조(Weak Reference) 로 만들고, self
가 필요할 때 guard let self = self
로 안전하게 처리하면 된다.@escaping
을 사용해야 하며, 클로저가 self
를 캡처하는 경우 강한 참조 여부를 항상 고려해야 한다.