비동기 프로그래밍이란?
동기(Synchronous) vs 비동기(Asynchronous) 프로그래밍
동기(Synchronous) 프로그래밍
하나의 작업이 끝날 때까지 다음 작업을 실행하지 않음
작업이 완료될 때까지 앱이 멈춘 것처럼 보일 수 있음
print("작업 시작")
sleep(3) // 3초 동안 멈춤
print("작업 완료")
문제점
sleep(3) 동안 다른 작업을 할 수 없음
네트워크 요청 같은 작업이 오래 걸리면 UI가 멈출 수 있음
비동기(Asynchronous) 프로그래밍
작업을 백그라운드에서 실행하고 완료될 때까지 기다릴 필요 없음
UI가 멈추지 않고 사용자 경험이 개선됨
print("작업 시작")
DispatchQueue.global().async {
sleep(3)
print("작업 완료")
}
print("다른 작업 실행 가능!")
// 작업 시작
// 다른 작업 실행 가능!
// (3초 후) 작업 완료
Swift에서 비동기 처리하는 방법
GCD(Grand Central Dispatch) – DispatchQueue 사용
iOS에서는 GCD를 사용해 멀티스레딩을 쉽게 구현할 수 있다.
DispatchQueue.global(qos: .background).async {
// 백그라운드에서 실행 (예: 네트워크 요청)
print("백그라운드 작업 실행")
DispatchQueue.main.async {
// UI 업데이트 (메인 스레드에서 실행)
print("UI 업데이트")
}
}
사용되는 큐의 종류
DispatchQueue.global(qos: .background) → 백그라운드 작업
DispatchQueue.global(qos: .userInitiated) → 사용자 요청 처리
DispatchQueue.main → UI 업데이트
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print("2초 후 실행됨")
}
OperationQueue 사용
GCD보다 더 객체지향적으로 비동기 작업을 처리할 수 있다.
let queue = OperationQueue()
queue.addOperation {
print("백그라운드 작업 실행")
OperationQueue.main.addOperation {
print("UI 업데이트")
}
}
GCD보다 유연한 작업 제어 가능
작업 간 의존성 설정 가능
특정 작업을 취소하거나 일시 중지 가능
async/await 사용 (Swift 5.5 이상)
비동기 코드의 가독성을 높이는 최신 문법
func fetchData() async {
print("데이터 요청 시작")
try? await Task.sleep(nanoseconds: 2_000_000_000) // 2초 대기
print("데이터 받아옴")
}
Task {
await fetchData()
}
비동기 코드를 동기 코드처럼 읽기 쉽게 작성 가능
네트워크 요청에서 비동기 프로그래밍 활용하기
전통적인 URLSession 방식
콜백 방식이라 코드가 복잡해질 수 있다.
let url = URL(string: "https://api.example.com/data")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
print("데이터 크기: \(data.count) bytes")
}
}.resume()
async/await을 활용한 네트워크 요청 (Swift 5.5 이상)
코드가 간결하고 가독성이 좋다.
func fetchData() async throws -> Data {
let url = URL(string: "https://api.example.com/data")!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
Task {
do {
let data = try await fetchData()
print("데이터 크기: \(data.count) bytes")
} catch {
print("에러 발생: \(error)")
}
}
비동기 프로그래밍 사용 시 주의점
UI 업데이트는 항상 메인 스레드에서 실행해야 함
백그라운드 작업이 끝난 후 UI를 변경하려면 DispatchQueue.main.async를 사용
DispatchQueue.global().async {
let data = fetchData()
DispatchQueue.main.async {
label.text = "데이터 업데이트 완료"
}
}
네트워크 요청 시 에러 처리를 반드시 추가해야 함
do {
let data = try await fetchData()
print("데이터 로드 성공")
} catch {
print("네트워크 요청 실패: \(error)")
}
메모리 관리 주의 – 강한 참조 순환 방지
weak self
를 사용해 메무리 누수 방지
class ViewController: UIViewController {
func loadData() {
Task { [weak self] in
guard let self = self else { return }
let data = try await fetchData()
print("데이터: \(data)")
}
}
}