IOS/Swift

[TIL] Swift. Closure

Closure 기본 개념

  • 이름이 없는 매서드라고 생각할수있다( = 이름이 없는 함수)

  • 안쪽 Scope는 바깥쪽 Scope를 참조할수 있지만 바깥 Scope는 안쪽을 참조할수 없다.

  • Scope안쪽에서 생성된 함수가 바깥쪽의 변수를 참조 할 수 있고, 만약 안쪽에서 생성된 함수가 Scope 밖에서 사용될 경우 안쪽에서 참조 되었던 변수를 바깥에서도 참조 할 수 있다

  • 아주 정확히는 함수는 Closure의 한가지 타입

  • Closure의 3가지 타입

    • Global 함수
    • Nested 함수
    • closure expressions
  • 함수는 func가 필요하지만 클로저는 func키워드가 필요없다

  • 함수와 클로저는 First Class Type이다

    • First Class Type이란
      • 변수에 할당할 수 이다
      • 인자로 받을수 있다
      • 리턴 할 수 있다
  • 자주 쓰이는 Closure의 형태

    • Completion Block
      • 어떤 테스크가 완료 되었을 때 클로저가 실행
      • 예를 들어 데이터 로딩이 끝나고 클로저를 실행하고 싶은 경우
    • Higher Order Functions
      • =인풋으로 함수를 받을수 있는 유형의 함수 = 고계함수

Closure Expression

{ (parameters) - > return type in statement }

Trailing Closure 후위 클로저

// Trailing Closure 후위 클로저

func somesimpleFunction(message: String, simpleClosure: () -> Void) {
    print("함수에서 호출이 되었어요, 메세지는 \(message)")
    simpleClosure()
}

somesimpleFunction(message: "로나로나 메로나, 코로나 극혐", simpleClosure: {
    print("헬로 코로나 from Closure")
})

somesimpleFunction(message: "로나로나 메로나, 코로나 극혐") {
    print("헬로 코로나 from Closure")
}


두번째 코드와 세번째 코드는 동일하게 작동한다 3번째를 추천한다.
마지막 함수실행의 파라미터에 함수이름이 없어도 그냥 실행이되네... 신기하다

//추가 예시
//기본타입 인라인 클로저
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

// 축약형
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

//암시적 반환형
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

// 인자 이름 축약
reversedNames = names.sorted(by: { $0 > $1 } )

// 연산자 메소드
reversedNames = names.sorted(by: >)
  • 클로저를 활용하는 매서드 예시
    • sorted(by: )
//기본타입 인라인 클로저
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

// 축약형
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

//암시적 반환형
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

// 인자 이름 축약
reversedNames = names.sorted(by: { $0 > $1 } )

// 연산자 메소드
reversedNames = names.sorted(by: >)

Capturing Values 값 캡쳐

###사용시 주의사항
만약 클로저를 어떤 클래스 인스턴스의 프로퍼티로 할당하고 그 클로저가 그 인스턴스를 캡쳐링하면
강한 순환참조에 빠지게 됩니다
. 즉, 인스턴스의 사용이 끝나도 메모리를 해제하지 못하는 것이죠. 그래서 Swift는 이 문제를 다루기 위해 캡쳐 리스트(capture list)를 사용합니다. 더 많은 정보는 클로저의 강한 참조 순환을 참조하세요.

1. 중첩함수 형태(nested function)

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

//실행
let incrementByTen = makeIncrementer(forIncrement: 10)

//출력
incrementByTen()
// 값으로 10을 반환합니다.
incrementByTen()
// 값으로 20을 반환합니다.
incrementByTen()
// 값으로 30을 반환합니다.
  • 인자는 amount Int이고 → Void → Int 가 출력되는 함수입니다.
  • 클로저는 레퍼런스 타입입니다.

Escaping Closures

  • 클로저를 함수의 파라미터로 넣을수 있는데 함수 밖(함수가 끝나고) 에서 실행되는 클로저 예를들어, 비동기로 실행되거나 completionHandler로 사용되는 클로저는 파라미터 타입 앞에 @escaping 이라는 키워드를 명시해야 합니다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

//예시
func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()    // 함수 안에서 끝나는 클로저
}

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 } // 명시적으로 self를 적어줘야 합니다.
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"

completionHandlers.first?()
print(instance.x)
// Prints "100"

정리

  • 클로저가 함수의 인자로 전달되고 함수 외부에서 실행되면 Escaping Closure 라고 합니다.
  • Escaping Closure를 이용하여 함수가 종결된 이 후 해당 클로저가 실행되는 것을 보장 받을 수 있습니다.
  • @escaping으로 표기된 클로저는 외부 값을 참조할 때  self를 명시해야 합니다.

Autoclosures 자동 클로저

NOTE 자동클로저를 너무 남용하면 코드를 이해하기 어려워 질 수 있습니다. 그래서 문맥과 함수 이름이 autoclosure를 사용하기에 분명해야 합니다.
  • 자동클로저는 인자 값이 없으며 특정 표현을 감싸서 다른 함수에 전달 인자로 사용할 수 있는 클로저입니다. 자동클로저는 클로저를 실행하기 전까지 실제 실행이 되지 않습니다. 그래서 계산이 복잡한 연산을 하는데 유용합니다. 왜냐면 실제 계산이 필요할 때 호출되기 때문입니다. 예제를 보면서 무슨 뜻인지 알아 보겠습니다.

1. 변수형 자동 클로저

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"

// 여기서 {} 안의 remove(at:?) 은 지정된 배열의 ? 여기를 리턴합니다
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"
// 흥미로운 점은 클로저가 선언된 시점을 지나더라도 배열의 구조가 그대로 라는 것입니다.

// 따라서 Chris가 리턴되고 배열의 0번째에서 삭제 됩니다.
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// Prints "4"

2. 함수형 자동 클로저

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!"

위 예제에서는 함수의 인자로 클로저를 넣을 때 명시적으로 넣는 경우에 대해 알아 보았습니다. 위 예제를 @autoclosure키워드를 이용해서 보다 간결하게 사용할 수 있습니다. 예제를 보시겠습니다.

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"

3. 자동클로저와 탈출 클로저의 복합 사용

// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []        //  클로저를 저장하는 배열을 선언
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
} // 클로저를 인자로 받아 그 클로저를 customerProviders 배열에 추가하는 함수를 선언
collectCustomerProviders(customersInLine.remove(at: 0))    // 클로저를 customerProviders 배열에 추가
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."        // 2개의 클로저가 추가 됨
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")    // 클로저를 실행하면 배열의 0번째 원소를 제거하며 그 값을 출력
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"
  • collectCustomerProviders함수의 인자 customerProvider는 @autoclosure이면서 @escaping로 선언되었습니다. @autoclosure로 선언됐기 때문에 함수의 인자로 리턴값 String만 만족하는 customersInLine.remove(at: 0)형태로 함수 인자에 넣을 수 있고, 이 클로저는 collectCustomerProviders함수가 종료된 후에 실행되는 클로저 이기 때문에 인자 앞에 @escaping 키워드를 붙여주었습니다.
자료 출처
https://jusung.gitbook.io/the-swift-language-guide/language-guide/07-closures

 

'IOS > Swift' 카테고리의 다른 글

UserDefault 깔끔하게 쓰는 법 공유합니다  (0) 2022.03.09
Codable & Encodable  (0) 2020.10.21
[TIL] Swift. Type Casting(feat. is, as)  (0) 2020.10.18
[TIL] Swift. GCD (Grand Central Patch)  (0) 2020.10.17
[TIL] Swift. Frame and Bounds  (0) 2020.10.15