Swift - 클로저

이원석·2024년 11월 21일

Swift

목록 보기
14/38

클로저

클로저(Closure)란 내부 함수와 내부 함수에 영향을 미치는 주변 환경을 모두 포함한 객체
클로저(Closure)는 Named Closure & Unnamed Closure 둘 다 포함하지만, 보통 Unnamed Closure(익명함수)를 말함

  • Named Closure(일반적으로 함수라고 부름)
func doSomething(){
	print("abd")
}
  • Unnamed Closure(일반적으로 클로저라고 부름)
let closure = { print("abc")}
  • 클로저는 1급 객체 함수의 특성을 가지고 있다

클로저 표현식

{ (Parameters) -> Return Type in //Closure Head, Head 와 Body를 in으로 구분 지어줌
  실행구문 // Closure body
}

Parameter와 Return Type이 둘 다 없는 클로저

let closure = { () -> () in //1급 객체라 상수에 대입 가능
	print("Closure")
}

Parameter와 Return Type이 있는 클로저

  • 클로저에선 Argument Label을 사용하지 않음
let closure = { (name: String) -> String in
	return "Hello, \(name)"
}

closure("abc")
closure(name: "abc")//에러 (Argument Label을 사용하지 않음)

1급 객체로서 클로저

1. 클로저를 변수나 상수에 대입할 수 있다

let closure = { () -> () in
	print("closure")
}
let closure2 = closure

2. 함수의 파라미터 타입으로 클로저를 전달할 수 있다

func doSomething(parameter: () -> ()){ //함수를 파라미터로 전달받는 함수
	closure()
}

doSomething(parameter: { () -> () in  //클로저를 파라미터로 넘김
	print("closure")
})

3. 함수의 반환 타입으로 클로저를 사용할 수 있다

//기존 선언부는 기존 함수와 동일
func doSomething() -> () -> (){
}

//실제 값을 리턴할때 함수가 아닌 클로저 리턴 가능
func doSomething() -> () -> () {
	return { () -> () in
    	print("Hello")
    }
}

let closure = doSomething()
closure()

클로저 실행하기

클로저가 대입된 변수나 상수로 호출하기

let closure = { () -> String in
	return "Hello"
}

closure()

클로저 직접 실행하기

({ () -> () in
	print("Hello")
})()

문법 경량화

트레일링 클로저(Trailing Closure)

함수의 마지막 파라미터가 클로저일 때, 이를 파라미터 값 형식이 아닌 함수 뒤에 붙여 작성하는 문법. 이때, Argument Label은 생략된다

파라미터가 클로저 하나인 함수

func doSomething(closure: () -> ()){
	closure()
}

//일반적인 호출법(Inline Closure)
doSomething(closure : { () -> () in
	print("Hello")
})

//트레일링 클로저
doSomething () { () -> () in
	print("Hello")
}
doSomething { () -> () in
	print("Hello")
}
  • 파라미터가 클로저 하나일 경우, 이 클로저는 첫 파라미터이자 마지막 파라미터 이므로 트레일링 클로저가 가능
  • "closure"라는 ArgumentLabel은 트레일링 클로저에선 생략됨
  • 파라미터가 클로저 하나일 경우 호출구문인 ()도 생략 가능

파라미터가 여러 개인 함수

func fetchData(success: () -> (), fail: () -> ()){
	//do something...
}
//Inline Closure
fetchData(success: { () -> () in
	print("success")
}, fail: { () -> () in
	print("fail")
})
//트레일링 클로저
fetchData(success: { () -> () in
	print("success")
}) { () -> () in
	print("fail")
}
  • 파라미터가 여러개일 경우, 함수호출구문 () 생략 불가능

클로저의 경량 문법

문법을 최적화 하여 클로저를 단순하게 쓸 수 있게 하는 것

func doSomething(closure: (Int, Int, Int) -> Int) {
	closure(1, 2, 3)
}
//기본 형식
doSomething(closure: { (a: Int, b: Int, c: Int) -> Int in
	return a + b + c
})

파라미터 형식과 리턴 형식 생략

doSomething(closure: { (a, b, c) in
	return a + b + c
})

Parameter Name은 Shortand Argument Names으로 대체하고, 이 경우 Parameter Name과 in 키워드를 삭제한다

  • Shortand Argument Names란 Parameter Name 대신 $0, $1, $2식으로 $과 index를 이용해서 Parameter에 순서대로 접근 하는 것
doSomething(closure: {
	return $0 + $1 + $2
})

단일 리턴문만 남을 경우, return도 생략 가능

doSomething(closure: {
	return $0 + $1 + $2
})

doSomething(closure: {
	$0 + $1 + $2
})

doSomething(closure: {
	print("\($0),\($1),\($2)")
	$0 + $1 + $2	//단일 리턴문이 아니라 에러
})

클로저 파라미터가 마지막 파라미터면, 트레일링 클로저로 작성한다

doSomething(){
	$0 + $1 + $2
}

()에 값이 아무 것도 없다면 생략 한다

doSomething{
	$0 + $1 + $2
}

@autoclosure

@autoclosure란 파라미터로 전달된 일반 구문 & 함수를 클로저로 래핑 하는 것

func doSomething(closure: @autoclosure () -> ()) {
}
  • closure란 파라미터는 실제 클로저를 전달받지 않지만, 클로저 처럼 사용이 가능
func doSomething(closure: @autoclosure () -> ()) {
    closure()
}

doSomething(closure: 1>2) // 1>2는 클로저가 아닌 일반구문이지만 사용가능
  • 주의점은 반드시 파라미터가 없어야 함

autoclosure 특징: 지연된 실행

일반 구문은 작성되자마자 실행되어야 하지만 autoclosure로 작성할 경우 함수 내에서 글로저를 실행할 때까지 구문이 실행 되지 않음


^^^^ 까지는 non-escaping Closure ^^^^

  • non-escaping Closure : 함수 내부에서 직접 실행하기 위해서만 사용한다. 따라서 파라미터로 받은 클로저를 변수나 상수에 대입할 수 없고, 중첩 함수에서 클로저를 사용할 경우, 중첩함수를 리턴할 수 없다. 함수의 실행 흐름을 탈출하지 않아, 함수가 종료되기전 무조건 실행 되어야 한다.

@escaping

non-escaping Closure과 다르게 함수 실행을 벗어나서 함수가 끝난 후에도 클로저를 실행하거나, 중첩함수에서 실행 후 중첩 함수를 리턴하고 싶거나, 변수 상수에 대입하고 싶은 경우 사용

func doSomething(closure: @escaping () -> ()) {
}

func doSomething(closure: @excaping () -> ()) {
	let f: () -> () = closure // 변수나 상수에 파라미터로 받은 클로저 대입가능
}

// 함수가 종료된 후에도 클로저 실행 가능
func doSomething(closure: @excaping () -> ()) {
	print("function start")
    
    DispatchQueue.main.asyncAfter(dedline: .now()+10){
    	closure()
    }
    print("function end")
}
doSomething( print("closure")

클로저 값 캡쳐

func doSomething() {
	var message = "Hello"
    //클로저 범위 시작
    var num = 10
    let closure = { print(num) }
    //클로저 범위 끝
    print(message)
}

closure란 익명 함수는, 클로저 내부에서 외부 변수인 num이라는 변수를 사용(print)하기 때문에 num의 값을 클로저 내부적으로 저장하고 있는데, 이것을 클로저에 의해 num의 값이 캡쳐 되었다 라고 표현

message란 변수는 클로저 내부에서 사용하지 않기 때문에 클로저에 의해 값이 캡쳐되지 않음

클로저의 값 캡쳐 방식

Closure는 값을 캡쳐할 때 Value/Reference 타입에 관계없이 Reference Capture 한다

func doSomething() {
    var num: Int = 0
    print("num check #1 = \(num)") //0
    
    let closure = {
        print("num check #3 = \(num)") 
    }
    
    num = 20
    print("num check #2 = \(num)") // 20
    closure() // 20
}


func doSomething() {
    var num: Int = 0
    print("num check #1 = \(num)") // 0
    
    let closure = {
        num = 20
        print("num check #3 = \(num)")
    }
    
    closure() // 20
    print("num check #2 = \(num)") // 20
}

클로저의 캡쳐 리스트(Capture Lists)

클로저의 시작인 { 의 바로 옆에 []를 이용해 캡쳐할 멤버를 나열한다. 이때 in 키워드도 꼭 함께 작성한다

let closure = {[num1, num2]} in

Value Type의 값을 복사해서 캡쳐하기

Capture Lists를 사용하면 가능

func doSomething() {
    var num: Int = 0
    print("num check #1 = \(num)") //0
    
    let closure = { [num] in
        print("num check #3 = \(num)")
    }
    
    num = 20
    print("num check #2 = \(num)") // 20
    closure() // 0
}
  • Closure를 선언할 당시의 num의 값을 Const Value Type(상수)으로 캡쳐
  • Closure 내부에서 Value Capture 된 값 변경 X
  • Reference Type의 값을 Capture Lists에 작성한다고 Value Capture가 되지는 않음

클로저와 ARC

  • ARC : 인스턴스의 Reference Count를 자동으로 계산하여 메모리를 관리하는 방법

참조
개발자 소들이

0개의 댓글