본문 바로가기

문과 코린이의, [IOS] 기록/Swift 이론

[문과 코린이의 IT 기록장] 클로저(Closure) - 클로저(Closure)란?, 클로저 문법, 클로저 호출, 1급 객체 특징을 지닌 클로저, 클로저 경량 문법, @autoclosure, @escaping

반응형

[문과 코린이의 IT 기록장] 클로저(Closure) - 클로저(Closure)란?, 클로저 문법, 클로저 호출, 1급 객체 특징을 지닌 클로저, 클로저 경량 문법, @autoclosure, @escaping

[문과 코린이의 IT 기록장] 클로저(Closure) - 클로저(Closure)란?, 클로저 문법, 클로저 호출, 1급 객체 특징을 지닌 클로저, 클로저 경량 문법, @autoclosure, @escaping

 


1.  클로저(Closure)란?

- 사용자의 코드 안에서 전달되어 사용할 수 있는 로직을 가진, 중괄호({})로 구분된 코드의 블럭을 의미한다.

- 이는 일급 객체의 역할을 할 수 있다.

 * 일급 객체 : 전달 인자 가능 / 변수 및 상수 등 저장, 전달 가능 / 함수의 반환값 가능

 

[ 클로저의 종류 ]

1) Named Cloure (= 함수)

func NamedClosure(){
	...
}

 

2) Unnamed Clousre

- 이름을 붙이지 않고 사용하는 함수, 즉 익명함수를 의미한다.

- 일반적으로 클로저라고하면, 이 Unnamed Clousre를 이야기한다.

let UnnamedClosure = { ... }

 


2. 클로저 문법 (Closure Expression Syntax)

{ (Parameters) -> return type in // Clousre Head
	statements // Clousre Body (실행 구문)
}
// in 키워드를 기준으로 Clousre Head와 Closure Body가 구분된다.

 

ex 1. 파라미터 및 반환값이 존재하지 않는 클로저

let closure = { () -> () in // 파라미터, 반환값 모두 존재 X
	...
}

 

ex 2. 파라미터 및 반환값이 존재하는 클로저

let closure = { (str : String) -> String in // String형 파라미터 str, 반환값 String형
	...
}

 


3. 클로저 호출

1) 클로저가 대입된 변수 및 상수로 호출

// 클로저 정의
let ClosureStr = { (str : String) -> () in
	...
}

// 클로저 호출
ClosureStr("Hello World") // 호출구문 ()를 이용해서 실행 가능
// ClosureStr(str : "Hello World") // error 발생

 

2) 클로저를 직접 실행 (변수 및 상수에 클로저 대입 X)

({ () -> () in
	...
})()
// 일회성으로 사용한다.
// 클로저를 ()로 감싸고, 마지막에 호출 구문 ()를 추가해주면 된다.

 


4. 1급 객체 특징을 지닌 클로저

1) 클로저는 변수 및 상수에 대입 가능하다.

// 변수 및 상수에 대입, 실행 가능
let closure1 = { () -> () in
	...
}

// 새로운 변수 및 상수에 대입 가능
let closure2 = closure1

 

2) 함수의  파라미터 타입으로 클로저 전달 가능

// 함수 정의
func ClosureFunc(closure : () -> ()){ // ClosureFunc함수는 파라미터를 closure형태로 받음
	closure() // 클로저 실행
}

// 함수 호출
ClosureFunc(closure : { () -> () in
	...
})
// = ClosureFunc(closure : { () -> () in ... })

 

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

// 함수 정의
func ClosureFunc() -> () -> (){ // 파라미터 X, 반환형 : ()->() (파라미터와 반환형이 없는 클로저)
	
    return { () -> () in // 반환형이 파라미터와 반환형이 없는 클로저형태
    	...
    }
}


// 함수 호출
let closure = ClosureFunc() // 클로저를 리턴받아서 closure변수에 저장
closure() // 클로저 실행

 


5. 클로저 경량 문법

1) 트레일링 클로저 (Trailing Closure)

- 함수의 마지막 파라미터클로저일 때, 이를 파라미터 값 형식이 아닌 함수 뒤에 붙여 작성하는 문법

- 이 때 Argument Label은 생략된다.

 

ex 1. 인라인 클로저 방식

// 함수 정의 : 파라미터가 클로저 하나뿐
func ClosureFunc(closure : () -> ()){
	closure()
}

// 함수 호출
ClosureFunc(closure : { () -> () in
	...
})
// 이와 같이 함수 호출구문() 안에 작성되어 있는 것을, 인라인 클로저(Inline Closure)라고 함.

 

ex 2. 트레일링 클로저 방식 (파라미터가 클로저 하나인 경우)

// 함수 정의 : 파라미터가 클로저 하나뿐
func ClosureFunc(closure : () -> ()){
	closure()
}

// 함수 호출
// 1) 이와 같이 함수의 가작 마지막에 클로저를 덧붙여 쓰는 것도 가능
ClosureFunc(){ () -> () in
	...
}
// 2) 또한 호출구문 ()도 생략 가능
ClosureFunc{ () -> () in
	...
}

// 트레일링 클로저에서는 ArgumentLabel이 생략된다.
// 즉 {closure : () -> () in ... } 이러한 형태로 쓰여지지 않고, closure : 가 생략된다는 것이다.

 

ex 3. 인라인 클로저 & 트레일링 클로저 방식 (파라미터가 여러개인 경우)

// 함수 정의
func fetchData(success: () -> (), fail : () -> ()){
	...
}

// Inline Closure
fetchData(success : { () -> () in
	...
}, fail : { () -> () in
	...
})

// Trailing Closure
fetchData(success : { () -> () in 
	...
}) { () -> () in
	...
}

 

2) 클로저의 경량 문법

- 문법을 최적화해, 클로저를 단순하게 쓸 수 있도록 하는 것

// 기존 방법
func ClosureFunc(closure : (Int, Int, Int) -> Int){
	closure(1,2,3)
}

ClosureFunc(closure : { (a:Int, b:Int, c:Int) -> Int in
	return a + b + c
})

- 경량문법 특징

a. 파라미터와 리턴 형식을 생략 가능하다.

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

b. 파라미터 이름은, Shortand Argument Names으로 대체하고, 이 경우 파라미터이름과 in 키워드를 삭제한다.

 * Shortand Argument Names : 파라미터 이름 대신에, $0, $1, ...과 같은 형태를 사용하는 것

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

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

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

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

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

// 파라미터가 하나인 경우는 ()도 생략 가느아핟
ClosueFunc{
	$0 + $1 + $2
}

6. @autoclosure

- 파라미터로 전달된 일반 구문 & 함수 클로저로 래핑(Wrapping) 하는 것

func AutoClosureFunc(closure : @autoclosure () -> ()){ // autoClosure는 파라미터가 반드시 존재하지 않아야 한다.
	closure()
}
// 이제 AutoClosureFunc함수는, 실제 클로저를 전달받지는 않지만 클로저처럼 사용이 가능하다.

AutoClosureFunc(closure : 1 > 2) // 일반 구문 전달
// 그러나 실제 클로저를 전달하는 것은 아니기에, 파라미터로 값을 넘기는 것처럼 ()를 통해 넘겨줄 수 가 없다.

- autoclosure는 작성되자마자 실행되는 것이 아닌, 지연되어 실행된다는 특징을 가지고 있다.

 * 즉, autoclosure는 함수가 실행될 시점에 구문을 클로저로 만들기 때문에, 함수 내에서 클로저를 실행할 때까지 구문이 실행되지 않는다.

 


7.  @escaping

1) non-escaping Closure

- 함수 내부에서 직접 실행하기 위해서만 사용하는 것

- 따라서 파라미터로 받은 클로저 변수 및 상수에 대입할 수 없고, 중첩함수에서 클로저를 사용할 경우 중첩함수 리턴이 불가능

- 함수의 실행흐름을 탈출하지 않아, 함수가 종료되기 전에 무조건 실행되어야 한다.

// 지금까지 사용한 클로저 : non-escaping Closure
func ClosureFunc(closure : () -> ()){
	let a : () -> () = closure // 에러 발생 (상수 대입 불가능)
    DispatchQueue.main.asyncAfter(deadline : .now() + 10){ // 에러 발생 (함수가 종료된 후 클로저 실행 불가능)
    	closure()
    }
	... 
}

func OverlapClosureFunc(closure : () -> ()) -> () -> () {
	func inner(){ // 중첩함수
    	closure() // 중첩함수 내부에서 클로저 사용
    }
    return inner // 에러 발생 (중첩함수 리턴 불가능)
}

 

2) @escaping

- 위의 문제점들을 해결하기 위해 사용하는 것이, @escaping 키워드다.

func ClosureFunc(closure : @escaping () -> ()){ // @escaping 키워드 사용
	let a : () -> () = closure // 대입 가능
    	
    print("start")
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 10){ // 함수 종료 후 클로저 실행 가능
    	closure()
    }
    
    print("end")
}


ClosureFunc { print("closure") }

// 출력
// start
// end
// closure

 

반응형