[swift] closure

김개발소발·2022년 2월 17일
0

swift

목록 보기
1/1

파라미터 등으로 전달할수 있는 자체 코드를 가지고 있는 블럭

캡쳐 : 클로저를 감싸고 있는 블럭의 변수 상수 등 컨텍스트의 범위의 값을 독립적으로 참조할 수 있다.
⇒ 클로저를 감싸고 있는 블럭의 메모리가 해제되어도 클로저는 해당 값,변수를 참조할 수 있다.

전역(global), 중첩?(nested) 함수도 클로저의 하나다

전역 함수는 이름을 가지고 있지만, 외부의 어떤 참조, 값도 없는 클로저

중첩 함수는 이름을 가지고 있고, 블럭을 감싸는 외부의 참조, 값을 캡쳐하고 있는 클로저

클로저의 기본 표현식 이름이 없는 무명 함수에 함수 블럭이 선언된 블럭의 값을 캡쳐하고 있는 상태

Closure Expression

Nested Function: 함수의 내부에 자체 코드블럭을 가지고 있는 함수( ⇒ inner function?)

간결하고 집중된 구문으로 의도와 간결함을 유지하여 최적화할 수 있는 인라인 클로저를 작성하는 방법

The Sorted Method

Swift 표준 라이브러리가 제공하는 함수 중 sorted(by:) 함수는 Collection을 순회하며
사용자가 by 파라미터로 넘긴 구문을 기준으로 엘리먼트를 정렬한 결과 값을 리턴한다.

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

func backward(_ s1: String, _ s2:String) {
	return s1 > s2
}

names.sorted(by: backward)
// ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

Closure Expression Syntax

{ (parameter) -> return_type in
	// code block
}

코드 블럭은 in 키워드로 시작
⇒ 클로저의 코드 블럭은 짧아야 하기 때문에 in 키워드를 기준으로
앞은 파라미터와 리턴 타입,
뒤는 본문을 작성한다.

inferring type from context (타입 추론)

namessorted(by) 메소드는 (String, String) -> Bool 으로 메소드가 정의 되어 있다.

타입을 유추할 수 있을 때 명시적으로 쓰지 않아도 타입 추론으로 생략이 가능하다.

파라미터와 리턴 데이터 타입을 추론할 수 있을 떄 메소드의 리턴 타입을 표현하는 도 생략 가능하다.

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

implicit returns from single-expression closure (단일 표현식 클로저의 암시적 반환)

별도의 로직이 없는 단일 표현식 클로저는 암시적으로 리턴 타입을 추론하므로 생략이 가능하다

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

Shorthand argument names

swift는 인라인 클로저에 파라미터의 선언을 생략할 수 있다.

생략된 파라미터의 이름은 컴파일러의 추론을 통해 결정된다. ($0, $1, ...)

메소드 선언부와 코드 블럭을 구분하는in 키워드도 생략할 수 있다.

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

operator method

메소드의 파라미터와 리턴타입을 만족할 수 있는 연산자(operator)가 있다면 모든 것을 생략하고 해당 연산자만으로 클로저를 대신할 수 있다.

operator methods 참고 링크

names.sorted(by:>)
// names.sorted(by: {s1, s2 -> Bool in s2 > s1 })

Trailing Closure

Captuing Values

  • 클로저는 선언된(클로저를 감싸고 있는) 함수의 범위에서 벗어나도 상수, 변수를 사용하거나 수정할 수 있다.
  • 값을 캡쳐할 수 있는 가장 심플한 형태의 클로저는 함수의 내부에 있는 중첩(nested) 함수다
  • 중첩 함수는 감싸고 있는 함수의 arguments, 값 등 함수에 정의된 모든 것에 접근할 수 있다.
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

let incrementByTen = makeIncrementer(forIncrement: 10)
  • 이 메소드를 살펴보면 정수 amount를 받아 amount 만큼 값을 증가시키고 누적 값을 리턴하는 메소드를 반환한다.
  • 반환 타입 정의 부분에 -> () -> Int 행태로 낯설 수 있지만, 첫 -> 만 제외하면 () -> Int 함수의 선언과 동일하다.
  • 최적화를 위해 클로저가 생성된 후 수정되지 않은 값에 대해서는 복사하여 저장한다.
  • 클래스 인스턴스 프로퍼트(멤버변수)에 할당하여 인스턴스나 인스턴스의 프로퍼티를 캡쳐할 경우 강한 참조(String Reference) 사이클을 생성하게 된다. 스위프트는 강한 참조를 방지하기 위해 브레이크 리스트(break list)를 사용한다. Automatic Reference Counting

Closures Are Reference Types

클로저는 참조다

call by reference

Escaping Closure

클로저가 arguments로 넘겨줄 때 클로저를 이스케이프(escape)했다고 표현하고, 함수가 반환(종료)되고 나서 호출 된다.

함수 선언부의 arguments로 받을 때 파라미터의 타입 앞에 @escaping 키워드를 붙여 클로저의 이스케이핑을 허용한다.
→ 클로저의 구현된 로직은 함수의 외부에 있다.

비동기 호출할 때 completion handler 를 파라미터로 전달하면, 함수가 끝날 때 호출되므로, 클로저의 레퍼런스를 파라미터로 전달하여 함수와 독립적으로 비동기 연산이 종료된 후에 호출할 수 있다.

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

someFunctionWithEscapingClosure를 arguments로 받아 전역에 선언된 배열에 추가하는 로직이다.

@escaping 키워드를 completionHandler에 붙이지 않는다면 컴파일 에러가 발생한다.

이스케이핑 클로저가 self를 참조할 경우 주의해야 한다.

캡쳐된 self와 이스케이핑 클로저가 강한 참조 사이클을 만들 수 있다.

Automatic Reference Counting

이스케이핑 클로저는 struct나 enumeration가 self 일 때 ⇒ enumeration, struct의 이스케이핑 클로저일 때 변경 할 수 있는(mutable) 객체를 참조 할 수 없다. (struct와 enumeration은 value)

Autoclosure

클로저 형식으로 선언된 형식으로 생성되어 메소드 파라미터로 전달되는 클로저

중요한 특징으로

  1. 파라미터를 받지 않으며
  2. 내부 로직의 리턴 타입으로 리턴

오토클로저는 호출되기 전에 코드가 실행되지 않기 때문에 미리 평가(evaluation)되지 않는다.

Delaying evenaluation은 사이드 이펙트를 가지는 코드 사용에 용이하고, 그렇게 때문에 계산에 비용이 비싸다.

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

// + remove and return first elements on called
// + not work it before call
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"

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

오토 클로저를 남용하면 코드의 이해를 떨어뜨릴 수 있다. 사용해야 할 경우 이름에 의도를 고려하여 명확하게 결정한다.

이스케이핑 클로저를 오토클로저에 활용할 경우 @autoclouse @escaping 키워드를 사용한다.

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

// (1)
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."

// (2)
for customerProvider in customerProviders {
		// (3)
    print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"

위 코드에서 (1) 표기된 customersInLine.remove(at:Int) 호출이 오토 클로저로 collectionCustomerProviderscustomerProvider 파라미터로 전달된다.

(2)의 루프가 실행되기 전에는 customersInLine.remove(at:Int) 는 실제로 호출되지 않고,

루프가 실행되면 (3)의 코드가 실행 될 때 실제로 customersInLine.remove(at:Int)코드가 실행된다.

profile
사람들 속에 숨어사는 INTJ 성향을 가진 개발자

0개의 댓글