이 글은 공부하는 내용을 기반으로 정리하기 위한 목적으로 작성합니다. 언제든 건전한 비판은 환영합니다.
클로저란?
클로저는 함수를 말하는 것이다. 무슨 소리냐면 func 도 함수라고 이야기 할 수 있다. 근데 왜 클로져에 함수라는 이야기가 나오는지????
먼저 클로져를 알기 전에 2가지 클로져가 있다
named closure, unnamed closure 두 개가 있다.
그렇다면 named closure와 un
먼저 named closure는 그냥 함수이다
func someThing(){
print("someThing")
}
위에 것이 named closure이다. 그냥 이것을 함수라고 부른다
unnamed closure는
let someThing = {print("some")}
위에 것이 흔히 클로져라고 통칭되는 unnamed closure이다.
클로져 사용방법
먼저 named closure를 쓰다 보면 밑에와 같이 사용하면 된다
func score(a : Int) -> String{
return "\(a)점"
}
위의 name closure에서 String 부분의 { 괄호를 a 앞으로 빠주고 그 자리에 in을 사용하면 된다
{(a: Int) -> String in
return "\(a)점"
}
위와 같이 바꿔주면 된다.
물론 위와같이 바꿔주게 되면 받을 수 있는 변수를 넣어주어야 한다.
또 다른 방법으로도 클로져를 사용할 수 있게 된다.
let score2 = {(a: Int) -> String in
return "\(a)점"
}
위에가 받을 수 있는 변수를 선언해 준 것 이다.
또한 리턴을 제거 할 수도 있다.
{(a: Int) -> String in
"\(a)점"
}
위와 같이 사용할 수도 있다.
또한 리턴 타입도 제외할 수 있다
{(a: Int) in
"\(a)점"
}
또한 중괄호( '{' )를 옮길 수도 있다.
let score = (Int) -> String { a in
"\(a)점"
}
위와 같은 경우는 파라미터를 아예 뒤로 옮기는 것이다
또한 파라미터를 사용하지 않더라도 약속된 언어 가지고 사용할 수 있다.
let score: (Int, Int, Int) -> String = {
"\($0 + $1 + $2)점"
}
파라미터 순으로 들어가는 것이다.
클로져를 이용해 함수안에 함수를 작성할 수 있는 로직도 있다
예를 들면
let names = ["find","apple","banana","melon","waterMelon"]
let containAlpha:(String, String) -> Bool = {name, find in
if name.contains(find){
return true
}
return false
}
와 같이 함수를 작성하면 알파벳이 리스트에 있냐 확인할 수 있습니다.
하지만 만약에 저런 알파벳 찾는 것이 아닌 다른 로직인 리스트의 내용을 리턴하는 거라면 코드의 재사용을 위해 함수 자체를 파라미터 값으로 넣을 수 있습니다.
func find(findString : String, condition:(String, String) -> Bool) -> [String]{
var newName = [String]()
for name in names{
if condition(name,findString){
newName.append(name)
}
}
return newName
}
위와 같이 condition의 파라미터에다가 함수를 작성할 수 있습니다.
그러면 저 위에를 어떻게 써야 하나?
find(findString : "a", condition: containAlpha)
먼저 작성해 놓은 것은 함수를 기반으로 사용할 수 있습니다.
sort 사용법
sort도 unNamed closure 방식으로 사용할 수 있다.
예를 들면
var names = ["Alex","chch", "lua", "ancy", "rep"]
위와 같은 배열이 있다고 한다면
sort할 수가 있다
원래는
names.sort{ (lhs, rhs) -> Bool in
return lhs>rhs
}
위와 같이 사용해야 하지만
축약형을 사용할 수 있다 아래와 같이
names.sort(by : { $0 < $1 } )//인자 이름 축약
names.sort() { $0 < $1 }
names.sort{ $0 < $1 }
names.sort(by : < )//연산자 메소드
후위 클로져
후위 클로져를 쓰는 이유는 마지막 인자로 클로져를 넣었을 시에 클로져의 길이가 길다면 후위 클로져를 사용할 수 있습니다.
에를들면 일반적인 클로져의 코드를 짜게 된다면
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
}
이렇게 넣을 수 있습니다. 이것을 후위 클로져로 바꿔본다면
someFunctionThatTakesAClosure(closure: {
// closure's body goes here
})
이런 방식으로 바뀔 수 있습니다. ()를 {} 안에 넣어준다면 후행 클로져 방식을 사용할 수 있습니다.
이번에는 후위 클로저를 이용해 숫자(Int)를 문자(String)로 매핑(Mapping)하는 예제를 살펴 보겠습니다. 다음과 같은 문자와 숫자가 있습니다.
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]
이 값을 배열의 map(_:)메소드를 이용해 특정 값을 다른 특정 값으로 매핑하는 할 수 있는 클로저를 구현합니다.
let strings = numbers.map { (number) -> String in
var number = number
var output = ""
repeat {
output = digitNames[number % 10]! + output
number /= 10
} while number > 0
return output
}
// let strings는 타입 추론에 의해 문자 배열([String])타입을 갖습니다.
// 결과는 숫자가 문자로 바뀐 ["OneSix", "FiveEight", "FiveOneZero"]가 됩니다.
위 코드는 각 자리수를 구해서 그 자리수를 문자로 변환하고, 10으로 나눠서 자리수를 바꾸며 문자로 변환하는 것을 반복합니다. 이 과정을 통해 숫자 배열을, 문자 배열로 바꿀 수 있습니다. number값은 상수인데, 이 상수 값을 클로저 안에서 변수 var로 재정의 했기 때문에 number값의 변환이 가능합니다. 기본적으로 함수와 클로저에 넘겨지는 인자 값은 상수입니다.
값 캡쳐
클로저는 특정 문맥의 상수나 변수의 값을 캡쳐할 수 있습니다. 다시말해 원본 값이 사라져도 클로져의 body안에서 그 값을 활용할 수 있습니다. Swift에서 값을 캡쳐 하는 가장 단순한 형태는 중첩 함수(nested function) 입니다. 중첩 함수는 함수의 body에서 다른 함수를 다시 호출하는 형태로 된 함수 입니다. 예제를 보겠습니다.
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
이렇게 보면 익숙치 않아서 어려울 수 있는데 하나하나 뜯어보면 어렵지 않습니다.
먼저 () 부분은 함수를 받겠다는 의미입니다
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
이런 식으로 {} 안에 작성되 있는것을 이야기하며
그 이후에 -> Int는
return incrementer
입니다.
클로저는 참조타입
클로져는 복사 타입이 아닌 참조타입 입니다. 클래스를 아시는 분이라면 이게 무슨 뜻인지 알지만 저 나름대로 다시 정리해서 설명해 드리겠습니다.
let alsoIncrementByTen = makeIncrement(10)
alsoIncrementByTen()
위에 값 캡쳐에서 한 값에 10을 인자를 넣어주면 당연히 10이 나옵니다.
let alsoIncrementByTen = makeIncrement(10)
let alsoIncrementByTen//20
let alsoIncrementByTen//30
let alsoIncrementByTen//40
let alsoIncrementByTen//50
함수를 실행할 수록 값이 커지는 것을 발견할 수 있습니다.
클로져에서 값이 캡쳐 되어서 점점 켜져갑니다.
이스케이핑 클로저
클로저를 함수의 파라미터로 넣을 수 있는데, 함수 밖(함수가 끝나고)에서 실행되는 클로저 예를들어, 비동기로 실행되거나 completionHandler로 사용되는 클로저는 파라미터 타입 앞에 @escaping이라는 키워드를 명시해야 합니다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
그러면 함수 밖에 있는 completionHandlers에 추가 됩니다.
위 함수에서 인자로 전달된 completionHandler는 someFunctionWithEscapingClosure 함수가 끝나고 나중에 처리 됩니다. 만약 함수가 끝나고 실행되는 클로저에 @escaping 키워드를 붙이지 않으면 컴파일시 오류가 발생합니다.
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure() // 함수 안에서 끝나는 클로저
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 } // 명시적으로 self를 적어줘야 합니다.
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"
completionHandlers.first?()
print(instance.x)
// Prints "100"
자동 클로져
자동클로저는 인자 값이 없으며 특정 표현을 감싸서 다른 함수에 전달 인자로 사용할 수 있는 클로저입니다. 자동클로저는 클로저를 실행하기 전까지 실제 실행이 되지 않습니다. 그래서 계산이 복잡한 연산을 하는데 유용합니다. 왜냐면 실제 계산이 필요할 때 호출되기 때문입니다. 예제를 보면서 무슨 뜻인지 알아 보겠습니다.
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// Prints "4"
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!"
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"
// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = [] // 클로저를 저장하는 배열을 선언
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
} // 클로저를 인자로 받아 그 클로저를 customerProviders 배열에 추가하는 함수를 선언
collectCustomerProviders(customersInLine.remove(at: 0)) // 클로저를 customerProviders 배열에 추가
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures." // 2개의 클로저가 추가 됨
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!") // 클로저를 실행하면 배열의 0번째 원소를 제거하며 그 값을 출력
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"
'Language > Swift' 카테고리의 다른 글
클래스 vs 구조체 차이 (0) | 2022.02.19 |
---|---|
클래스와 상속 (0) | 2022.02.19 |
Swift 기본 문법 - 자료형 (0) | 2022.02.08 |