[iOS/Swift] 비동기 프로그래밍에 대한 이해

최정은·2023년 9월 12일
0

Swift

목록 보기
21/27

동기와 비동기

동기(sync)

  • 다른 쓰레드로 작업을 보내고, 그 작업이 끝날 때까지 기다린다.

비동기(aysnc)

  • 다른 쓰레드로 작업을 보내고, 작업이 끝나는 것을 기다리지 않고 다른 작업을 시작한다.

직렬과 동시

직렬

  • 하나의 쓰레드에서만 작업을 처리한다.
  • 순서가 중요한 작업을 처리할 때 사용

동시

  • 몇 개의 쓰레드를 사용하는지 알 수는 없지만, 여러 개의 쓰레드에서 작업을 처리한다.
  • 각자 독립적이지만 유사한 여러개의 작업을 처리할 때 사용

동시성 처리

  • 직접적으로 쓰레드를 관리하는 개념이 아닌, 대기열(Queue)의 개념을 이용해서, 작업을 분산처리하고, OS에서 알아서 쓰레드 숫자(갯수)를 관리
  • 메인쓰레드가 아닌 다른 쓰레드에서 오래 걸리는 작업(예: 네트워크 처리)들과 같은 작업들이 쉽게 비동기적으로 동작하도록 함
  • 성능 / 반응성 / 최적화와 관련 (화면의 버벅 거림의 문제를 해결하기 위한 프로그래밍 기법)

병렬과 동시

동시

  • 실제 1개의 쓰레드(물리적)가 OS영역에서 여러개의 쓰레드로 나뉠 수 있다. 나뉘어진 여러개의 쓰레드(소프트웨어적)는 앱 단위에서 각기 다른 역할로 쓰일 수 있다.
  • 실제 1개의 물리적 쓰레드가 여러개의 소프트웨어적 쓰레드로 나뉘어 여러가지의 작업을 처리하는 것 (소프트웨어적 쓰레드 관점)

병렬

  • 실제로 물리적인 쓰레드가 여러개 있고, 여러개의 쓰레드가 각자의 작업을 하는 것 (물리적인 쓰레드 관점)

Queue(대기열)의 종류

  1. DispatchQueue (GCD - Grand Central DispatchQueue)

  2. OperationQueue

큐의 종류생성 코드특징직렬/동시
.mainDispatchQueue.main메인큐
(UI업데이트 내용 처리하는 큐)
Serial
(직렬)
Dispatch
Queue
(GCD)
.global()DispatchQueue.global(qos: )6가지 Qos(작업에 따라 Qos상승가능)
(시스템이 우선순위에 따라 더 많은 쓰데드를 배치하고,
배터리를 집중해서 사용하도록 함)
Concurrent
(동시)
custom
(프라이빗)
DispatchQueue(label: "...")Qos추론/Qos설정가능디폴트: Serial
둘 다 가능
(attributes로 설정)
OperationQueuelet opQ = OperationQueue()디폴트: .background
기반(underlying) 디스패치큐에 영향 받음
(unspecified)를 제외한 5가지)
디폴트: Concurrent
둘 다 가능
(maxConcurrentOperationCount로
사용할 쓰레드 갯수 설정 가능)

큐의 Qos (서비스품질)

  1. userInteractive
  • 유저와 직접적 인터렉티브: UI 업데이트 관련(직접X), 애니메이션, UI반응관련 어떤 것이든
    (사용자와 직접 상호 작용하는 작업에 권장, 작업이 빨리 처리되지 않으면 상황이 멈춘 것처럼 보일만한)
  • 소요시간: 거의 즉시
  1. userInitiated
  • 유저가 즉시 필요하긴 하지만, 비동기적으로 처리된 작업
    (ex. 앱내에서 pdf 파일을 여는 것과 같은, 로컬 데이터베이스 읽기)
  • 소요시간: 몇초
  1. default
  • 일반적인 작업
  1. utility
  • 보통 Progress Indicator와 함께 길게 실행되는 작업, 계산
    (ex. Networking, 지속적인 데이터 feeds)
  • 소요시간: 몇초에서 몇분
  1. background
  • 유저가 직접적으로 인지하지 않고(시간이 안 중요한)작업
    (ex. 데이터 미리가져오기, 데이터베이스 유지보수, 원격 서버 동기화 및 백업 수행)
  • 소요시간: 몇분이상 (속도보다는 에너지효율성 중시)
  1. unspecified
  • legacy API 지원
    (스레드를 서비스 품질에서 제외시키는)

GCD 사용 시 주의사항

  1. 반드시 메인큐에서 처리해야하는 작업
    에러 발생 : UI와 관련된 작업들은 메인 쓰레드에서 처리하지 않으면 에러가 발생!
    메인쓰레드: UI와 관련된 작업들을 메인쓰레드에서 처리할 수 있도록 메인큐를 통해서, 작업을 다시 메인쓰레드로 보냄
DispatchQueue.global(qos: .utility).async{
	...
    DispatchQueue.main.async{
    	self.textLabel.text = "New posts updated!"
        }
}
  1. 컴플리션핸들러의 존재이유 - 올바른 콜백함수의 사용
    잘못된 함수설계: 비동기적인 작업을 해야하는 함수를 설계할 때 return을 통해서 데이터를 전달하려면 항상 nil이 반환
    제대로된 함수설계: 비동기적인 작업을 해야하는 함수는 항상 클로저를 호출할 수 있도록 함수를 설계해야 함
func getImages(..., c...: @escaping (UIImage?) -> Void) {
	...
    URLSession.shared.dataTask(..){
    	...
        completion(photoImage) // 함수 내부의 일이 끝나면 completion 클로저 호출
    }.resume()
}
  1. weak, strong 캡처의 주의 - 객체 내에서 비동기코드 사용 시
    강한참조: 캡처리스트 안에서 weak self 로 선언하지 않으면 강한 참조(strong)
    1) 서로를 가리키는 경우 메모리 누수(Memory Leak)발생 가능
    2) (메모리 누수가 발생하지 않아도) 클로저의 수명주기가 길어지는 현상이 발생할 수 있음
    약한참조: 대부분의 경우 캡처리스트 안에서 weak self로 선언하는 것을 권장
DispatchQueue.global(qos: utility).async{ [weak self] in 
	guard let 'self' = self else {return}
	...
    DispatchQueue.main.async{
    	self.textLabel.text = "New posts update!"
    }
}
  1. 동기함수를 비동기적으로 동작하는 함수로 변형하는 방법
    일반(동기) 함수 : 오래 걸리는 일반적인 함수를 단순히 동기함수로 만들면 메인쓰레드에 부하가 걸림
    비동기함수로 : 오래걸리는 일반적인 함수를 내부에 비동기적 처리를 하면 비동기로 동작하는 함수로 변형 가능
func doSomething(com: @escaping (Void) -> Void) {
	DispatchQueue.global().async{
    	...
    }
}

비동기적으로 구현된 메서드

  1. URLSession.shared.dataTask
  • 이미 내부적으로 GCD를 이용해서, 비동기적으로 처리하는 메서드로 생각해야 됨

동시성 프로그래밍과 관련된 문제점

경쟁상황 / 경쟁조건 (Race Condition)

  • 멀티 쓰레드의 환경에서, 같은 시점에 여러개의 쓰레드에서 하나의 메모리에 동시접근 하는 문제
  • (메모리에) 쓰고 있는 동안에는 여러 쓰레드에서 접근하지 못하도록 Lock(잠금) -> Thread-Safe 처리

DeadLock

  • 멀티 쓰레드의 환경에서, 배타저인 메모리사용으로 일이 진행이 안되는 문제
  • 2개 이상의 쓰레드가 서로 배타적인 메모리의 사용으로 인해 (서로 잠그고 점유하려고 하면서) 메서드의 작업이 종료도 못하고 일의 진행이 멈춰버리는 상태

Thread Safe 처리

  • 다른 쓰레드에서 동시에 접근하지 못하도록 하는 여러가지 기법

동시큐에서 직렬큐로 보내기!!

직렬큐는 하나의 쓰레드에서만 한 번에 메모리에 접근하기 때문에 동시큐로 처리하는 중에 시리얼큐로 보낸다!

0개의 댓글