Closure (클로져)
Updated:
🔷 기본 Closure
🔑 클로저는 일정 기능을 하는 코드를 하나의 블록으로 모아놓은 것을 말합니다
함수를 만들어 변수에 할당하고, 해당 변수를 사용하여 해당 한수를 호출하고, 해당 함수를 다른 한수에 매개 변수로 전달할 수 있습니다
위에 설명을 보면 함수와 작동이 비슷하지만 형식은 다릅니다
-
Swift 에서 클로저를 사용하는 가장 일반적인 이유는 기능을 저장 하기 위해서 입니다
-
클로저를 통해 일부 기능을 단일 변수로 마무리 하고 어딘가에 저장할 수 있습니다. 또한 기능에서
return
하고 클로저를 다른 곳에save
할 수도 있습니다. -
클로저는 자기자신의 매개 변수를
accept
하거나return
할때 주의해서 code reading 해야 합니다
// String 을 반환하는 클로저
let myName: String = {
// myName 으로 들어간다
return "Jacob"
}()
print(myName) // Jacob
// 매개 변수를 사용하는 closure : parameter 로 String 을 받아서 String 으로 return 한다는 것
let myRealName = { (name: String) -> String in
return "제 이름은 \(name) 입니다"
}
myRealName("Jacob") // 제 이름은 Jacob 입니다
// logic 처리 : return 값을 내뱉지 않고 안에 있는 값을 가지고 있게 처리가 가능
// return 이 없을 때는 Void 로 주로 처리
let myRealNameLogic : (String) -> Void = { (name: String) in
print("개발하는 \(name)")
}
myRealNameLogic("Jacob Ko") // 개발하는 Jacob Ko
🔶 매개변수로서 클로저 사용
클로저를 만들 때 매개변수를 사용할 이름이나 공백이 없습니다. 그 말은 클로저에서 매개변수는 { }
안에서 나열하게 되는데, 그 안에 ( )
에 in
을 작성해서 클로저가 시작된다는것을 알려 줍니다
// completion 이라는 클러저를 매개변수로 가지는 매소드 정의
func sayHi(completion: () -> Void) {
print("sayHi() called")
sleep(2) // 2초 감깐 멈추기
// completion 클로저 실행
completion()
}
// 메소드 호출부에서 이벤트 종료를 알 수 있다
sayHi(completion: {
print("2초가 지났다. 1") // 2초가 지났다. 1
})
// 다른 반법으로 호출
sayHi() {
print("2초가 지났다. 2") // 2초가 지났다. 2
}
// 클로져가 매개변수로 되어 있으면 그냥 {} 블럭으로만 사용이 가능
sayHi {
print("2초가 지났다. 3") // 2초가 지났다. 3
}
// 매개변수로서 데이터를 반환하는 클로저
func sayHiWithName(completion: (String) -> Void) {
print("sayHiWithName() called")
sleep(2)
// 클로저를 실행과 동시에 데이터를 반환
completion("오늘은 안녕하십니까?")
}
sayHiWithName(completion: { (comment: String) in
print("2초 뒤에 그가 말했습니다! comment: ", comment) // 2초 뒤에 그가 말했습니다! comment: 오늘은 안녕하십니까?
})
// 위에것을 줄여서 사용
sayHiWithName(completion: { comment in
print("2초 뒤에 그가 말했습니다! comment: ", comment) // 2초 뒤에 그가 말했습니다! comment: 오늘은 안녕하십니까?
})
// 더 줄일 수 있음 -> 주로 사용
sayHiWithName { comment in
print("2초 뒤에 그가 말했습니다! comment: ", comment)
}
// 제일 간편하게 줄이는것 : $0 을 사용하면 첫번째 들어오늘것을 받으면서 사용할 수 있음
sayHiWithName {
print("2초 뒤에 그가 말했습니다! comment: ", $0) // 2초 뒤에 그가 말했습니다! comment: 오늘은 안녕하십니까?
}
// 2가지 이상의 매개변수를 데이터를 반환하는 클로저
func sayHiWithNameAge(completion: (String, Int) -> Void) {
print("sayHiWithName() called")
sleep(2)
// 클로저를 실행과 동시에 데이터를 반환
completion("Jacob", 40)
}
// 두가지 type parameter 호출
sayHiWithNameAge { name, age in
print("저의 이름음 \(name) 이고, 나이는 \(age) 입니다") // 저의 이름음 Jacob 이고, 나이는 40 입니다
}
// 위에꺼 줄여서 $0, $1 으로 사용
sayHiWithNameAge {
print("저의 이름은 \($0) 이고, 나이는 \($1) 입니다") // 저의 이름은 Jacob 이고, 나이는 40 입니다
}
// 만약 parameter 가 4개가 들어온다 하면 호출해서 사용할때는 순서대로 $0, $1, $2, $3 으로 지정해서 사용해주면 됩니다
// 만약 매개변수 중에 앞에꺼 String 을 쓰고 싶지 않고 Age 만 쓰고 싶을때는
// _ 언더바 를 사용하게 되면 첫번째 부분은 안쓰게 됨 (생략이라고 보면 됨)
sayHiWithNameAge { _, age in
print("나이는 \(age) 입니다") // 나이는 40 입니다
}
// 실행 할때 completion 을 받지 않고 싶은 경우 : optional 로 만들고, 기본형을 nil 로 지정
func sayHiOptional(completion: (() -> Void)? = nil) {
print("sayHiWithName() called")
sleep(2)
// 클로저를 실행과 동시에 데이터를 반환
completion?()
}
sayHiOptional() // sayHiWithName() called
sayHiOptional {
print("2초가 지난거 체크") // 2초가 지난거 체크
}
() -> Void
형태는 반환형이 없다는것 그래서 void 인거 이벤트가 발생되었다는것만 알려줍니다
// () -> Void 와 같은것임
func completion() {
}
(String) -> Void
는 클로저에서 매개변수가 있는 형태로서
// (String) -> Void 와 같은 형태임
func completion (userInput: String) {
}
🔶 매개변수를 받아서 return 하는 closure
- 주로 collection type 에서 map 등 고차함수에서 주로 사용됩니다 (parameter 를 받아서 연산 처리하고, 다시 값을 rethrow 하는 방식임)
// (Int) -> String : void 가 아닌 String 으로 return 값이 있는것
func transform(number: Int) -> String {
return "숫자 : \(number)"
}
// 위의 경우는 주로 collection 에서 사용됨
var myNumbers : [Int] = [0, 1, 2, 3, 4, 5]
var transformedNumbers = myNumbers.map { aNumber in
return "숫자: \(aNumber)"
}
print(transformedNumbers) // ["숫자: 0", "숫자: 1", "숫자: 2", "숫자: 3", "숫자: 4", "숫자: 5"]
// 줄여서 $0 형태로 사용할 수 있음
var tenPlusFromMyNumbers = myNumbers.map {
return $0 + 10
}
print(tenPlusFromMyNumbers) // [10, 11, 12, 13, 14, 15]
Closure 를 통한 sorted 함수 사용하기 (내림차순, 오름차순 정렬)
var numbers = [1, 2, 4, 6, 3, 9, 7]
// 내림차순 정렬
let descendingOrder = numbers.sorted(by: { $0 > $1 })
print(descendingOrder) // [9, 7, 6, 4, 3, 2, 1]
// 오름차순 정렬
let ascendingOrder = numbers.sorted(by: { $0 < $1 })
print(ascendingOrder) // [1, 2, 3, 4, 6, 7, 9]
🔶 @escaping 와 @autoclosure
클로저를 함수나 메소드의 인자값으로 사용할 때에는 용도에 따라 @escaping 과 @autoclosure 속성을 사용할 수 있습니다
👉 @escaping
- @escaping 속성은 인자값으로 전달된 클로저를 저장해 두었다가, 나중에 다른 곳에서도 실행할 수 있도록 허용해 주는 속성입니다
func callback(fn: () -> Void) {
fn()
}
callback {
print("Closure 가 실행 되었습니다") // Closure 가 실행 되었습니다
}
// 위의 함수를 다음과 같이 사용
func callback2(fn: () -> Void) {
let f = fn // 클로저를 상수 f 에 대입
f() // 대입한 클로저 실행
} // 오류 발생 Non-escaping 파라미터인 'fn' 은 오직 직접 호출만 가능함
-
swift 에서 함수의 인자값으로 전달된 클로저는 기본적으로 탈출불가(non-escape) 성격을 가집니다. 이는 해당 클로저를 1. 함수내에서 2. 직접 실행을 위해서만 사용해야 합니다. 때문에 함수 내부라도 변수나 상수에 직접 대입할 수 없습니다
-
이때 클로저르 련수나 상수에 대입하거나 중첩 함수 내부에서 사용해야 하는 경우에는 @escaping 속석을 사용합니다. 이 속성을 붙여주면 해당 클로저는 탈출이 가능한 인자값으로 설정됩니다.
func callback2(fn: @escaping () -> Void) {
let f = fn
f()
}
callback2 {
print("callback2 실행") // callback2 실행
}
-
클로저의 기본 속성을 non-escape 하게 관리함으로써 얻어지는 장점은 컴파일러가 코드를 최적하는 과정에서의 성능 향상입니다. 해당 클로저가 탈출할 수 없다는 것은 컴파일러가 더 이상 메모리 관리상에 관여 하지 않아도 되기 때문입니다
-
탈출불가 클로저 내에서는 self 를 사용할 수 있습니다. 이 클로저는 해당 함수가 끝나서 리턴 되기 전에 호출될 것이 명확하기 때문입니다. self 에 대한 약한 참조 weak reference 를 사용할 필요가 없습니다.
👉 @autoclosure
-
@autoclosure 속성은 인자값으로 전달된 일반 구문이나 함수 등을 클로저로 래핑 (wrapping) 하는 역활을 합니다. 즉, 일반 구문을 인자값으로 넣더라도 컴파일러가 알아서 클로저를 만들어서 사용합니다
-
이 속섯ㅇ을 적용하면 인자값을
{}
형태가 아니라()
형태로 사용할 수 있다는 장점이 있습니다. 인자값을 직접 클로저 형식으로 넣어줄 필요가 없기 때문입니다.
func conditions(stmt: @autoclosure () -> Bool) {
if stmt() == true {
print("결과가 참입니다")
} else {
print("결과가 거짓입니다")
}
}
// 실행
conditions(stmt: (4 > 2)) // 결과가 참입니다
- @autoclosure 를 사용함으로 클로저가 아니라 그 안에 들어가는 내용만 인자값으로 넣어줄 뿐입니다.
Reference
꼼꼼한 재은 씨의 스위프트 문법편 - https://book.jacobko.info/#/book/1186710233
서근 개발 노트 : Swift : 기초문법 [#1 클로저 기본 - Closure] - https://seons-dev.tistory.com/112
개발하는 정대리 스위프트 기초 문법 - 10일차 / 클로저 - https://www.youtube.com/watch?v=8nPQKGrpbmY&list=PLgOlaPUIbynoqbQw_erl3L2w7vfOTCtFD&index=10
Leave a comment