해당 글은 앨런의 특강을 듣고 작성한 글입니다
모든 이미지는 앨런의 강의에서 참고하였으며,
자세한 강의 내용은 앨런 유튜브 혹은 인프런 강의 에서 볼 수 있습니다


비동기 처리가 필요한 이유??

네트워크 통신과 비동기 처리!

앱을 만들게 되면 데이터를 서버에서 받아오는 경우가 많다.

서버에 데이터를 요청하는 일은 부하가 많이 걸리기 때문에 그렇다.




이게 무슨 말인지 모르겠다면??
일단 핸드폰에서 CPU가 어떻게 연산 처리를 진행하는지부터 알아보자!!!

우리 핸드폰이나 컴퓨터의 CPU에는 아래 같은 용어들로 스펙이 정리되어 있다

코어 / 쓰레드 / 클럭

코어 : CPU내의 연산 회로의 개수 라고 할 수 있다

쓰레드 : 작업(연산)의 흐름이라고 할 수 있다. 쓰레드가 많다는 것은 "입(CPU)은 한 개이지만 손(쓰레드)가 여러 개여서 밥을 빠르게 먹는다"라고 이해하면 빠를 것이다.

클럭 : 진동 단위를 말하는 것으로 높을 수록 빠르게 연산을 한다고 할 수 있다. 클럭이 높을 수록 발열 문제나, 배터리 문제가 생길 수 있어, 물리적으로 클럭을 계속 올리는 것에서는 한계가 있다.


소프트웨어적인 Thread / 물리적인 Thread

우리가 보통 아는 CPU의 쓰레드는 물리적인 쓰레드를 지칭하는 것이다. 소프트웨어적인 쓰레드는 소프트웨어적으로 쓰레드를 더 작은 단위로 나눈 것을 말한다. 현재 물리적인 쓰레드는 하나의 코어당 2개의 쓰레드를 하이퍼쓰레딩 기술을 통해서 만들어 내고 있는데, 실제로 프로세스를 실행해서 소프트웨어적인 쓰레드를 파악해보면 100개 보다 더 많은 수를 가질 수 있다.


이제 다시 와서 그럼 왜 네트워크 통신 같은 작업들은 부하가 많을까??
앱의 시작과정에서 보면 메인 쓰레드는 여러 가지 작업을 맡고 있다

Running (실행중)일 때

앱의 작업을 보면 Event 발생을 파악하고 그에 대한 반응을 하기 위해서 Event Loop 라는 것이 끝 없이 돌고 있다.

이벤트가 발생하면 운영체제 는 포트를 통해서 App 에게 이벤트에 대한 정보를 Event Queue에 전달한다.

그럼 그 Queue에서 하나씩 메인 런루프에서 처리하면서 앱 객체에 알려주고

그 이벤트들은 이제 어떤 행동으로 취해지게 될 것인지 정하게 되고, 그러면 그것을 이제 화면에 표시하는 방식으로 작동한다.

여튼 그래서 메인 런루프에서 이벤트 핸들링을 하고 있으며, 이벤트에 대한 화면 업데이트를 1번 쓰레드(메인 쓰레드)에서 하고 있으며 이는 1초에 60번 정도의 속도로 작동하고 있다.

그러니 메인 쓰레드의 경우는 화면을 렌더링 하는 일과 같이 여러 가지 일을 하고 있기에, 오래 걸리는 일(부하가 큰 일)의 경우는 메인 쓰레드에서 시키면 안된다는 것이다

그래서 우리가 비동기 프로그래밍, 동시성 프로그래밍에 대해서 학습하는 것이다.

iOS에서는 작업(Task)를 대기행렬(큐, Queue)에 보내기만 하면,

우리의 iOS(운영체제시스템)가 알아서 여러쓰레드로 나눠서 분산처리를 한다(동시적 처리)

그러니 우리는 동시성 처리를 하기 위해서 큐에 Task를 보내주기만 하면 되는 것이다

iOS 프로그래밍에서는 대기열에 크게 2가지 종류가 있다

  1. DispatchQueue / 2. OperationQueue

    • 직접적으로 쓰레드를 관리하는 개념이 아닌, 대기열(Queue)의 개념을 이용해서, 작업을 분산처리하고, OS에서 알아서 숫자(갯수)를 관리
    • (쓰레드 객체를 직접 생성시키거나 하지 않는) 쓰레드 보다 더 높은 레벨 / 차원에서 작업을 처리
    • 메인 쓰레드(1번)가 아닌 다른 쓰레드에서 오래걸리는 작업(ex: 네트워크 처리) 들과 같은 작업들이 쉽게 비동기적으로 동작하도록 함.

    여기서 잠깐 병렬(Parallel) vs 동시(Concurrency)

    병렬(Parallel) - 물리적인 쓰레드에서 실제 동시에 일을 하는 개념

    • 내부적으로 알아서 동작하기 때문에 개발자가 전혀 신경쓸 필요가 없는 영역이다

    동시성(Concurrency) - 메인 쓰레드가 아닌 다른 소프트웨어적인 쓰레드에서 동시에 일을 하는 개념

    • 개발자가 신경써야 되는 부분
      • 물리적인 쓰레드를 알아서 swtiching하면서 엄청나게 빠르게 일을 처리
      • (예) 2개의 쓰레드에서 일을해도 내부적인 물리적인 쓰레드는 1개만 동작하고 있을 수도 있음

    우리는 이제 동시성 프로그래밍에 대해서 배울 것이다!!!

    성능 / 반응성 / 최적화와 관련된 것이니 매우 중요!!!

    (화면의 버벅 거림의 문제를 해결하기 위한 프로그래밍 기법)


취소 기능이 필요한 경우

하지만 실무에서는 OperationQueue를 통해서 저렇게 이미 지나간 영역까지 비동기 작업 취소를 하는 경우는 거의 없다. 대부분 DispatchQueue를 이용해서 간단하게 처리해준다.



비동기(async), 동시(Concurrent)의 개념

1) Synchronous(동기) vs Asynchoronous(비동기)

비동기 처리의 경우 Task를 Thread에 올려놓자마자 바로 return을 한다.

일을 시작 시키고, 작업이 끝날때까지 "안기다리며" 리턴을 하며 바로 다른 Task를 처리하는 것이다

동기 처리의 경우는 작업을 시키고, 뿐만아니라 해당 작업이 끝날때까지 "기다리는" 것이다.

사실상 다른 쓰레드에서 동기 작업을 한다는 것은 메인 쓰레드에서 작업을 진행하는 것과 차이가 없다.

2) serial(직렬) vs Concurrent(동시)

직렬 -> 하나의 the other 쓰레드에서 순서대로 처리할 경우

동시(Concurrent) -> 분산처리 시킨 작업을 others 쓰레드에서 한 번에 처리하는 경우

그러니

직렬큐는 순서가 필요한 작업, 동시큐는 순서보다는 동시에 여러 작업을 처리하는데 사용한다.


GCD의 개념 및 종류

우리가 iOS 프로그래밍에서 비동기 프로그래밍을 하기 위해서 사용하는 것은 크게

1) DispatchQueue(GCD)

2) OperationQueue

두개인데, 가장 많이 사용하고 중요한 GCD에 대해서 정리해보자!!

DispatchQueue의 종류 위 처럼 세 가지 있다.

위 내용 중 Quality Of Service (Qos)는 Queue의 품질을 정할 수 있는 값이다

iOS가 알아서 우선적으로 중요한 일임을 인자하고 쓰레드에 우선순위를 매겨 더 많은 쓰레드를 배치하고 CPU의 배터리를 더 집중해서 사용하도록 해서 끝내도록 하는 개념

외울 필요는 그닥 없는 것 없다. .default, .utillity에 대해서만 알아두고 나머지는 찾아서 쓰자

이렇게 다양한 종류의 Qos 가 있다고 하나,

현업에서는 .default, .utility만 거의 사용되고 있다.

프라이빗(Custom) 큐 : DispatchQueue(label: "...")

커스텀으로 만드는 큐, 기본설정 직렬(Serial), Qos(설정 가능)


GCD 사용시 주의 사항

1) 반드시 메인큐에서 처리해야하는 작업

UI 관련 일들은 다시 메인쓰레드로 보낼 필요가 있다

다른 쓰레드에서 작업을 하더라도 UI관련 업무는 바로 다시 main 쓰레드로 보내는 것을 볼 수 있다.

코드)



2) 컴플리션핸들러의 존재이유 - 올바른 콜백함수의 사용

비동기 Task를 진행하다가 보면 언젠가, 프로그래머는 비동기작업의 끝나는 시점이 필요할 때가 생길 것이다.

그럴 경우에는 함수 설계를 다르게 해야된다

그리고 비동기 함수는 99% return 값을 가지게 하면 곤란해진다...

why???

이 함수를 보면 URLSession의 dataTask 함수가 비동기로 작업을 처리하는 것을 볼 수 있다.

(URLSession은 내부적으로 원래 비동기 함수이다.)

이러할 경우 비동기 작업이 끝나기 전에 return을 해버리기 때문에 비동기 작업과 관계없이 무조건 nil이 return된다.

그러면 어떻게 설계를 해야되나?? closure를 통해서 하자! 콜백함수!!!

코드)

즉, 우리가 직접 콜백함수를 통해서 비동기 작업이 끝난 뒤에 수행될 작업을 정해주는 것이다!

=> But, Swift 5.5에서 도입되는 새로운 개념으로 인해서 바뀔 것이다!!

return 값을 줘도 비동기 함수가 제대로 작동할 것이다!



3) weak, strong 캡처의 주의

학습 후 업로드
6/22일자 수정 업로드

메모리 누수를 대비한 ARC의 reference counting을 고려한 weak 사용을 하자!



4) 동기함수를 비동기적으로 동작하는 함수로 변형하는 방법

동기 함수 부분에 DispatchQueue를 활용해서 비동기화 시켜버림

비동적으로 구현된 메서드 / 함수

URLSession.shared.dataTask와 같은 메서드는 내부적으로 비동기 함수로 구현되어 있다!


Swift 5.5이후에 도입될 Async / await in Swift

비동기함수를 이어서 처리하는 것(코드상의 불편함) 해결 / Swift 5.5에서 도입

이렇게 콜백함수에서 또 다른 함수를 실행하고 콜백함수에서 다른 함수를 호출하는 꼬리물기가 계속될 경우 코드는 피라미드 형태처럼 될 것이다. 굉장히 불편하다...

But, 이제 async와 await 을 통해서, 함수 자체를 비동기로 정의할 수 있게 되었고, 리턴시 까지 await를 통해서 해동 구문을 기다릴게 할 수 있다.


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

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

  • 멀티 쓰레의 환경에서, 같은 시점에서 여러 개의 쓰레드가 하나의 메모리에 동시접근 하는 문제
  • 이럴 경우, 해결 방법
    - (메모에) 쓰고 있는 동안에는 여러 쓰레드에서 접근하지 못하도록 Lock(잠금) -> Thread Safe 처리
    - 하나의 메모리에 접근하는 부분을 Serial 큐에 보내버려서 동시에 접근하지 않도록 제한해 버리기

2) 교착상태(Deadlocks)

  • 멀티 쓰레드 환경에서, 베타적 메모리사용으로 일이 진행되지 않는 문제

profile
iOS Developer Student

0개의 댓글