swift 심화(8)

EN·2022년 10월 6일

요번에는 클로저의 심화 지식들에 대해 알아보자.
클로저

import Foundation
func functionA() -> () -> Int{
  var counter = 0
  func functionB() -> Int{
    return counter + 10
  }
  return functionB
}

let myClosure = functionA()
let result = myClosure()
  • functionA에서의 ()->Int 이게 리턴형이다.
  • functionB는 중첩된 함수라고 한다.
  • functionB는 counter변수를 "잡고 있다” 또는 “가두고 있다” 라고 말할 수 있으므로 전통적인 컴퓨터공학 용어인 클로저로 간주된다.
  • 클로저는 기능 블록이다.

클로저 표현식

  • 중첩 함수에서 소개된 중첩 함수는 더 큰 함수에 부분으로 자체 포함된 코드 블럭의 이름을 지정하고 정의하기 편리한 수단.
  • 그러나 완전한 선언과 이름없이 함수와 유사한 구조의 짧은 버번을 작성하는 것이 때떄로 유용함. 함수를 하나 이상의 인자로 사용하는 함수 또는 메서드로 작업할 때 특히 그럼.
  • 클로저 표현식은 간단하고 집중적인 구문으로 인라인 클로저로 작성하는 방법임. 클로저 표현식은 명확성이나 의도를 잃지 않고 짧은 형태로 클로저를 작성 하기위한 몇가지 구문 최적화를 제공한다

정렬 메서드

import Foundation
let names : [String] = ["Lion", "Tiger", "Elephant]
func backward(_ s1: String, _ s2: String) -> Bool{
  return s1 > s2
}

var reversedNames = names.sorted(by: backward)
//var reversedNames = names.sorted(by: >) <- 이런것도 된다.
print(reversedNames)

클로저 표현구

//전역함수 backward와 똑같이 작동하는 클로저 표현식을 만들어
//sorted의 by: 매개변수로 인라인 클로저라는 구현법으로 바로 써서 보낸다.
reversedNames = names.sorted(by: {(s1: String, s2: String) -> Bool in
  return s1 > s2
})
print(reversedNames)
  • 근데 이것보다 좀 더 짧게 할 수 있다.
//더 짧은 표현
//배열이 String 문자열들로 채워진 걸 아니까
reversedNames = names.sorted(by: {(_ s1, _ s2) -> Bool in return s1 > s2})
print(reversedNames)
  • 위에를 컨텍스트로 타입 유추라고 한다. 좀 더 줄이면
//더 짧게
reversedNames = names.sorted(by: { s1, s2  in s1 > s2})
print(reversedNames)
//이렇게도 된다.
reversedNames = names.sorted(by: {$0 > $1})
print(reversedNames)
  • sorted말고 map, refuce, filter 같은 것들도 있다.이런걸 일급 함수라고 한다.
  • 후행 클로저는 클로저가 길어서 한줄로 인라인으로 작성이 불가능할 때 유용하다. 예를 들어 Array타입은 단일 인자로 클로저 표현식을 가지는 map메서드가 있다. 이 map 메서드는 어레이의 각 요소들을 하나씩 꺼내서 어떻게 처리할 것인지를 지정한다.
  • 그리고 후행클로저에서는 클로저가 지원하는 함수 바로 뒤에 있는 클로저의 기능을 깔끔하게 캡슐화한다. 전체 클로저를 메서드의 바깥 소괄호로 감쌀 필요가 없다.
let strings: [String] = numbers.map{(number) -> String in 
  //매개변수로 가져온 number는 상수이기 때문에, 변수로 다시 만들어줘야 변경 가능.
  var n = number
  var output = ""
  repeat {
    if let name = digitNames[number % 10]{
      output = name + output
    }else{
      output = "?" + output
    }
  }while n > 0
  return output
}
print(strings)
  • digitNames[number%10]이 null일 가능성을 피하기 위해 if let 사용
  • 요즘은 ?? 문법을 이용하기도 한다.
repeat {
    if let name = digitNames[number % 10] ?? "?"
    output = name + output
    n /= 10
  }while n > 0

캡처값

  • 클로저는 정의된 둘러/싸인 컨텍스트에서 상수와 변수를 캡처할수 있다.
  • 예를 들어 makeIncrementer함수 안에 incrementer함수가 있는데, 이때
  • incrementer함수는 둘러싸인 컨텍스트에 runningTotal과 이 함수의 파라미터인 amount 2개의 값을 캡쳐한다.
func makeIncrementer(forIncrement amount: Int) -> () -> Int{
  var runningTotal: Int = 0
  func incrementer()-> Int{
    runningTotal += amount
    return runningTotal
  }
  return incrementer
}
let increFunc = makeIncrementer(forIncrement: 10)
print("\(increFunc())")//10
print("\(increFunc())")//20
  • 스윞은 더이상 필요하지 않을 때 변수를 처리하는 것과 관련된 모든 메모리 관리도 처리합니다.(걍 알아서 처리한다)

클로저는 참조 타입이다.

이스케이프 클로저

  • 함수에 인자로 클로저를 전달하지만 함수가 반환된 호출되는 클로저를 함수를 탈출하다 라고 말한다. 클로저를 파라미터로 갖는 함수를 선언할 때 이 클로저는 탈출을 허락한다는 의미로 파라미터의 타입 전에 @escaping을 작성할수 있다.
  • 클로저가 탈출할 수 있는 한가지 방법은 함수 바깥에 정의된 변수에 저장되는 것이다. 예를 들어 비동기적 작업을 시작하는 대부분의 함수는 완료 핸들러로 클로저를 사용함. 이 함수는 작업을 시작한 후에 반환되지만 작업이 완료될때까지 클로저가 호출되지 않는다. 클로저는 나중에 호출하려면 탈출해야 함.
var completionHandlers: [() -> Void] = []
func someFunctionWithExcapingClosure(completionHandler: @excaping () -> Void){
  completionHandlers.append(completionHandler)
}
  • some뭐시기 함수는 인자로 클로저를 갖고 있고, 함수 바깥에 선언된 배열에 추가한다. 함수의 파라미터에 @escaping을 표시하지 않으면 컴파일 시 에러가 발생한다.
  • self를 참조하는 이스케이프 클로저는 self가 클래스의 인스턴스를 참조하는 경우 특별한 고려가 필요하다.
  • https://jusung.github.io/Escaping-Closure/

뮤테이팅

import Foundation

class SomeClass{
  var name: String = ""
  func changeName(newName : String){
    self.name = newName
  }
}

var somethingByClass = SomeClass()
somethingByClass.changeName(newName: "ned")
print("\(somethingByClass.name)")

struct SomeClass{
  var name: String = “"

  mutating func changeName(newName : String){
    self.name = newName
  }
}

var somethingByStruct = SomeStruct()
somethingByStruct.changeName(newName: "뽀로로")
print("\(somethingByStruct.name)")
  • struct는 값타입이기 때문에 인스턴스 메서드로는 프로퍼티를 변경 불가.
  • 이때 mutating을 써서 할 수 있게 해야함.
  • struct로 stack을 구현할 때 pop 연산이나 push 연산을 구현하려면 이름 앞에 mutating을 붙여야함.
profile
iOS/JUJITSU

0개의 댓글