Swift 정리 - Closures

김세영·2021년 12월 19일
0

Swift Doc 정리

목록 보기
6/17
post-thumbnail

클로저는 코드 블록으로, C와 Objective-C의 블럭 또는 다른 언어의 람다(Lambda)와 비슷

{ (parameters) -> ReturnType in
    statements
}

클로저의 형태

  • 전역 함수: 이름이 있고 어떤 값도 캡쳐하지 않는 클로저
  • 중첩 함수: 이름이 있고 관련한 함수로부터 값을 캡쳐할 수 있는 클로저
  • 클로저 표현: 경량화된 문법으로 쓰여지고 관련 컨텍스트에서 값을 캡쳐할 수 있는, 이름이 없는 클로저

클로저의 최적화

  • 컨텍스트에서 Parameter Type과 Return Type의 추런
  • 단일 표현 클로저에서의 암시적 반환
  • 축약된 인자 이름
  • 후위 클로저 문법

정렬 메서드로 알아보는 클로저의 축약

스위프트 표준 라이브러리에는 sorted(by:)라는, 배열 값을 정렬하는 메서드가 존재
by에 정렬 기준을 적은 클로저를 넣으면 그 기준대로 정렬이 된다.

let someInts = [3, 2, 6, 4, 1, 5]

func desc(_ lhs: Int, _ rhs: Int) -> Bool {
    return lhs > rhs
}

let descInts = someInts.sorted(by: desc)
// descInts : [6, 5, 4, 3, 2, 1]

이 때 by의 형태는 (Int, Int) -> Bool 라는 것을 알 수 있다.

  • 클로저 표현 문법
let descInts = someInts.sorted(by: { (lhs: Int, rhs: Int) -> Bool in
    return lhs > rhs
})
  • 문맥에서 타입 추론
    someInts에서 사용되는 sorted(by:)는 타입이 (Int, Int) -> Bool 이라는 것을
    알 수 있기 때문에, 타입을 생략하고 사용 가능
let descInts = someInts.sorted(by: { lhs, rhs in return lhs > rhs })
  • 단일 표현 클로저에서의 암시적 반환
    단일 표현 클로저에서는 return 키워드를 생략 가능
let descInts = someInts.sorted(by: { lhs, rhs in lhs > rhs })
  • 인자 이름 축약
    클로저는 인자 값이 앞에서부터 $0, $1, ... 으로 축약된
    축약 인자 이름을 제공
let descInts = someInts.sorted(by: { $0 > $1 })
  • 연산자 메서드
    Int, String 등 해당 타입끼리 비교할 수 있는 비교 연산자가 있으므로, 그냥 연산자 사용 가능
let descInts = someInts.sorted(by: >)

Trailing Closure

함수의 마지막 인자에 클로저가 있다면
클로저를 블록 형태로 사용하는 후위 클로저를 사용할 수 있다.

func functionWithClosure(closure: () -> Void) { ... }

functionWithClosure(closure: { 
    ... 
})

functionWithClosure() {
    ...
}

// 후위 클로저일 때 ()를 생략 가능
functionWithClosure {
    ...
}

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)

이 때 incrementByTenincrementer() 이라는 클로저(함수)를 반환함.

incrementByTen() // 10
incrementByTen() // 20
incrementByTen() // 30

runningTotalamountincrementer()의 내부에 변수로 저장되어 있지 않지만, 계속 값이 증가한다.
이는 runningTotal, amount가 캡쳐링되어서 변수를 공유하기 때문

  • 강한 참조 순환
    추후 ARC에서 정리

  • Closure : Reference Type
    위 예제에서, incrementByTen()let으로 선언되었지만
    해당 함수(클로저)의 Reference가 할당되기 때문에 runningTotal, amount를 변경 가능

let anotherIncrementByTen = incrementByTen
anotherIncrementByTen() // 40

anotherIncrementByTen 또한 makeIncrementer(amount:)의 참조를 가지게 되므로
기존 incrementByTen()에서 만들어 놓았던 30이란 값을 가지고 계산을 함.

@escaping

클로저를 함수의 파라미터로 넣을 때, 함수가 끝나고(함수 밖에서) 실행되는 클로저는 @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)
// 200

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

@autoclosure

함수의 인자로 전달되는 코드를 감싸서, 자동으로 클로저로 만들어주는 키워드
전달되는 코드는 인자가 없고 반환값만 있어야 한다.

자동 클로저는 클로저를 호출하기 전까지 실행되지 않으므로,
복잡한 연산을 할 때 유용

하지만 예제는 간단한 연산

  • @autoclosure 없을 때
func printAdds(_ closure: () -> Int) {
    print("Add: \(closure())")
}

printAdds({ 2 + 3 })
  • @autoclosure 있을 때
func printAdds(_ closure: @autoclosure () -> Int) {
    print("Add: \(closure())")
}

printAdds( 2 + 3 ) // { } 사라짐

위에서 보는 것과 같이, @autoclosure을 남용하면 코드를 이해하기 어려워지므로
적재적소에 사용해야 한다.

@escaping이 필요한 경우 두 키워드 모두 붙여주어야 함

func function(_ closure: @autoclosure @escaping () -> String) {
    ...
}
profile
초보 iOS 개발자입니다ㅏ

0개의 댓글