Basic - Closure

Eli·2021년 1월 30일
2

Swift

목록 보기
3/17
post-thumbnail

의미

사용자가 정의한 코드를 전달할 수 있는 Block.

타언어에서는 람다식과 비슷한 기능이다.

앞서 배운 function은 이름있는 closure이다.

앞으로 다룰 내용들은 이름이 없는 closure라고 생각을 하면 된다.

function은 1급 객체이므로 상위 개념인 closure 역시 1급 객체이며, 참조 타입이다.

기본 문법

{ (파라미터) -> 반환타입 in
	//로직
	return 반환값
}


//예시 함수
let numbers = [ 2, 3, 4, 1, 5, 10, 9, 8 ]
let sortedNumbers = numbers.sorted(by: { (s1: Int, s2: Int) -> Bool in
	return s1 < s2
})

축약

축약 기능들을 사용하면서 sorted 함수를 단계별로 축약하는 예시이다.

코드의 가독성을 위해서 항상 모든 내용들을 축약하기보단 코드에 따라 적절히 축약기능들을 사용하면 된다.
Swift 처음 접하는 사람들이 가장 놀라고 띠용? 하는 부분일 수도 있을 것 같다ㅋㅋㅋㅋ

//먼저, 축약이 없는 기본 클로저
let numbers = [ 2, 3, 4, 1, 5, 10, 9, 8 ]
let sortedNumbers = numbers.sorted(by: { (s1: Int, s2: Int) -> Bool in
	return s1 < s2
})

//반환타입(return type) 축약
//이미 Sorted 함수에 반환타입이 지정되어있으므로 반환타입 지정을 축약할 수 있다.
let sortedNumbers = numbers.sorted(by: { (s1: Int, s2: Int) in
	return s1 < s2
})

//인자타입 축약
//이미 Sorted 함수에 인자타입이 지정되어있으므로 반환타입 지정을 축약할 수 있다.
let sortedNumbers = numbers.sorted(by: { s1, s2 in
	return s1 < s2
})

//return 축약
//한줄의 표현식에서는 return 역시 축약할 수 있다.
let sortedNumbers = numbers.sorted(by: { s1, s2 in
	s1 < s2
})

//인자명 축약
//별도의 인자명을 지정하지 않고 첫번째 인자는 $0, 두번째 인자는 $1. 
//위와 같은식으로 대체되어 클로저 내에서 호출 할 수 있다. 
let sortedNumbers = numbers.sorted(by: { $0 < $1 })

//연산자 축약
//리턴값이 단순 연산자로 돼 있을 경우엔 인자들을 축약할 수 있다. 
let sortedNumbers = numbers.sorted(by: { < })

//함수의 마지막 인자로 클로저가 들어 갈 경우의 축약
//이 경우엔 위의 "연산자 축약"은 불가능하다.
let sortedNumbers = numbers.sorted() { $0 < $1 }

무슨 트랜스포머가 변신하듯 축약되는 기능...
지금봐도 신기하긴 하다.

Captureing Values

Closure의 특성중 하나로 볼 수 있는 기능.

Closure가 사용하게 되는 Scope 밖의 변수들을 자동적으로 참조하는데, 이때 값이 없어지더라도 Capturing을 통해 가지고 있으므로 외부의 변수를 그대로 사용할 수 있다.

struct Calculator {
    var a: Int
    var b: Int

    var sum: Int { return a + b }
}


let calculator = Calculator(a: 3, b: 5)

let closure = {
    print("\(calculator.sum)")
}

closure() 
//closure에서 위의 calculator를 자동적으로 참조해 8이 출력된다.

calculator.b = 20

closure()
//closure에서 위의 calculator를 계속해서 참조하고 있으므로 23이 출력된다.
//실제 클로저를 사용하다보면 의도적으로 원하지 않을때에도 저렇게 값이 변경될 수 있어 주의해야한다.

주의해야 할 사항은 어떤 인스턴스가 클로저 인스턴스를 프로퍼티로 갖고 있고 그 클래스의 내용을 캡쳐를 하면 서로를 참조하게되는 강한 순환참조에 빠진다. 메모리 릭이 발생 할 수 있는 상황. 추후에 다룰 내용인데 이 문제를 해결하기 위한 내용은 바로 다음 다룰 내용인 Capture list를 사용하게 된다.

Capture List

위의 Swift에서 자동적으로 참조해 값이 변경되는 것을 원하지 않을 때 사용할 수 있는 기능이다.

위와 같은 Capturing 상황에서 값이 변한 후에도 이전 값을 사용하고 싶다면 아래와 같이 명시하면 된다.

let calculator = Calculator(a: 3, b: 5)

let closure = { [calculator] in 
    print("\(calculator.sum)")
			
		calculator.a = 20 //변경이 불가능하므로 컴파일 에러 발생
}

closure() //print 8

calculator.b = 20

closure() //print 8

Escaping Closures

일반 클로저(noescape) 함수를 사용하게 되었을 때 불가능한 부분이 있다.

먼저 클로저의 라이프사이클은 아래와 같다.

  1. 함수의 파라미터로 인자로 클로저가 생성되어 전달된다.
  2. 함수 내부에서 클로저가 실행된다.
  3. 함수가 종료(return)되면 클로저는 메모리에서 해제된다.

위의 케이스에서는 함수가 종료되는 것과 동시에 클로저도 종료되므로 함수 종료 이후에 클로저를 사용할 수 없게된다.

위와 같이 함수 종료 이후에도 클로저가 필요한 경우는 두가지 케이스가 있다.

  1. 클로저를 글로벌 변수로 새롭게 저장해두는 경우
  2. 비동기 처리로 함수 종료 후에 콜백이 와서 실행되는 경우(가장 자주 보게 될 케이스)

사용방법은 간단하다.

전달하는 인자를 선언할 때, @escaping을 명시해주면 된다.

var globalHandler: ((Int -> Void))!

func getSumOf(array:[Int], handler: @escaping ((Int)->Void)) {
    var sum: Int = 0
    for value in array {
        sum += value
    }

		//비동기로 함수 호출 종료 후에 사용하는 경우
    DispatchQueue.global().asyncAfter(deadline: .now() + 1.0){
        handler(sum)
    }

		//글로벌 변수로 선언해주는 경우
		self.globalHandler = handler
}

func doSomething() {
    self.getSumOf(array: [16,756,442,6,23]) { (sum) in
        print(sum)
    }
}

Auto Closure

함수의 전달인자로 클로저를 생성해서 전달을 할 때에 {}를 축약해 클로저를 선언할 수 있도록 하는 기능.

간단한 구문을 구사할 경우 가독성을 높일 수 있으나, 로직이 길어지거나 복잡해지면 오히려 가독성에 문제가 생길 수 있으므로 적당히 판단해서 사용하면 된다.

사용방법은 @escaping 구문과 동일하게 @autoclosure로 선언해준다.

그렇게 대단하거나 자주 쓰일 것 같은 기능은 아닐 것으로 생각된다.

// 일반 구문
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customerInLine.remove(at: 0) }) 

// autoclosure 구문
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customerInLine.remove(at: 0))

#학습에 대한 내용으로 틀린 내용이 있을 수 있습니다.
#댓글로 남겨주시면 더 좋은 게시글로 수정하도록 하겠습니다.

profile
애플을 좋아한다. 그래서 iOS 개발을 한다. @Kurly

0개의 댓글