일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 티스토리챌린지
- 영화
- sopt 35기
- 인프런
- 자바
- 키노
- 새벽녘 소소한 기록
- 영화후기
- 리뷰
- inflearn
- Flutter Toy Project
- 영화리뷰
- java
- 영화기록
- SOPT
- backend
- 자바 스터디
- 토이프로젝트
- 코딩공부
- 백엔드
- 백준
- Flutter
- 일기
- toy project
- 오블완
- 영화일기
- 스프링 입문
- sopt ios
- 플러터
- 자바공부
- Today
- Total
새벽의 기록
[SOPT] 문법 스터디 #2 클로저 본문
클로저
- 클로저의 메모리 관리 방식
- 클로저의 캡처 리스트
- Trailing Closure이 사용되는 예시를 찾아보거나 실제로 구현해보고, 어떤 점이 좋은지 알아봅시다 !
- Escaping 클로저와 Non-escaping 클로저의 차이
1. 클로저의 메모리 관리 방식
클로저는 참조타입이라 ARC(Automatic Reference Counting)로 메모리를 관리한다.
ARC(Automatic Reference Counting)는 Swift의 메모리 관리 시스템으로, 클래스 인스턴스의 수명을 관리한다.
→ 각 클래스 인스턴스에 대해 몇 개의 참조가 있는지 추적하여, 참조 카운트가 0이 되면 해당 인스턴스의 메모리를 해제한다.
→ 즉, 참조 카운트가 1 이상이라면 메모리에서 할당 해제되지 않는다.
2. 클로저의 캡처 리스트
캡쳐
func doSomething() {
var message = "Hi i am Kim"
//클로저 범위 시작
var num = 10
let closure = { print(num) }
//클로저 범위 끝
print(message)
}
익명함수는 closure는 클로저 내부에서 외부 변수인 num이라는 변수를 사용(print)하기 때문에
num의 값을 클로저 내부적으로 저장하고 있는데, 이것을 클로저에 의해 num의 값이 캡쳐 되었다 라고 표현함.
클로저의 값 캡쳐 방식
Closure는 값을 캡쳐할 때 Value/Reference 타입에 관계 없이 Reference Capture 한다.
→ 즉, 구조체나 배열처럼 값을 복사해서 사용하는 값 타입이든, 클래스처럼 값을 참조해서 사용하는 참조 타입이든 상관없이 Reference Capture.
예를 들어,
func doSomething() {
var num: Int = 0
print("num check #1 = \\(num)")
let closure = {
print("num check #3 = \\(num)")
}
num = 20
print("num check #2 = \\(num)")
closure()
}
// print : num check #1 = 0
// print : num check #2 = 20
// print : num check #3 = 20
closure는 num이라는 외부 변수를 클로저 내부에서 사용하기 때문에 num을 캡쳐한다.
이 때 캡쳐의 방식이 Reference Capture 즉, num이란 변수를 참조!
→ 따라서, closure를 실행하기 전에 num이란 값을 외부에서 변경하면 클로저 내부에서 사용하는 num의 값 또한 변경된다.
func doSomething() {
var num: Int = 0
print("num check #1 = \\(num)")
let closure = {
num = 20
print("num check #3 = \\(num)")
}
closure()
print("num check #2 = \\(num)")
}
// print : num check #1 = 0
// print : num check #3 = 20
// print : num check #2 = 20
혹은, 클로저 내부에서 num의 값을 바꾸면 클로저 외부에 있는 num의 값도 변경된다.
그럼 만약 Value Type으로 Capture를 하고 싶으면 어떻게 할까??
→ 이 때 사용하는 게 캡쳐 리스트
클로저의 캡쳐 리스트
let closure = { [num, num2] in
이런식으로 클로저의 시작인 { 바로 옆에 []를 이용해서 캠쳐할 멤버를 나열
→ 이 때 in 키워드도 꼭 작성해주기
Value Type의 값을 복사해서 Value Capture 하기
func doSomething() {
var num: Int = 0
print("num check #1 = \\(num)")
let closure = { [num] in
print("num check #3 = \\(num)")
}
num = 20
print("num check #2 = \\(num)")
closure()
}
// print : num check #1 = 0
// print : num check #2 = 20
// print : num check #3 = 0
closure를 실행하기 전에 외부 변수 num의 값을 20으로 변경했지만, 클로저의 num에는 영향을 주지 않는다.
한 가지 유의할 점은,
이 방식은 Closure를 선언할 당시의 num의 값을 Const Value Type으로 캡쳐한다.
→ 즉 상수로 캡쳐한다.
따라서 closure 내부에서 Value Capture된 값을 변경할 수 없음
그럼 Reference Type의 값을 Value Capture 할 수도 있나?
→ 못한다. Reference Type은 자동으로 Reference Capture함
그렇다면 Reference Type은 클로저 캡쳐 리스트가 필요 없나?
캡쳐 리스트를 사용하면 클로저와 클로저가 캡쳐한 객체 사이의 참조를 약하게(weak) 또는 비소유(unowned)로 설정해 순환 참조를 방지할 수 있다.
→ 클로저의 강한 순환 참조 해결방법!!
class Human {
lazy var getName: () -> String? = { [weak self] in
return self?.name
}
}
이런식으로 캡쳐 리스트 앞에 weak 혹은 unowned 작성해서 사용
3. Trailing Closure이 사용되는 예시를 찾아보거나 실제로 구현해보고, 어떤 점이 좋은지 알아봅시다 !
트레일링 클로저는 함수의 마지막 파라미터가 클로저일 때, 이를 파라미터 값 형식이 아닌 함수 뒤에 붙여 작성하는 문법
→ 마지막 파라미터가 클로저면 Argument Label은 생략
→ 파라미터가 하나인 경우 ()도 생략
예를 들어,
func doSomething(closure: () -> ()) {
closure()
}
이것처럼 클로저를 파라미터로 받는 함수가 있을 때, 이 함수를 호출하려고 하면
doSomething(closure: { () -> () in
print("Hello!")
})
이런 식으로 작성해야했다. 근데 이럴 경우 가독성이 별로 좋지 않아서 트레일링 클로저를 사용!
doSomething () { () -> () in
print("Hello!")
}
이런 식으로 “closure:” 부분을 생략하고 바로 붙여 쓸 수 있다.
하지만 여전히 ‘() -> () in’ 요런 게 너무 지저분해서 사용하는 게 경량 문법!
클로저의 경량 문법
func doSomething(closure: (Int, Int, Int) -> Int) {
closure(1, 2, 3)
}
이런 함수가 있으면 원래는
doSomething(closure: { (a: Int, b: Int, c: Int) -> Int in
return a + b + c
})
이런 식으로 작성했었는데, 여기서 경량 문법은 파라미터 형식(‘Int’)과 리턴 형식(‘→’)을 생략할 수 있다.
doSomething(closure: { (a, b, c) in
return a + b + c
})
여기서 한 번 더, Parameter Name은 Shortand Argument Names으로 대체하고, 이 경우 Parameter Name과 in 키워드를 삭제할 수 있다.
→ ‘a, b, c’ 얘네 대신 $0, $1, $2 얘네들 사용 가능
doSomething(closure: {
return $0 + $1 + $2
})
그러면 이렇게 간단화 할 수 있고, 단일 리턴문만 남을 경우 return도 생략할 수 있다.
doSomething(closure: {
$0 + $1 + $2
})
그럼 이제 맨 처음 찾아본 트레일링 클로저를 적용할 수 있는데, 클로저 파라미터가 마지막 파라미터면, 트레일링 클로저로 작성할 수 있다.
doSomething() {
$0 + $1 + $2
}
파라미터가 하나인 경우 ()도 생략 가능하니 결과물은
doSomething {
$0 + $1 + $2
}
이렇게까지 줄여진다. 코드의 길이도 줄고 가독성도 좋아져서 굳.
4. Escaping 클로저와 Non-escaping 클로저의 차이
Non-escaping 클로저는 함수 내에서만 실행되며, 함수가 종료되면 메모리에서 해제된다.
→ 기본적으로 클로저는 non-escaping
Escaping 클로저는 함수가 종료된 후에도 실행될 수 있으며, 함수 외부로 클로저가 전달될 수 있다.
→ 클로저가 함수의 외부에서 사용되거나 비동기 작업에 전달될 때 사용된다.
→ 함수 외부에서 클로저가 사용될 수 있기 때문에, 클로저가 참조를 강하게 유지하면 순환 참조 문제가 발생할 수 있다.
'SOPT > 문법 스터디' 카테고리의 다른 글
[SOPT] 문법 스터디 #4 메서드와 옵셔널 체이닝 (7) | 2024.11.14 |
---|---|
[SOPT] 문법 스터디 #3 열거형과 프로퍼티 (0) | 2024.11.13 |
[SOPT] 문법 스터디 #1 콜렉션 타입 (0) | 2024.11.11 |