동기와 비동기의 개념
쓰레드 (Thread)
컴퓨터 프로그램을 실행하는데 사용되는
가장 작은 실행 단위
쓰레드는 프로세스 내에서 실행되는 작은 단위의 실행 흐름
각각의 쓰레드는 독립적으로 실행될 수 있으며,
여러 쓰레드가 동시에 작업을 수행가능
이러한 다중 쓰레드 작업은 시스템 자원을 효율적으로 활용,
병렬적 작업 처리가능
동기 (Synchronous)
순차적으로 진행되는 것을 의미
한 작업이 시작되면 그 작업이 완료될 때까지 다음 작업은 대기
즉, 작업이 차례대로 실행되며, 한 작업이 끝날 때까지 다음 작업이 기다림
이렇게 되면 작업들이 순차적으로 실행되므로 순서가 중요한 경우에 사용
비동기 (Asynchronous)
순차적으로 기다리지 않고, 여러 작업이 동시에 진행되는 것을 의미
한 작업이 시작되더라도 결과를 기다리지 않고 다음 작업이 시작될 수 있음
비동기 작업은 대표적으로 네트워크 요청, 파일 입출력, 사용자 입력 대기 등과 관련 있음
비동기적인 작업을 사용하면 여러 작업이 동시에 처리되기에 시스템 자원을 효율적으로 사용가능
DispatchQueue
DispatchQueue는, iOS에서 Grand Central Dispatch(GCD)를 사용하여
비동기적으로 작업을 관리하는데 사용되는 클래스
다양한 작업을 백그라운드 스레드에서 실행, 메인 Thread와 같은 특정 스레드에서 실행되도록 예약가능
이를 통해 앱의 성능을 향상시키고 사용자 인터페이스의 반응성을 유지가능
일반적으로 다음과 같은 두 가지 유형의 DispatchQueue가 사용
Serial Queue (직렬 큐)
작업을 순차적으로 실행하는 Queue
한 번에 하나의 작업만 실행, 이전 작업이 완료된 후에 다음 작업이 실행
Concurrent Queue (병렬 큐)
여러 작업을 동시에 실행 가능한 Queue입니다.
병렬 큐는 여러 작업을 동시에 시작하고, 시스템 자원과 상황에 따라 동시에 실행
// MARK: - DispatchQueue를 사용하여 작업을 예약, 실행 간단예제
// Serial Queue 생성
let serialQueue = DispatchQueue(label: "com.example.serialQueue")
// Concurrent Queue 생성
let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
// 작업 예약
serialQueue.async {
// 직렬 큐에서 실행될 작업
print("Serial Queue 작업 1")
}
concurrentQueue.async {
// 병렬 큐에서 실행될 작업
print("Concurrent Queue 작업 1")
}
// 메인 스레드에서 실행될 작업
DispatchQueue.main.async {
// UI 업데이트 등을 포함한 메인 스레드 작업
print("메인 스레드 작업")
}
[DispatchQueue 예약 예제 코드분석]
async 메서드를 사용하여 작업을 각 큐에 예약
async 메서드를 호출함으로써 코드 블록이 해당 큐에 추가,
해당 큐에서 비동기적으로 실행
Thread 사용에서, 주의할 점으로
UI 업데이트와 관련된 작업은 반드시 메인 스레드에서 실행되어야 하므로,
UI 업데이트와 관련된 코드는 "DispatchQueue.main.async"를 사용하여
메인 스레드에서 실행되어야 함
// MARK: - 동기 예제
// Serial Queue를 사용한 동기
import Foundation
func syncOperation() {
print("시작")
let queue = DispatchQueue(label: "com.example.queue")
queue.sync {
for i in 1...3 {
print("동기 작업 \(i)")
}
}
print("끝")
}
syncOperation()
[동기 예제 코드분석]
"시작", "동기 작업 1", "동기 작업 2", "동기 작업 3", "끝" 순서로 메시지 출력
↓
DispatchQueue의 sync 메서드를 통해 클로저 내의 작업이 순차적으로 실행되는 것을 보여줌
↓
이 때, sync 메서드를 사용하면 해당 작업이 완료될 때까지 현재 스레드가 차단되므로 주의
// MARK: - 비동기 예제
// Serial Queue를 사용한 비동기
import Foundation
func asyncOperation(completion: @escaping () -> Void) {
print("시작")
let queue = DispatchQueue(label: "com.example.queue")
queue.async {
for i in 1...3 {
print("비동기 작업 \(i)")
}
completion() // 작업이 완료된 후에 콜백 함수 호출
}
}
asyncOperation {
print("끝")
}
[비동기 예제 코드 분석]
"시작", "비동기 작업 1", "비동기 작업 2", "비동기 작업 3", "끝" 순서로 메시지를 출력
↓
DispatchQueue의 async 메서드를 통해 클로저 내의 작업이 비동기적으로 실행
↓
작업이 완료된 후에 콜백 함수가 호출되는 것을 보여줌
방법1 - Swift로 비동기 프로그래밍 구현
Callback을 활용한 비동기 프로그래밍
fetchDataUsingCallback 함수는 주어진 URL에서 데이터를 다운로드
↓
다운로드가 완료되면 클로저를 호출하여 결과를 전달
순수 Swift만으로 구현 가능
여러개의 연쇄 비동기 작업을 처리해야 하는 경우
↓
Callback Hell (콜백 지옥) 혹은 pyramid of doom (파멸의 피라미드) 라고도 불리는,
callback의 깊이가 깊어지는 단점이 있음
import Foundation
func fetchDataUsingCallback(from url: URL, completion: @escaping (Result<Data, Error>) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
let error = NSError(domain: "InvalidData", code: 0, userInfo: nil)
completion(.failure(error))
return
}
completion(.success(data))
}
task.resume()
}
// 단일 사용 예제
guard let url = URL(string: "https://www.example.com/data") else { return }
fetchDataUsingCallback(from: url) { result in
switch result {
case .success(let data):
print("Received data: \(data)")
case .failure(let error):
print("Error: \(error)")
}
}
// 연속된 비동기 처리 예제
guard
let url1 = URL(string: "https://www.example.com/data/1"),
let url2 = URL(string: "https://www.example.com/data/2")
else {
return
}
fetchDataUsingCallback(from: url1) { result in
switch result {
case .success(let data1):
print("Received data 1: \(data1)")
fetchDataUsingCallback(from: url2) { result in
switch result {
case .success(let data2):
print("Received data 2: \(data2)")
case .failure(let error2):
print("Error 2: \(error2)")
}
}
case .failure(let error1):
print("Error 1: \(error1)")
}
}
Concurrency를 활용한 비동기 프로그래밍
fetchDataUsingConcurrency 함수가 async로 선언되어 있음
await 키워드를 사용하여 URLSession의 data(from:) 메서드를 호출하고
↓
데이터 다운로드가 완료될 때까지 일시 중단
Callback 과 달리, 비동기 코드를 동기적으로 작성하는 것처럼 보이게 하면서도
백그라운드 스레드에서 비동기적으로 실행가능
순수 Swift만으로 구현가능 (단, Swift 5.5 이상 버전)
import Foundation
func fetchDataUsingConcurrency(from url: URL) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
// 단일 사용 예제
Task {
do {
guard let url = URL(string: "https://www.example.com/data") { return }
let data = try await fetchDataUsingConcurrency(from: url)
print("Received data: \(data)")
} catch {
print("Error: \(error)")
}
}
// 연속된 비동기 처리 예제
Task {
do {
guard
let url1 = URL(string: "https://www.example.com/data/1"),
let url2 = URL(string: "https://www.example.com/data/2")
else {
return
}
let data1 = try await fetchDataUsingConcurrency(from: url1)
print("Received data 1: \(data1)")
let data2 = try await fetchDataUsingConcurrency(from: url2)
print("Received data 2: \(data2)")
} catch {
print("Error: \(error)")
}
}
Async
async 키워드는 비동기 작업을 나타내는 함수나 블록을 표시하는 데 사용
이 키워드를 함수 앞에 붙이면 해당 함수는 비동기적으로 실행
비동기 함수는 작업이 완료될 때까지 기다리지 않고 다른 작업을 계속할 수 있도록 함
함수 내부에서 await를 사용하여 비동기 작업이 완료될 때까지 대기하고 결과를 가져올 수 있음
Await
await는 async 함수 내에서 사용,
비동기 작업의 완료를 기다리고 해당 작업의 결과를 반환
await는 값을 반환하는 함수나 메서드 앞에 사용
비동기 작업이 완료될 때까지 실행을 일시 중지하고 그 결과를 반환
Task
Task는 비동기적으로 실행되는 코드 조각을 나타냄
각각의 Task는 독립적으로 실행가능
비동기적인 작업을 수행
(이는 쓰레드를 직접적으로 다루지 않고도 비동기 코드를 실행하고 관리를 도움)
방법2 - Reactive Programming으로 비동기 프로그래밍 구현하기
Reactive Programming 의 이해
Reactive Programming은 데이터 스트림과 변화에 반응하여 데이터 처리를 수행하는 프로그래밍 패러다임
이 패러다임은 데이터 스트림의 발생, 전달, 처리를 중심, 데이터의 비동기적인 변화를 다루기 위해 설계됨
Reactive Programming 구현체의 이해
Reactive Programming의 개념을 구현하는 Swift기반 프레임워크인 Combine과 RxSwift
Combine
Combine은 Apple이 iOS 13부터 소개한 프레임워크,
Swift에서 Reactive Programming을 위한 공식 라이브러리
Publisher, Subscriber, Operator 등의 개념을 사용하여
데이터 스트림의 생성, 변형, 구독 등을 다룸
RxSwift
Swift에서 Reactive Programming을 위한 써드파티 라이브러리
Observable, Observer, Operator 등의 개념을 사용하여
데이터 스트림의 생성, 변형, 구독 등을 다룸
Reactive Programming 구성 요소의 이해
Observable(RxSwift) / Publisher(Combine)
데이터의 스트림을 나타내는 핵심 개념으로, 데이터의 발행자
Observer(RxSwift) / Subscriber(Combine)
Observable / Publisher가 발행한 데이터를 구독하여 처리하는 역할을 함
Operators
데이터 스트림을 변환하거나 조작하기 위한 함수들을 제공
map, filter, merge 등의 연산자를 이용하여 데이터를 다룸
Schedulers
비동기 작업을 수행하는 스케쥴러를 제공하여 코드 실행의 시기와 순서를 관리
Combine을 활용한 비동기 프로그래밍
Combine에서, Publisher ↔ Subscriber간의 기본적인 관계
Subscriber가 Publisher에 붙음
Publisher가 Subscription을 보냄
↓
Subscriber는 Subscription을 통해 Publisher에게 N개의 value를 받겠다고 요청
Publisher는 Subscriber에게 자유롭게 값을 보냄
만약 Publisher가 제한되어 있다면 Completion이나 Error를 내려보냄
fetchDataUsingCombine 함수가 AnyPublisher을 반환
함수는 URLSession의 dataTaskPublisher를 사용하여
데이터를 다운로드, Combine의 map 및 sink 연산자를 사용하여 데이터나 오류를 처리
외부 라이브러리를 설치해야하는 RxSwift 와는 달리,
별다른 외부 라이브러리의 설치가 필요없으며,
내장된 Combine을 import하여 바로 사용가능
여러개의 연쇄 비동기 작업을 처리해야 하는 경우
callback에 비해 비교적 간단한 flatMap연산자를 활용하여 처리가능
import Combine
// URLSession Publisher 스트림 생성
func fetchDataUsingCombine(from url: URL) -> AnyPublisher<Data, Error> {
return URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.mapError { $0 as Error }
.eraseToAnyPublisher()
}
// 단일 사용 예제
guard let url = URL(string: "https://www.example.com/data") else { return }
var cancellables: Set<AnyCancellable> = []
fetchDataUsingCombine(from: url)
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
break // 성공적으로 완료된 경우 아무 작업도 하지 않는다.
case .failure(let error):
print("Error: \(error)")
}
}, receiveValue: { data in
print("Received data: \(data)")
})
.store(in: &cancellables)
// 연속된 비동기 처리 예제
guard
let url1 = URL(string: "https://www.example.com/data/1"),
let url2 = URL(string: "https://www.example.com/data/2")
else {
return
}
fetchDataUsingCombine(from: url1)
.handleEvents(receiveOutput: { data in
print("Received data1: \(data)")
})
.flatMap { firstData in
fetchDataUsingCombine(from: url2)
}
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
break // 성공적으로 완료된 경우 아무 작업도 하지 않는다.
case .failure(let error):
print("Error: \(error)")
}
}, receiveValue: { data in
print("Received data2: \(data)")
})
.store(in: &cancellables)
RxSwift를 활용한 비동기 프로그래밍
fetchDataUsingRxSwift 함수가 Observable을 반환
Observable은 비동기적으로 데이터를 다운로드 후 데이터가 발생할 때마다 onNext 이벤트를 발생시킴
외부 라이브러리인 RxSwift를 설치하여야 사용 가능
다양한 연산자를 사용할 수 있는 추가적인 라이브러리 생태계가 많이 발전되어 있음
여러개의 연쇄 비동기 작업을 처리해야 하는 경우
callback에 비해 비교적 간단한 flatMap연산자를 활용하여 처리가능
import RxSwift
// URLSession Observable 스트림 생성
func fetchDataUsingRxSwift(from url: URL) -> Observable<Data> {
return Observable.create { observer in
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
observer.onError(error)
} else if let data = data {
observer.onNext(data)
observer.onCompleted()
}
}
task.resume()
return Disposables.create {
task.cancel()
}
}
}
// 단일 사용 예제
guard let url = URL(string: "https://www.example.com/data") else { return }
var disposeBag = DisposeBag()
fetchDataUsingRxSwift(from: url)
.subscribe(onNext: { data in
print("Received data: \(data)")
}, onError: { error in
print("Error: \(error)")
})
.disposed(by: disposeBag)
// 연속된 비동기 처리 예제
guard
let url1 = URL(string: "https://www.example.com/data/1"),
let url2 = URL(string: "https://www.example.com/data/2")
else {
return
}
fetchDataUsingRxSwift(from: url1)
.do(onNext: { data in
print("Received data1: \(data)")
})
.flatMap { _ in
fetchDataUsingRxSwift(from: url2)
}
.subscribe(onNext: { data in
print("Received data2: \(data)")
}, onError: { error in
print("Error: \(error)")
})
.disposed(by: disposeBag)
'IOS > Swift-Study' 카테고리의 다른 글
[Swift-Study] iOS 앱 개발 심화 1주차 - Instruments (0) | 2024.05.01 |
---|---|
[Swift-Study] iOS 앱 개발 심화 1주차 - 동영상 재생하기 (0) | 2024.05.01 |
[Swift-Study] iOS 앱 개발 심화 1주차 - 디자인 패턴 (0) | 2024.05.01 |
[Swift-Study] iOS 앱 개발 심화 1주차 - 아키텍처 (0) | 2024.05.01 |
[Swift-Study] iOS 앱 개발 숙련 1주차 - 화면전환 (0) | 2024.04.14 |