[Swift 문법] - Closure_02

sun02·2021년 7월 12일
0

100 days of Swift 

목록 보기
6/40

1. Using closures as parameters when they accept parameters

함수로 전달한 클로져는 자체 매개변수도 받아들일 수 있다.

“매개변수를 받지 않고 아무것도 반환하지 않는다” 를 의미하는 ( ) -> Void 를 사용해왔지만

func travel (action : (String) -> Void) {
    print(“I’m getting ready to go.”)
    action(“London”)
    print(“I arrived!”)
}
  • 다음과 같이 클로져가 받아들여야하는 매개변수의 타입으로 ()를 채운 travel()함수를 작성할 수 있다.
Travel { (place: String) in
    Print(“I’m going to \(place) in my car”)
}
  • 이제 후행 클로져 구문을 사용해서 travel()을 호출할 때 클로져 코드는 문자열을 받아들여야한다.

2. Using closures as parameters when they return values

우리는 “매개변수를 받지 않고 어느 것도 반환하지 않음” 이라는 의미로 ( ) -> Void를 사용해왔지만
Void를 클로져가 반환하기를 원하는 값의 자료 타입으로 교체할 수 있다.

이를 보여주기 위해, 우리는 클로져를 유일한 매개변수로 받아들이는 함수

func travel(action: (String) -> String) {
    print(“I’m getting ready to go.”)
    let description = action(“London”)
    print(“I arrived!”)
}
  • 문자열을 받아들이고 문자열을 반환하는 클로져를 매개변수로 받아들이는 travel() 이다.
travel { (place:String) -> String in
    return “I’m going to \(place) in my car”
}
  • 후행 클로져 구문을 사용하여 travel()을 호출할 때 우리의 클로져는 문자열을 받아들이고 문자열을 반환하도록 요구된다.

3. Shorthand parameter names

위에서 만든 travel() 함수는 한 개의 매개변수를 받아들이는데
이것은 하나의 매개변수를 받아들이고 문자열을 반환하는 클로져이다.

func travel(action: (String) -> String) {
    print(“I’m getting ready to go.”)
    let description = action(“London”)
    print(“description)
    print(“I arrived!”)
}
  • 그 클로져는 두 개의 print()호출 사이에서 실행된다.
travel { (place: String) -> String in
   return “I’m going to \(place) in my car”
}
  • 우리는 다음과 같이 travel()을 호출할 수도 있다.
travel { place -> String in
    return “I’m going to \(place) in my car”
}
  • Swift는 클로져의 매개변수가 문자열이어야만 하는 것을 이미 알기 때문에 다음과 같이 생략할 수 있다.
travel { place in
   return “I’m going to \(place) in my car”
}
  • 또한 그 클로져가 문자열만 반환할 수 있는 것을 알기 떄문에 이것도 생략할 수 있다
travel { place in
   “I’m going to \(place) in my car”
}
  • 이 클로져는 반환해야하는 값을 나타내는 한 줄의 코드만 포함하고 있기 때문에 Swift는 return 키워드도 생략 가능하게 한다.

스위프트는 더 짧게 작성할 수 있는 단축 구문을 갖고 있다.

travel {
“I’m going to \($0) in my car”
}
  • place in 을 작성하는 대신에 스위프트가 클로져 매개변수에 대한 자동 이름을 제공하도록 할 수 있다.
    이들은 달러 기호($)로 이름 지어지고 0부터 세어진다.

4. Closures with multiple parameters

두 가지 매개변수를 사용하는 다른 클로져 예제를 적어볼 것이다.
travel() 함수는 이제 여행하는 장소와 가는 속도를 지정하는 클로져를 필요로한다.

func travel(action: (String, Int) -> String ) {
    print(“I’m getting ready to go.”)
    let description = action(“London”, 60)
    print(description)
    print(“I arrived!”)
}
  • 따라서 매개변수 타입을 위해 (String, Int) -> String 을 사용해야한다.
travel { 
   “I’m goingt to \($0) at \($1) miles per hour.”
}
  • 후행 클로져 구문과 단축 클로져 매개변수 이름을 사용하여 호출하였다.
  • 두개의 매개변수를 받아들이기 때문에 $0 과 $1을 사용하였다.

5. Returning closures from functions

함수에 클로져를 전달한 것과 같은 방식으로 클로져를 함수로부터 반환받을 수도 있다.

이 구문은 ‘->’ 를 두 번 사용하기 때문에 조금 혼란스러울 수 있다.
함수의 반환 값을 특정하기 위해 한 번,
클로져의 반환 값을 특정하기 위해 한 번 사용된다.

func travel() -> (String) -> Void {
    return {
          print(“I’m going to \($0)”)
    }
}
  • 매개변수를 받아들이지 않고 클로져는 반환하는 travel() 함수이다.
  • 반환되는 클로져는 string으로 호출될 것이고 아무것도 반환하지 않을 것이다.
let result = travel()
result(“London”)
  • 클로져를 반환받기위해 travel()을 호출할 수 있고 클로져를 함수처럼 호출한다.
let result2 = travel()(“London”)
  • 이것도 기술적으로 허용가능하다 – 비록 추천하지는 않지만! – travel() 로부터 반환 값을 바로 호출하기

6. Capturing values

클로져 안에서 외부 값을 사용할 때 Swift는 이것을 캡쳐한다 – 클로져와 함께 저장하므로 더 이상 존재하지 않더라도 수정될 수 있다.

func travel() -> (String) -> Void {
    return {
          print(“I’m going to \($0)”)
    }
}
  • 지금 travel() 함수는 클로져를 반환하고 반환된 클로져는 매개변수로서 문자열을 받아들이고 아무것도 반환하지 않는다.
let result = travel()
result(“London”)
  • 클로져를 돌려받기 위해 travel()을 호출하고 그 클로져는 자유롭게 호출한다.
func travel() -> (String) -> Void {
    var counter = 1
    return {
          print(“\(counter). I’m going to \($0)”)
       counter += 1
     }
}
  • 클로져 안에서 사용되는 값을 travel()안에서 만들어 낼 때 클로져 캡쳐가 발생한다
  • 반환되는 클로져가 얼마나 자주 호출되는지 알고 싶을 수 있다.

Counter 변수가 travel()안에서 만들어졌더라도 이것은 클로져에 의해 캡쳐되기 때문에 그 클로져에서 계속 살아있다.

result(“London”)
result(“London”)
result(“London”)
  • 따라서, 우리가 result(“London”)을 여러 번 부른다면, counter 의 값은 계속해서 증가한다.

Swift의 closure들은 왜 값을 캡쳐하는가?

Swift closure의 가장 중요한 특징 중 하나는 사용하는 값을 캡쳐한다는 것이다.
동시에, Siwft의 가장 혼란스러운 특징 중 하나는 그들이 사용하는 값을 캡쳐한다는 것이다.
값을 캡쳐함으로써 closure가 필요할 때마다 그 데이터에 항상 접근할 수 있다는 것은 swift가 그 클로져를 안전하게 실행할 수 있다는 것을 의미한다.

랜덤 숫자를 발생시키는 함수를 작성해보자 : 한 줄에 같은 숫자를 두 번 반환하지는 않는다.

이 논리는 다음과 같다

  • 초기 값 0에서 시작하고 previousNumber에 저장된다.
  • 새로운 랜덤 숫자를 저장할 newNumber를 만든다.
  • Int.random(in:1..3) 과 함께 repeat 루프문을 사용한다.
  • 위 루프문의 조건은 newValue == previousNumber 이다 –> 새로운 랜덤 숫자가 이전의 랜덤 숫자와 같을 때까지 그 루프는 계속해서 새로운 숫자를 뽑는다.

루프가 한번 끝나면 이는 숫자가 이전 숫자와 다르다는 것을 의미하고
previousNumber를 newNumber로 업데이트하고 반환한다.
이것은 클로져로 반환되기 때문에 우리는 어디에서나 랜덤 숫자를 발생시킬 수 있다.

func makeRandomNumberGenerator() -> () -> Int {
    return {
       var previousNumber = 0
       var newNumber : Int

       repeat {
           newNumber = Int.random(in : 1…3)
      } while newNumber == previousNumber

      previousNumber = newNumber
      return newNumber
    }
}
  • 다음과 같이 함수를 작성할 수 있다.
let generator = makeRandomNumberGenerator() 

for _ in 1…10 {
    print(generator())
]
  • 다음과 같이 이 코드를 테스트할 수 있다.

실행해보면 계획대로 진행되지 않는다 – 나는 여러 번 반복되는 숫자를 받았다, e.g. 1,2,1,1,3,1,3,3,3,2. 와 같이

나는 반복되지 않는 숫자를 원했다. 문제는 다음과 같다.

return {
     var previousNumber = 0
     var newNumber : Int
}
  • 이것이 반환되는 값인데 이는 우리가 generator()를 호출할 때마다 클로져가 새로운 previousNumber 변수를 만들고 0으로 값을 세팅한다는 것이다 – 이전의 값을 전혀 저장하지 않는다.
var previousNumber = 0

return {
    var newNumber : Int
}
  • var previousNumber = 0 줄을 return 이전으로 옮긴다.

이 코드에서는 원하는대로 실행되는 것을 볼 수 있다 – 반복되지 않는 매번 새로운 랜덤 값을 받는다.

여기서 클로져 캡쳐의 힘을 알 수 있다.
previousNumber 변수가 클로져 안에 있지 않지만
클로져가 실행되기 위해 그 변수가 존재해야하기 때문에 이것은 캡쳐된다.
즉, Swift는 makeRandomNumberGenerator() 의 실행이 완료되고 일반적으로 파괴된 이후에도 그 변수가 존재를 유지하도록 한다.

이것은 두 가지 이유에서 중요하다.

  1. 만약 그 변수가 파괴되면, 클로져는 더이상 실행될 수 없다. 클로져는 previousNumber를 읽고 실행하려고 노력하기 떄문에 스위프트의 변수를 살아있는 상태로 유지하는 것이 클로져가 의도한 대로 작동하도록 보장한다.
  2. 그 변수가 클로져 밖에서 생성되었더라도 클로져에 의해 사용된다. 이는 클로져가 실행될 때마다 0으로 세팅되기보다 한 번만 세팅하면 된다는 것을 의미한다. 그렇기 때문에 이전 값을 정확하게 저장할 수 있다.

출처

0개의 댓글