[iOS 2주차] 클로저(Closure)

황석범·2024년 10월 30일
0

클로저(closure)

  • 익명 함수 라고 불리는 클로저는 함수와 유사하게 특정 작업을 수행하는 코드블록 입니다.
  • 함수의 4가지요소(이름 ,파라미터 , 반환타입 , 구현부)에서 이름 을 제외한 파라미터 , 반환타입 , 구현부 로 구성되어 있습니다.
  • 데이터타입 으로 클로저를 사용할 수 있습니다.
    • 변수에 할당하여 재사용할 수 있습니다.
    • 함수의 파라미터로 전달할 수 있습니다.
  • 클로저가 함수 안에 포함된것이 아니라 클로저 안에 함수가 포함되어 있습니다.

  • 코드의 재사용성을 높이고, 비동기 처리, 컬렉션 연산 등의 장점이 있습니다.

타입으로 사용하는 방법


// 타입으로 사용할 때 표현방법
(파라미터 데이터 타입) -> 리턴 타입
  • 파라미터의 타입은 없을 때 비워두고, 리턴 타입이 없는 경우 Void 라고 명시합니다.
    • 파라미터가 없을 때도 ( ) 괄호는 있어야 합니다!
  • 데이터 타입으로 사용할 수 있기 때문에 Optional 타입으로 사용할 수 있습니다.
    이 때 전체를 ( ) 로 감싼 후 ? 를 입력해야 합니다.
    • 나중에 옵셔널을 배우고 다시한번 공부해보세요!

let closure: (Int, String) -> Void // 파라미터는 튜플(Int, String) 타입이고
								  // 반환타입은 없습니다.
                                  // 만약 -> Void가 생략되었다면 튜플과 같죠!?
                            
let closure2: (Int) -> Viod      // 파라미터는 1개로 Int이며 반환타입은 없습니다.

let closure3: () -> Void.       // 파라미터는 없고 반환타입은 없습니다.
							   // 파라미터는 ()로 감싸고 있어서 Void를 생략할 수 있어요.
                               
let closure4: (Int) -> Int.   // 파라미터는 1개이고 Int 타입이며, 반환값은 Int 입니다.
let closure5: (String, Int) -> Int // 파라미터는 튜플(String, Int) 타입이고 
							 // 반환타입은 Int 입니다.
                             
let optionalClosure: ((Int) -> Int)? // 옵셔널 타입입니다.
									// 만약 ()가 없었다면 리턴값이 Int? 라고 판단됩니다!

클로저 구현하는 방법

  • 클로저는 익명함수 이므로, 구현할 때 in 키워드를 사용하여 파라미터와 구현부를 구분해야 합니다.

// 기본적인 구현 방법
// 중괄호로 시작하고 파라미터와 구현부 부분을 in 키워드를 사용하여 나눕니다.
{ 파라미터 이름 in
	// 구현부
    // 리턴
}
  • in 키워드를 사용하여 앞에는 파라미터 이름을 뒤에는 구현부를 분리하여 작성합니다.
  • 파라미터 이름은 생략 가능하며 0번째 파라미터 $0 으로 사용할 수 있습니다. ($0 , $1 , $2 ...)
    파라미터 이름을 생략할 때는 in 키워드까지 생략해야 합니다.
  • 파라미터가 없다면 in 을 생략해야 합니다.
  • 파라미터 이름은 사용하실 이름을 자유롭게 작성하면 됩니다.

클로저를 사용(호출)하는 방법

  • 클로저를 변수나 상수에 저장했다면 함수처럼 이름뒤에 괄호를 사용하면 됩니다.
  • 옵셔널 변수에 저장한 클로저는 변수이름?() 형식으로 사용하면 됩니다.
  • 구현하고 즉시 호출한 결과값을 저장하고 싶다면 {} 뒤에 괄호 () 를 사용하면 됩니다.

이번 강의에서 클로저를 공부하면서 람다가 생각나서 한번 찾아서 정리해보려고 한다.

  • 클로저(Closure)
    • 익명함수(Unnamed) 외, 이름이 있는 함수(Named) 또한 클로저이다.

The Swift language Guide 에서 소개하는 클로저는 다음과 같습니다.

클로저(Closure)
코드블록으로 C와 Objective-C의 블럭(blocks)과 다른 언어의 람다(lambdas)와 비슷합니다.
클로저는 어떤 상수나 변수의 참조를 캡쳐(capture)해 저장할 수 있습니다.

  1. 코드를 간결하게 만들고, 반복 관련 코드의 불필요한 부분을 제거
  2. 주로 고차함수에 매개변수로 전달되거나, 결과값으로 활용

위 두 가지 특징 정도로만 이해해놓고 넘어가보도록 하자...


escaping closure

  • escaping탈출하다는 의미
  • 기본적으로 함수의 파라미터로 전달된 클로저는 함수 내부에서만 사용할 수 있지만,
    함수가 종료된 후에도 실행해야 할 경우에는 @escaping 키워드를 사용합니다.
  • 클로저의 타입 앞에 @escaping 키워드를 명시하면 됩니다.
  • 클로저는 기본적으로 non-escaping 입니다.
    • ⭐️ 파라미터로 받은 클로저는 함수의 실행이 완료되기 전에 함수 내부에서 클로저를 사용해야 합니다.
    • 파라미터로 받은 클로저는 다른 변수나 상수에 할당할 수 없습니다.
// 정상적인 클로저 
func testEsacpingClosure(closure: () -> Void) {
    closure()
}

testEsacpingClosure {
    print("Hello")
}
// 출력값 : Hello
// 파라미터로 받은 클로저가 함수가 실행된 후 사용되는 경우
import UIKit
func testEsacpingClosure(closure: () -> Void) {
        // 나중에 배울 개념으로 1초뒤에 closure를 호출해달라는 의미
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
        closure() // Error 발생! 
    }
}

// 함수가 끝나고 1초뒤에 closure를 호출하기 때문에 오류 발생
//파라미터로 받은 클로저는 다른 변수나 상수에 할당할 수 없습니다.
func testEsacpingClosure(closure: () -> Void) {
    let newClosure: () -> Void = closure // Error 발생
}
  • 비동기적인 작업 후 클로저를 호출할 때 주로 @escaping closure를 사용합니다.
    • 예시
      • 서버에서 API를 호출하는 경우 서버의 응답을 기다린 후 데이터를 받아 처리해야 합니다.
      • 이때, 서버에서 응답이 도착하기 전에 함수가 먼저 종료될 수 있습니다.
      • 함수가 종료된 이후에도 클로저를 실행할 수 있도록 @escaping 을 사용하여 서버의 응답을 받은 후에 클로저를 호출합니다.

캡처 리스트

📚 클로저가 주변 환경의 변수를 캡처할 때 메모리관리를 명시적으로 제어할 수 있는 방법입니다.
  • value type 의 값도 참조로 캡처하지만 value type 으로 캡처할 수 있습니다.
  • reference type 은 참조방식을 정할 수 있습니다.
  • [ ] 대괄호 안에 사용할 변수나 상수를 작성하여 캡처리스트를 정할 수 있습니다.

reference type 참조 방식 정하기

  • 기본은 strong이기때문에 강한순환참조가 발생할 수 있습니다!
  • 대괄호를 사용하고 참조방식과 캡처할 변수(상수) 이름을 작성하면 됩니다.
  • weak, unowned 사용 가능합니다
class Animal {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

func testClosureCaptureReference() {
    var animal = Animal(name: "Dog", age: 1)
    
    let closure = { [weak animal] in // [ ]
        print(animal?.age)
    }
    
    closure() // 출력 값 : Optional(1)
    animal.age = 2
    closure() // 출력 값 : Optional(2)
}


testClosureCaptureReference()

value type으로 캡처하는 방법

  • 대괄호를 사용하고 캡처할 변수(상수)를 작성하면 됩니다.

struct Person {
    var name: String
    var age: Int
}

func testClosureCapture() {
    var person = Person(name: "Brody", age: 20)
    var a = 1
    
    let closure = { [person] in
        print(person.age)
        print(a)
    }
    closure() // 출력 값 : 20
    person.age = 25
    a = 2
    closure() // 출력 값 : 20
}

testClosureCapture()

/* 출력 값
20 // person은 value type으로 복사를 하였기 때문에 변경이 없음
1 // a는 참조캡처를 하여 값 변경
20 // person은 value type으로 복사를 하였기 때문에 변경이 없음
2
*/
profile
iOS 공부중...

0개의 댓글

관련 채용 정보