클로저
<aside> 💡 클로저는 코드 블록(중괄호{} 에 둘러싸인 코드)으로, 독립적인 기능을 수행하는 함수와 비슷한 역할 함수와 마찬가지로 입력 매개변수와 반환 값이 있을 수도 있음,다른 함수에 전달 할수 있고 함수 내부에서 선언하여 사용 가능.클로저를 사용하여 함수의 매개변수로 전달가능
</aside>
클로저의 일반적인 사용 사례 : 비동기적으로 실행되는 코드에서 콜백함수로 사용
클로저는 어떤 상수나 변수의 참조를 캡처해 저장, Swift는 이 캡처와 관련한 모든 메모리를 알아서 처리
클로저 포현(Closure Expressions)
- 인라인 클로저를 명확하게 표현하는 방법으로 문법에 초점이 맞춰짐.
- 코드의 명확성과 의도를 잃지 않으면서도 축약해 사용할 수 있음으로 문법의 최적화 방법 제공
정렬 메소드(The Sorted Method)
- sorted(by:)라는 알려진 타입의 배열 값을 정렬하는 메소드를 제공
- by에 어떤 방법으로 정렬을 수행할 것인지에 대해 기술한 클로저를 넣으면 그 방법대로 정렬된 배열을 얻을 수 있음
- 예시 : 함수를 만들어서 정렬에 인자로 넣을 수 있는 클로저를 만들 수 있음
- let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] func backward(_ s1: String, _ s2: String) -> Bool { return s1 > s2 } var reversedNames = names.sorted(by: backward) // reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
클로저 표현 문법 (Closure Expression Syntax)
클로저 표현 문법 일반적 형태
// 파라미터 , 반환 값, 처리할 내용(statements)
{ (parameters) -> return type in
statements
}
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
문맥에서 타입 추론 (Inferring Type From Context)
- String배열에서 sorted(by:) 메소드의 인자로 사용됩니다. sorted(by:)의 메소드에서 이미 (String, String) -> Bool 타입의 인자가 들어와야 하는지 알기 때문에 클로저에서 이 타입들은 생략
- reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
단일 표현 클로저에서의 암시적 반환 (Implicit Returns from Single-Express Closures)
- 반환 키워드 생략 가능
- reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
인자 이름 축약 (Shorthand Arguments Names)
- Swift는 인라인 클로저에 자동으로 축약 인자 이름을 제공
- 이 인 자를 사용하면 인자 값을 순서대로 $0, $1, $2 등으로 사용
- 축약 인자 이름을 사용하면 인자 값과 그 인자로 처리할 때 사용하는 인자가 같다는 것을 알기 때문에 인자를 입력 받는 부분과 in 키워드 부분을 생략
- reversedNames = names.sorted(by: { $0 > $1 } )
후위 클로저 (Trailing Closures)
만약 함수의 마지막 인자로 클로저를 넣고 그 클로저가 길다면 후위 클로저를 사용할 수 있음
이런 일반적인 전역함수 형태가 사실 클로저를 사용하고 있던 것
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
}
someFunctionThatTakesAClosure(closure: {
// closure's body goes here
})
someFunctionThatTakesAClosure() {
// trailing 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"]가 됨
값 캡쳐 (Capturing Values)
makeIncrementer 함수 안에서 incrementer함수를 호출하는 형태로 중첩 함수
자와 반환 값 (forIncrement amount: Int) -> () -> Int 중에 처음 ->를 기준으로 앞의 (forIncrement amount: Int) 부분이 인자 값이고 뒤 () -> Int는 반환 값
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
runningTotal과 amount도 없음. runningTotal과 amount가 캡쳐링 되서 그런 것
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
실제로는 변수 runningTotal과 amount가 캡쳐링 되서 그 변수를 공유하기 때문에 계산이 누적된 결과를 갖음
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
// 값으로 10을 반환합니다.
incrementByTen()
// 값으로 20을 반환합니다.
incrementByTen()
// 값으로 30을 반환합니다.
클로저는 참조 타입 (Closures Are Reference Types)
- 함수와 클로저를 상수나 변수에 할당할 때 실제로는 상수와 변수에 해당 함수나 클로저의 참조(reference)가 할당
- 클로저를 두 상수나 변수에 할당하면 그 두 상수나 변수는 같은 클로저를 참조
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 50을 반환합니다.
이스케이핑 클로저 (Escaping Closures)
- 클로저를 함수의 파라미터로 넣을 수 있는데, 함수 밖(함수가 끝나고)에서 실행되는 클로저 예를들어, 비동기로 실행되거나 completionHandler로 사용되는 클로저는 파라미터 타입 앞에 @escaping이라는 키워드를 명시해야 함
- 함수에서 인자로 전달된 completionHandler는 someFunctionWithEscapingClosure 함수가 끝나고 나중에 처리
- @escaping를 사용하는 클로저에서는 self를 명시적으로 언급아래코드는 downloadImage 함수를 사용하여 이미지를 다운, 클로저를 사용하여 이미지 다운로드가 완료된 후에 imageView의 이미지를 업데이트 클로저는 DispatchQueue.main.async 를 사용하여 UI 업데이트를 메인 **스레드**에서 실행
func downloadImage(from url: URL, completion: (UIImage?) -> Void) { URLSession.shared.dataTask(with: url) { data, response, error in guard let data = data, error == nil else { completion(nil) return } let image = UIImage(data: data) completion(image) }.resume() } let imageUrl = URL(string: "<https://example.com/image.png>")! downloadImage(from: imageUrl) { image in DispatchQueue.main.async { imageView.image = image } }
- completion 클로저가 완료 핸들러 함수의 실행이 끝난 이후에도 호출될 수 있도록 @escaping 키워드를 사용하여 escaping closure로 선언
- var completionHandlers: [() -> Void] = [] func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) } 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"
자동클로저 (Autoclosures)
- 자동클로저는 인자 값이 없으며 특정 표현을 감싸서 다른 함수에 전달 인자로 사용할 수 있는 클로저
- 자동클로저는 클로저를 실행하기 전까지 실제 실행이 되지 않음
- 계산이 복잡한 연산을 하는데 유용, 실제 계산이 필요할 때 호출되기 때문
- 지연호출 예시
- 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" // 만약 customProvider()를 실행시키지 않는다면 마지막 값은 5
자동클로저를 함수의 인자값으로 넣는 예제
serve함수는 인자로 () -> String)형, 즉 인자가 없고, String을 반환하는 클로저를 받는 함수
함수를 실행할 때는 serve(customer: { customersInLine.remove(at: 0) } )이와 같이 클로저{ customersInLine.remove(at: 0) }를 명시적으로 직접 넣음
// 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!"
- serve함수의 인자를 받는 부분 customerProvider: @autoclosure ()에서 클로저의 인자() 앞에 @autoclosure라는 키워드를 붙임으로서 인자 값은 자도으로 클로저로 변환 함으로써 {} 없이 사용
- 정리하면 클로저 인자에 @autoclosure를 선언하면 함수가 이미 클로저 인것을 알기 때문에 리턴값 타입과 같은 값을 넣어줄 수 있음
- // 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!"
- 자동클로저@autoclosure는 이스케이프@escaping와 같이 사용
// 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!"
공부하면서 몰랐던 용어들
인라인클로저
- 함수 호출 시에 바로 작성할 수 있기 때문에, 함수 호출 시에 생성된 인스턴스가 바로 메모리에서 해제되어 메모리를 절약
- 코드의 가독성을 높이고 메모리 사용량을 줄일 수 있음
- map filter reduce 등의 고차함수
let numbers = [1, 2, 3, 4, 5]
let doubledNumbers = numbers.map({ $0 * 2 })
// 배열의 요소를 모두 2배씩 증가
// { $0 * 2 }는 인라인 클로저로, map 함수의 인자로 전달
비동기
- 한번에 하나 코드 실행 ❌, 여러코드 동시 실행 ⭕
- 비동기 코드를 처리하기위해 async/await 키워드를 사용하여 읽기 쉽고 이해하기 쉬운 코드를 작성
- 구현 방식
- Event-Driven
- 이벤트가 발생했을때 적절한 이벤트 헨들러(이벤트가 발생할 때 실행되는 함수or메서드,버튼 클릭 이벤트에 대한 로직)를 호출하여 처리
- 이벤트를 감지하는 메커니즘(Event Loop)과 이벤트를 처리하는 로직이 분리.
- 예시 : 네트워크 통신은 시간이 오래 걸려 동기적으로 처리하면 성능이 저하, 이벤트 기반 프로그래밍을 사용하면 적절한 이벤트 헨들러를 호출하여 결과를 처리
- Callback-Based
- 다른 함수의 인자로 전달되어 함수가 완료했을때 호출되는 함수를 의미
- 네트워크 요청을 보내는 작업은 시간이 오래걸려 비동기적으로 처리, 작업이 완료되면 해당 요청에 대한 응답을 처리하기 위해 콜백 함수를 호출
struct ContentView: View { var body: some View { Text("Hello, world!") .onAppear { guard let url = URL(string: "<https://example.com>") else { return } URLSession.shared.dataTask(with: url) { data, response, error in if let data = data { print("Data received: \\(data)") } else if let error = error { print("Error occurred: \\(error.localizedDescription)") } }.resume() } } }
- Event-Driven
- 주로 사용하는 곳
- 네트워크 호출
- 파일 I/O(컴퓨터에서 파일을 읽거나 쓰는 작업)
- 이벤트 처리
- 이러한 작업들은 동기적으로 실행하면 시간이 많이 소요하여 비동기적으로 효율적으로 처리
스레드
- 동시에 실행될 수 있는 코드 실행 흐름의 단위
- 멀티스레드를 이용하여 동시성 처리가 가능
'기타 > Today I Learned' 카테고리의 다른 글
[TIL] Swift 문법 클래스와 구조체 (0) | 2023.04.27 |
---|---|
[TIL] Swift 문법 열거형(Enum) (0) | 2023.04.25 |
[TIL] Swift 문법(함수 Functions) (1) | 2023.04.23 |
[TIL] Swift 제어문 (1) | 2023.04.22 |
[TIL] Swift 문법(문자열과 문자) (0) | 2023.04.17 |