Closure
: 일회용 함수를 작성할 수 있는 구문
일회용함수 (익명함수)
: 한 번만 사용할 구문들의 집합이면서, 그 형식은 함수로 작성되어야 하는 제약조건이 있을 때 만들어 사용할 수 있는 함수
클로저는 외부 함수 내에서 내부 함수를 반환하고, 이 내부 함수가 외부 함수의 지역 변수나 상수를 참조할 때 만들어진다.
// 전역 함수
func greet() {
print("Hello, World!")
}
// 사용 예
greet() // "Hello, World!"
func outerFunction() -> () -> Void {
let outerValue = "Hello"
func innerFunction() {
print(outerValue)
}
return innerFunction
}
// 사용 예
let closure = outerFunction()
closure() // "Hello"
// 클로저 표현식
let incrementer: (Int) -> Int = { value in
return value + 1
}
// 사용 예
let result = incrementer(5)
print(result) // 6
클로저 표현식은 대부분 인자값으로 함수를 넘겨주어야 할 때 사용하지만
직접 실행할 수 있는 두가지 방법이 있다.
// 클로저 표현식
{ (매개변수) -> 반환 타입 in
실행할 구문
}
// 인자값과 반환값이 없는 클로저, 반환값은 없어도 명시적으로 표시해줘야한다.
{ () -> () in
print("클로저가 실행됩니다")
}
// 매개변수가 있는 클로저 표현식
let c = { (s1: Int, s2: String) -> Void in
print("s1:\(s1), s2:\(s2)")
}
c(1, "closure") // 출력값: s1:1, s2:closure
let f = { () -> Void in
print("클로저가 실행됩니다")
}
// 클로저 실행 첫번째 방법
f() // "클로저가 실행됩니다"
// 클로저 실행 두번째 방법
({ () -> Void in
print("클로저가 실행됩니다")
})()
클로저 표현식은 주로 인자값으로 사용되는 객체여서 간결성을 극대화하기 위해 여러 부분 생략이 가능하다.
var value = [1,9,5,7,3,2]
func order(s1: Int, s2: Int) -> Bool {
if s1 > s2 {
return true
} else {
return false
}
}
// sort(by: ) 메서드를 이용해 정렬함
value.sort(by: order)
// [9,7,5,3,2,1]
// 1번 클로저 표현식
{(s1: Int, s2: Int) -> Bool in
if s1 > s2 {
return true
} else {
return false
}
}
// 1번 클로저 표현식을 이용해 정렬함
value.sort(by: {
(s1: Int, s2: Int) -> Bool in
if s1 > s2 {
return true
} else {
return false
}
})
// [9,7,5,3,2,1]
// 2번 클로저 표현식: s1 > s2 구문은 Bool 값을 리턴하므로 코드가 간결하게 바뀜
{ (s1: Int, s2: Int) -> Bool in
return s1 > s2
}
// 2번 클로저 표현식을 이용해 정렬함
value.sort(by: { (s1: Int, s2: Int) -> Bool in return s1 > s2 })
// 3번 클로저 표현식: 타입 어노테이션 생략
{ s1, s2 in return s1 > s2 }
// 3번 클로저 표현식을 이용해 정렬함
value.sort(by: { s1, s2 in return s1 > s2 })
// 4번 클로저 표현식: 매개변수 생략
// $0, $1, $2...를 내부 상수로 사용 -> 입력받은 인자값 순서대로 사용
// in 키워드로 실행 구문과 선언 구문을 분리할 필요도 없으므로 in 키워드도 생략 (return도 생략 가능)
{ return $0 > $1 }
// 5번 클로저 표현식: return 생략
value.sort(by: { $0 > $1 })
// 6번 표현식: 연산자 함수를 사용해 더 간결하게 표현
value.sort(by: >)
함수의 마지막 인자값이 클로저일때만 적용된다. (인자값이 하나여도 사용 가능하다.)
func calculate (a: Int, b: Int, method: (Int, Int) -> Int) -> Int {
return method(a, b)
}
var result: Int
// 후행클로저는 함수 소괄호 외부에 클로저 구현 가능
result = calculate(a: 10, b: 10) {(left: Int, right: Int) -> Int in return left + right
}
// 반환타입 생략 가능
result = calculate(a: 10, b: 10) {(left: Int, right: Int) in return left + right}
// 단축 인자이름 생략 가능, in 키워드로 실행 구문과 선언 구문을 분리할 필요도 없으므로 in 키워드도 생략
result = calculate(a: 10, b: 10) {return $0 + $1}
// 암시적 반환 표현으로 return 생략
result = calculate(a: 10, b: 10) {$0 + $1}
// 고차함수 사용해보기
let aArray = [1, 3, 5, 7, 9]
// 기존 메소드
func addOne(num: [Int]) -> [Int] {
var answer = [Int]()
for i in num {
answer.append(i + 1)
}
return answer
}
print(aArray) // 출력값: [2, 4, 6, 8, 10]
// 1. 함수 대입
func addNum(num: Int) -> Int {
return num + 1
}
let bArray = aArray.map(addNum)
print(bArray) // 출력값: [2, 4, 6, 8, 10]
// 2. 클로저 대입
let cArray = bArray.map({(n1: Int) -> Int in return n1 + 1})
print(cArray) // 출력값: [3, 5, 7, 9, 11]
// 3. 클로저 단축, 후행클로저
let dArray = cArray.map {$0 + 1}
print(dArray) // 출력값: [4, 6, 8, 10, 12]
// 1. 클로저 사용해서 문자열로 변환
print(cArray.map {(num1: Int) -> String in return "\(num1)"})
// 출력값: ["3", "5", "7", "9", "11"]
// 2. 클로저 단축
print(dArray.map {"\($0)"})
// 출력값: ["4", "6", "8", "10", "12"]
@escaping
: 인자값으로 전달된 클로저를 전달해 두었다가 나중에 다른 곳에서도 실행할 수 있도록 허용해주는 속성이다.
@escaping Closure
: 클로저가 함수의 인자로 전달됐을 때, 함수의 실행이 종료된 후 실행되는 클로저이다.
// 함수를 파라미터로 전달받는 doSomething이라는 함수, 사용 가능
func doSomething(closure: () -> ()) {
closure()
}
// nonEscapingClosure를 외부 변수에 담을 경우
var nonEscapingClosure = {}
func doSomething(closure: () -> ()) {
nonEscapingClosure = closure // Error: Assigning non-escaping parameter 'closure' to an @escaping closure
}
이렇게 작성하면 nonEscapingClosure = closure() 구문에서 에러가 발생한다!
인자값으로 전달된 클로저는 함수 안에서만 사용 가능하기 때문이다.
💡 이유는?
함수의 인자값으로 전달된 클로저는 기본적으로 탈출불가하기 때문에 함수 내에서 직접 실행을 위해서만 사용해야하고 함수 내부에서도 변수나 상수에 대입할 수 없다.
💡 왜?
변수나 상수에 대입되면 내부 함수를 통한 캡처 기능을 활용해 클로저가 함수 바깥으로 탈출할 수 있기 때문이다. (탈출이란 함수 내부 범위를 벗어나서 실행되는 것을 의미한다.)
동일한 의미에서 인자값으로 전달된 클로저는 내부 함수에서 사용할 수 있도록 허용할 경우, 이 역시 캡처를 통해 탈출될 수 있기 때문에 중첩된 내부 함수에서 사용할 수도 없다.
💡 하지만!
코드를 작성하다 보면 클로저를 변수나 상수에 대입하거나 중첩 함수 내부에서 사용해야 할 경우도 있다. 이때 @escaping 속성을 사용하면 된다. @escaping Closure를 사용할때는 명시적으로 self
를 적어줘야한다.
var nonEscapingClosure = {}
func doSomething(closure: @escaping () -> ()) {
nonEscapingClosure = closure
}
💡 왜 인자값으로 전달되는 클로저의 기본 속성이 탈출불가하도록 설정된것일까?
이유는 해당 클로저가 탈출할 수 없다는 것은 컴파일러가 더 이상 메모리 관리상의 일들에 관여할 필요가 없다는 뜻이다. 또한, 이 클로저는 해당 함수가 끝나서 리턴되기 전에 호출될 것이 명확하기 때문에 클로저 내에서 self에 대한 약한 참조(weak reference)를 사용해야 할 필요가 없다.
그렇기때문에 컴파일러가 코드를 최적화하는 과정에서 성능이 향상된다.