티스토리 뷰

swift/문법

09. Swift 함수 - Closures

DevBee 2020. 10. 17. 16:04

지금부터 Closures 에 대해 알아보겠습니다.

 

1. Closures

Closures 란, 비교적 짧고 독립적인 코드 공간을 의미하며 Self-contained code blocks 이라고 합니다.

 

종류는 다음과 같습니다.
- Named Closures: Function, Nested Function (일반적으로 Function 을 의미)
- Unnamed Closures: Anonymous Function (일반적으로 Closures 를 의미)

 

Closures 또한, 함수와 같이 다음의 특징을 가지며, 따라서 함수와 완전히 호환됩니다.
- 변수나 상수에 전달할 수 있고
- 파라미터로 전달할 수 있고
- 함수의 리턴 값으로 사용될 수 있음

 

클로저 형태는 다음과 같습니다.

// closures 표현식 문법
{ (parameters) -> ReturnType in
    statements
}
// 가장 단순한 표현식
{ statements }
// 문자열을 출력하는 가장 단순한 클로저
// global scope 에서 단독으로 사용 불가 (상수에 저장해서 사용 가능)
let c = { print("Hello, Swift") }
c()


// 파라미터와 리턴형이 있는 클로저
let c2 = { (str: String) -> String in
    return "Hello, \(str)"
}
// 클로저에서는 Argument Label 을 사용하지 않음
let result = c2("Closure")
print(result)


// 클로저를 파라미터로 전달하기
typealias SimpleStringClosure = (String) -> String

func perform(closure: SimpleStringClosure) {
    print(closure("iOS"))
}
perform(closure: c2)

// 클로저 자체를 직접 전달하는 것도 가능
// in-line 클로저라고 함
perform(closure: {(str: String) -> String in
    return "Hi, \(str)"
})
// 클로저 활용 예 1
let products = [
    "MacBook Air", "MacBook Pro",
    "iMac", "iMac Pro", "Mac Pro", "Mac mini",
    "iPad Pro", "iPad", "iPad mini",
    "iPhone Xs", "iPhone Xr", "iPhone 8", "iPhone 7",
    "AirPods",
    "Apple Watch Series 4", "Apple Watch Nike+"
]

var proModels = products.filter({(name: String) -> Bool in
    return name.contains("Pro")
})
print(proModels)

proModels.sort(by: {(lhs: String, rhs: String) -> Bool in
    return lhs.caseInsensitiveCompare(rhs) == .orderedAscending
})
print(proModels)

// 결과
// ["MacBook Pro", "iMac Pro", "Mac Pro", "iPad Pro"]
// ["iMac Pro", "iPad Pro", "Mac Pro", "MacBook Pro"]


// 클로저 활용 예 2
print("Start")

DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: {
    print("End")
})

// 위와 같은 코드에서 문법 최적화를 한 경우
//DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
//    print("End")
//}

// 결과
// Start
// 5초 후에
// End

 

2. Syntax Optimization

Swift 는 최대한 단순하게 작성하는 것을 선호하므로 코드 최적화를 할 수 있습니다.

위 예제 중에 살펴보았던 filter 함수를 최적화 해보겠습니다.

products.filter({(name: String) -> Bool in
    return name.contains("Pro")
})

// 1. 파라미터 형식과 리턴형 생략 가능
products.filter({(name) in
    return name.contains("Pro")
})

// 2. Parameter name 은 Shortend Argument Name 으로 대체할 수 있고
//    이 경우 파라미터 이름과 in 키워드는 생략
//    $0: 첫번째 파라미터를 의미함
products.filter({
    return $0.contains("Pro")
})

// 3. 단일 return 문만 남아있는 경우 return 키워드 생략
products.filter({
    $0.contains("Pro")
})

// 4. closure parameter 가 마지막 parameter 라면
//    trailing closure 로 작성함
products.filter() {
    $0.contains("Pro")
}

// 5. 괄호 사이의 다른 parameter 가 없는 경우 괄호 생략
products.filter {
    $0.contains("Pro")
}

위와 같이 5가지 단계를 거쳐 코드 최적화를 할 수 있습니다.

 

3. Capturing Values

클로저에서 값을 캡처하는 방식을 알아보겠습니다.

 

종류에 따라 값을 캡처하는 방식이 다릅니다.

- global Function 는 값을 캡처하지 않습니다.
- Nested Function 의 경우는 값을 캡처합니다.
    - 자신을 포함하고 있는 함수 body 에 접근할 때 값을 캡처
- Annonymous Function
    - 클로저 외부에 있는 값에 접근할 때 값을 캡처

var num = 0
let c = { print("check point #1: \(num)") }
// 이때, num 은 클로저 외부의 값으로
// 클로저는 클로저 외부의 값을 내부에서 사용할 때 값을 캡처
c()

num += 1
print("check point #2: \(num)")

// 값을 캡처하는 방식
// 1. 값을 복사 (Objective-C)
// 2. 값을 참조 (Swift)
// 따라서 스위프트 클로저에서 캡처한 값을 변경하면
// 원래 값도 변경이 됨

let cv = {
    num += 1
    print("check point #3: \(num)")
}
cv()

print("check point #4: \(num)")


// 결과
// 0
// 1
// 2
// 2

 

4. Escaping Closure

클로저가 파라미터로 함수에 주어지면 함수의 실행 흐름 사이에 클로저가 실행되고 종료되어야 합니다. 하지만 함수의 흐름을 벗어나 클로저가 실행되고 종료되어야 하는 경우 이를 사용할 수 있습니다.

func performNonEscaping(closure: () -> ()) {
    print("Start")
    closure()
    print("End")
}
performNonEscaping {
    print("non-escaping closure")
}

// 함수의 실행이 종료된 후에도 시작하거나 종료할 수 있음
// 클로저의 실행 시작 시기와 종료 시기를 특정할 수 없음
func performEscaping(closure: @escaping () -> ()) {
    print("Start")
    
    var a = 12
    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
        closure()
        print(a)
        // 클로저가 캡처한 값 또한 클로저 실행 전까지 삭제되지 않고
        // 남아있을 수 있도록 함
    }
    
    print("End")
}
performEscaping {
    print("escaping closure")
}


// 결과
// Start
// non-escaping closure
// End
// Start
// End
// -- 3초 후에 --
// escaping closure
// 12

지금까지 Swift 의 Closure 에 대해 알아보았습니다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
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 31
글 보관함