ios dev kangwook.

Swift) Closure 본문

Swift

Swift) Closure

kangwook 2022. 8. 19. 00:31

iOS개발을 하다보면 Closure는 필수적으로 알아야하는 개념이라고 할 수 있다.

이 Closure가 뭔지 확실하게 알고 넘어가야할 것 같아서 정리를 하려고한다.

Closure

일반적으로 Closure는 익명함수라고 알고 있는 사람들이 많을 것 같다.

왜냐면 보통 다 그렇게 생겼음 얘들이..

근데 이게 사실 그렇지가 않고 일반적으로 우리가 아는 함수들도 다 Closure 의 한 형태라고 생각하면 될 것 같다.

그럼 하나씩 살펴보도록 하자.

기본 클로저

{(매개변수들) -> 반환 타입 in 실행코드}

  • 클로저를 사용하지 않았을 때와 사용했을 때의 코드 비교
func backwards(first: String, second: String) -> Bool {
	return first > second
}

let reversed: [String] = names.sorted(by: backwards)
let reversed: [String] = names.sorted(by: {(first: String, second: String) -> Bool in 
	return first > second
})

클로저에 조금 익숙해져서 그런가 요녀석들이 훨씬 간결하게 변한걸 볼 수 있다.

후행 클로저

  • 기본 클로저를 좀 더 읽기 쉽게 바꾼 것
    • 함수의 괄호가 닫힌 뒤 사용한다.
    • sorted(by:)처럼 클로저 단 한 개 만을 전달 인자로 전달하는 경우에는 함수 뒤의 괄호마저 생략 가능하다.
let reversed: [String] = names.sorted(){(first: String, second: String) -> Bool in
	return first > second
}

클로저 표현의 간소화

  • 메서드의 전달인자로 전달하는 클로저의 경우에 요구하는 타입과 동일해야만 전달이 가능
    • 따라서 전달인자로 사용되는 클로저의 경우 자동으로 적합한 타입을 준수했다고 판단해서 리턴 값의 타입을 명시해줄 필요가 없다!
let reversed: [String] = names.sorted{(first, second) in 
	return first > second
}

상당히 간결하게 변한걸 확인할 수 있겠다.

그런데 심지어 여기서 더 단축시킬 수 있다.

  • 클로저는 매개변수의 이름을 간결하게 단축 인자로 변경 가능
    • 첫 번째 전달 인자부터 $0, $1, $2 ...
    • 실행 코드를 in을 통해 구분해줄 필요성이 사라졌으므로 in 또한 생략이 가능하다.
let reversed: [String] = names.sorted {
	return $0 > $1
}
  • 반환 값을 갖고 실행문이 단 한 줄일 경우 실행문 = 반환 값이므로 return 생략 가능
let reversed: [String] = names.sorted{ $0 > $1 }

이렇게까지 줄여진다.

값 획득

  • 클로저는 자신이 정의된 위치 주변의 상수나 변수의 값을 자신의 내부에서 참조하거나 수정할 수 있다.
func makeIncrementer(forIncrement amount: Int) -> (() -> Int) {
	var runningTotal = 0
	func incrementer() -> Int {
		runningTotal += amount
		return runningTotal
	}
	return incrementer
}
// incrementer 함수가 makeIncrementer 함수 외부로 빠졌을 경우 오류 발생
let incrementByTwo: (() -> Int) = makeIncrementer(forIncrement: 2)
let first: Int = incrementByTwo()
let second: Int = incrementByTwo()
let third: Int = incrementByTwo()

// 클로저는 참조타입으로 메모리 주소를 할당해 줌

탈출 클로저

  • 비동기 작업을 하는 함수들은 클로저를 completion handler의 전달인자로 받는데,
  • 이 때 사용된 클로저는 함수가 종료된 후 호출을 받는데 @escaping 키워드를 사용하여 탈출이 가능한 클로저임을 명시한다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
	completionHandlers.append(completionHandler)
}
typealias VoidVoidClosure = () -> Void
let firstClosure: VoidVoidClosure = {
	print("Closure A")
}
let secondClosure: VoidVoidClosure = {
	print("Closure B")
}

// first와 second 매개변수 클로저는 함수의 반환 값으로 사용될 수 있으므로 탈출 클로저
func returnOneClosure(first: @escaping VoidVoidClosure, second: @escaping VoidVoidClosure, shouldReturnFirstClosure: Bool) -> VoidVoidClosure {
	return shouldReturnFirstClosure ? first : second // 클로저를 반환
}

// 함수에서 반환한 클로저가 함수 외부의 상수에 저장됨
let returnedClosure: VoidVoidClosure = returnOneClosure(first: firstClosure, second: secondClosure, shouldReturnFirstClosure: true)
returnedClosure()

var closures: [VoidVoidClosure] = []

// closure 매개변수 클로저는 함수 외부의 변수에 저장될 수 있으므로 탈출 클로저
func appendClosure(closrue: @escaping VoidVoidClosure) {
	closures.append(closure)
}
  • 위에서 first와 second는 returnOneClosure의 반환 값으로 사용되지만 returnOneClosure 함수가 종료될 때까지 실행이 되지 않아 탈출 클로저라는 것을 알 수 있다.
  • 이 경우 @escaping 키워드가 없으면 코드는 에러가 나게 되고,,
  • 클로저는 클로저 내부에서 프로퍼티나 메서드 등에 접근할 때 self 키워드를 써줘야한다.
    • 비탈출 클로저에서의 self는 선택사항이다!

withoutActuallyEscaping

  • 전달한 비탈출 클로저가 탈출 클로저인 척 해야하는 경우가 간혹 있다..
func hasElements(in array: [Int], match prediction: (Int) -> Bool) -> Bool {
	return (array.lazy.filter{ predicate($0) }.isEmpty == false)
}
  • 요런 함수에서 match 매개변수 뒤에 @escaping 키워드가 없으므로 탈출 클로저가 아닌데 아래 코드를 보면
let numbers: [Int] = [2, 4, 6, 8]
let eveneNumberPredicate = {(number: Int) -> Bool in 
	return number % 2 == 0
}

let oddNumberPredicat = {(nubmer: Int) -> Bool in
	return number % 2 == 1
}

func hasElements(in array: [Int], match predicate: (Int) -> Bool) -> Bool {
	return withoutActuallyEscaping(predicate, do: { escapablePredicate in 
		return (array.lazy.filter{ escapablePredicate($0) }.isEmpty == false)
	})
}

let hasEvenNumber = hasElements(in: numbers, match: evenNumberPredicate)
let hasOddNumber = hasElements(in: numbers, match: oddNumberPredicate)

print(hasEvenNumber) // true
print(hasOddNumber)  //false
  • 사용된 filter메서드는 lazy로 설정되어 있고 lazy 컬렉션은 비동기 작업 시 사용되므로 filter 메서드는 탈출 클로저로 전달해주어야 한다. 
  • 이런게 에러를 유발한다..!

'Swift' 카테고리의 다른 글

Swift) Property  (0) 2022.09.25
Swift) Semaphore  (0) 2022.09.25
Swift) DispatchQueue  (0) 2022.09.22
Swift) Concurrency, GCD  (0) 2022.09.20
Swift) Opaque Type  (0) 2022.09.11
Comments