본문 바로가기
FRONT-END/iOS

Swift 클로저(Closure)

by 랄라J 2023. 8. 4.

클로저란?

  • 이름이 없는 함수를 의미함
  • 왜 이름이 불필요할까? 함수를 실행하는 형태가 아닌 전달하는 형태로 사용하기 때문에 이름이 불필요함 
    • ex) 파라미터 전달 등 함수가 필요한 순간 클로저로 정의해서 전달

 

함수와 클로저 형태의 비교

// 함수의 형태
func myFunction() -> Int {
    return ...
}

// 클로저의 형태
{() -> Int in
    return
}

 

함수를 1급 객체로 취급함, 함수는 타입! 

  • 함수를 변수에 할당할 수 있음
  • 함수를 호출할 때, 함수를 파라미터로 전달할 수 있음
  • 함수에서 함수를 반환할 수 있음

 

중괄호는 함수

// 아래 두 코드는 동일함을 인지할 수 있어야 함
var sample = {
    print("closure")
}

var sample = { () -> () in
    print("closure")
}

sample() // closure

 

함수를 클로저 형태로 변환하기

  • 리턴형에 대한 표기 생략 가능
  • (컴파일러가 타입 추론이 가능한 경우) 파라미터 타입에 대한 생략도 대부분 가능
func add(a: Int, b: Int) -> Int {
	let result = a + b
	return result 
}

{(a: Int, b: Int) -> Int in 
	let result = a + b
	return result 
}

// 클로저는 타입 추론이 되어 return Type 생략 가능
{(a: Int, b: Int) in 
	let result = a + b
	return result 
}

// 클로저는 타입이 명확한 경우 파라미터 Type 생략 가능
let result: Int = {(a, b) in 
	let result = a + b
	return result 
}

 

클로저 사용 이유

  • 파라미터 전달 등 함수가 필요한 순간 클로저로 정의해서 전달할 수 있음
  • 사후 적으로 클로저를 정의해서 전달을 할 수 있음 
    • 활용도가 커지고 및 커스터마이징이 가능해짐
func utilizeClosure (a:Int, b: Int, closure: (Int) -> Void) {
    let result = a + b
    closure(result)
}

utilizeClosure(a: 3, b: 5, closure: {result in
    print("result : \(result)") 
}

 

클로저의 문법 최적화

  • 실제 사용 시 읽기 쉽고 간결한 코드 작성을 위해 축약된 형태의 문법을 제공함
  •  문맥 상에서 파라미터와 리턴 밸류를 추론함
  •  코드가 한 줄인 경우(싱글 익스프레션), return을 생략할 수 있음
  •  아규먼트 이름을 $0(첫번째 파라미터), $1(두번째 파라미터)과 같은 형태로 축약할 수 있음
  •  트레일링 클로저 문법 :  함수의 마지막 인자로 클로저가 전달되는 경우 소괄호 생략 가능  

 

트레일링 클로저 문법

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

// 아래 함수들 호출 방식은 동일함 
closureParamFunc(closure: {
	print("sample")
})

closureParamFunc(){
	print("sample")
 }

// 아래 문법 방식이 후행 클로저 문법
closureParamFunc{
	print("sample")
 }

 

멀티플 트레일링 클로저 (Swift 5.3)

func multipleClosure(first: () -> (), second: () -> (), third: () -> ()) {
    first()
    second()
    third()
}

// Swift 5.3 이전
multipleClosure(first: {}, second: {}){}

// Swift 5.3 이후
multipleClosure{} second : {} third: {}

 

클로저의 메모리

  • 참조형식으로 Heap (주소를 Stack에 저장)
  • 클로저가 실제 실행되는 것은 Stack 프레임에서 동작함

 

클로저의 캡처

  • 클로저에서 저장할 필요가 있는 값을 캡쳐해서 (인스턴스와 비슷한 방식) 값의 주소를 저장함
  • 중첩함수 : 내부 함수를 리턴하는 경우 클로저에서 발생하는 동일한 캡쳐현상이 발생함
func calculateFunc() -> ((Int) -> Int) {
    var sum = 0
    
    func square(num: Int) -> {
        sum += (num * num)
        return sum
    }
 
    return square
}

var sqaureFunc = caculateFunc()

sqaureFunc(10) // 100
sqaureFunc(20) // 500
sqaureFunc(30) // 1400

calculateFunc()(10) // 100
calculateFunc()(20) // 400
calculateFunc()(30) // 900

 

 @escaping 키워드

  • 원칙적으로는 함수의 실행이 종료되면 파라미터로 쓰이는 클로저도 종료됨
  • 단, @escaping 키워드를 사용하면 클로저를 종료하지 않고 함수에서 탈출시켜 함수가 종료되어도 클로저는 존재하도록 함
  • 사용되는 경우
    • 함수 내부에서 사용된 클로저를 외부 변수에 저장하는 경우
    • GCD 비동기 코드
func aFunc: () -> () {
    print("a")
}

func bFunc(closure: @escaping () -> ()) {
    var saveFunc = closure
}

bFunc()
saveFunc() // a

 

@autoclosure 키워드

  • 파라미터가 없는 클로저에만 사용 가능
  • @non-esacaping
  • 사용하는 경우
    • 일반적으로 클로저로 써도 되지만 번거로운 경우 사용
    • 번거로움을 해결해주지만, 실제 코드가 명확해보이지 않을 수 있어 사용 지양 
    • 읽기위한 문법, 잘 사용하지 않음
func aFunc(closure: @autoclosure () -> Bool ) {
    if closure() {
    	print("true")
    } else {
    	print("false")
    }
}

var test = aFunc(closure: true)

// @autoclosure 키워드가 붙어있어 자동으로 중괄호를 붙여 자동으로 클로저를 만들어 줌
var test = aFunc(closure: {true})
반응형

댓글