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

Categories:

Updated:

Leave a comment