[🌟] 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를 캡처하는 경우 강한 참조 여부를 항상 고려해야 한다.