소소한개발팁
article thumbnail
반응형

Closures

- Closure는 코드 블록으로 C와 Objective-C의 블록과 다른 언어의 람다와 비슷합니다.

  클로저는 어떤 상수나 변수의 참조를 캡처하여 저장할 수 있습니다.

  Swift는 이 캡처와 관련한 모든 메모리를 자동으로 처리합니다.

 

- 클로저의 형태

  전역 함수 : 이름이 있고 어떤 값도 캡처하지 않는 클로저

  중첩 함수 : 이름이 있고 관련한 함수로부터 값을 캡처할 수 있는 클로저

  클로저 표현 : 경량화된 문법으로 쓰이고 관련된 문맥으로부터 값을 캡처할 이름 이 없는 클로저

 

 

Closure Expression Syntax  

- closure의 기본적인 예시입니다.

 

{ (parameters) -> return type in
   statements
}

 

- closure를 적용하지 않은 변수와 적용시킨 변수의 예시입니다.

 

func backward(_ s1: String, _ s2: String) -> Bool {
   return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
   return s1 > s2
})

 

- closure 문의 길이가 짧을 경우 아래와 같이 축약할 수 있습니다.

 

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )

 

- closure 문 안에서 타입 유형을 지정하지 않아도 Swift 유형 추론으로 인하여 처리됩니다. 

 

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

 

- closure 문 안에서 return 값을 생략할 수 있습니다.

 

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

 

- Shorthand Argument Names을 사용하여 아예 변수명을 생략할 수 있습니다.

 

reversedNames = names.sorted(by: { $0 > $1 } )

 

- 또는 단순히 Operator Methods를 사용해서 표현하더라도 유추를 통하여 처리될 수 있습니다.

 

reversedNames = names.sorted(by: >)

 

 

Trailing Closures 

- 아래는 Trailing Closures의 예시입니다.

 

func someFunctionThatTakesAClosure(closure: () -> Void) {
 // function body goes here
}

// Here's how you call this function without using a trailing closure:

someFunctionThatTakesAClosure(closure: {
// closure's body goes here
})

// Here's how you call this function with a trailing closure instead:

someFunctionThatTakesAClosure() {
// trailing closure's body goes here
}

 

- 아래는 Trailing Closures를 이용하여 배열안에 있는 숫자를 문자열로 출력하는 예시입니다.

 

let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]

let strings = numbers.map { (number) -> String in
  var number = number
  var output = ""
  repeat {
    output = digitNames[number % 10]! + output
    number /= 10
  } while number > 0
    return output
}
// strings is inferred to be of type [String]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]

 

 

- 아래는 Trailing Closures 를 이용하여 사진을 로드할 때 사용하는 함수를 처리하는 예시입니다.

 

func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
   if let picture = download("photo.jpg", from: server) {
     completion(picture)
   } else {
     onFailure()
   }
}

loadPicture(from: someServer) { picture in
   someView.currentPicture = picture
} onFailure: {
   print("Couldn't download the next picture.")
}

 

 

Capturing Values 

- 아래는 단순히 값을 반환하는 게 아닌 함수를 반환하는 것의 예시입니다.

 

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

 

- 아래는 위의 함수를 반환하는 함수를 선언하고 사용한 예시입니다.

 

let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30.

 

- 아래는 위의 함수를 반환하는 함수를 인자 값을 변경하여 새로 선언하고 사용한 예시입니다.

 

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// returns a value of 7

 

- 원래의 함수를 실행시켰을 때 값이 변하지 않았던 것을 확인할 수 있습니다.

 

incrementByTen()
// returns a value of 40

 

 

 

Closures Are Reference Types 

- alsoIncrementByTen라는 상수가 incrementByTen 상수와 동일하기 때문에 같은 곳을 참조하는 것을 확인할 수 있습니다.

 

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50

incrementByTen()
// returns a value of 60

 

 

Escaping Closures 

- 클로저를 함수의 파라미터로 넣을 수 있는데, 함수 밖에서 실행되는 클로저 예를 들면, 비동기로 실행되거나 completionHandler로 사용되는 클로저는 파라미터 앞에 @escaping이라는 키워드를 명시해야 합니다.

 

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}

 

- @escaping를 사용하는 클로저에서는 self를 명시적으로 언급해야 합니다.

 

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"
class SomeOtherClass {
 var x = 10
 func doSomething() {
  someFunctionWithEscapingClosure { [self] in x = 100 }
  someFunctionWithNonescapingClosure { x = 200 }
 }
}

struct SomeStruct {
  var x = 10
   mutating func doSomething() {
    someFunctionWithNonescapingClosure { x = 200 } // Ok
    someFunctionWithEscapingClosure { x = 100 } // Error
   }
}

 

 

Autoclosures

- 자동 클로저는 인자 값이 없으며 특정 표현을 감싸서 다른 함수에 전달 인자로 사용할 수 있는 클로저입니다. 자동클로저는 클로저를 실행하기 전까지 실제 실행이 되지 않습니다. 그래서 계산이 복잡한 연산을 하는데 유용합니다. 그 이유는 실제 계산이 필요할 때 호출되기 때문입니다. 아래는 예제입니다.

 

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

let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"

print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// Prints "4"

 

-  자동 클로저는 적힌 라인 순서대로 바로 실행되지 않고, 실제 사용될 때 지연 호출됩니다. 아래는 자동 클로저를 함수로 넣을 경우의 예시입니다.

 

// 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!"

 

- serve함수는 인자가 없고, String을 반환하는 클로저를 받는 함수입니다. 이 함수를 실행할 때는 serve(customer: { customersInLine.remove(at: 0) } ) 이와 같이 클로저{ customersInLine.remove(at: 0) }를 명시적으로 직접 넣을 수 있습니다. 아래는 @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!"

 

- 클로저 인자에 @autoclosure를 선언하면 함수가 이미 클로저 인 것을 알기 때문에 리턴 값 타입과 같은 값을 넣어줄 수 있습니다. 아래는 @escaping과 @autoclosure를 같이 사용한 예시입니다.

 

// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
 customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
 print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"

 

- collectCustomerProviders함수의 인자 customerProvider는 @autoclosure이면서 @escaping로 선언되었습니다. @autoclosure로 선언됐기 때문에 함수의 인자로 리턴값 String만 만족하는 customersInLine.remove(at: 0) 형태로 함수 인자에 넣을 수 있고, 이 클로저는 collectCustomerProviders함수가 종료된 후에 실행되는 클로저 이기 때문에 인자 앞에 @escaping 키워드를 붙여주었습니다.

 

 

 

내용은 https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html를 보면서 작성하였고 원문으로 작성된 내용을 옮기다 보니 이상한 부분이 있을 수 있습니다. 자세한 내용은 위의 링크를 확인해주시기 바랍니다.

반응형
profile

소소한개발팁

@개발자 뱅

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!