도니의 iOS 프로그래밍 세상

Swift Concurrency(1) - 기본개념 및 작동원리 본문

Swift

Swift Concurrency(1) - 기본개념 및 작동원리

Donee 2023. 2. 8. 18:34

1. Swift Concurrency의 도입 배경

iOS 13.0부터 도입된 Swift Concurrency는 비동기 프로그래밍을 보다 편리하게 할 수 있습니다.

그전에, Escaping closure를 사용해서 비동기 처리하는 방식의 문제점은 두가지였습니다.

첫번째로, 가독성이 좋지 않고, 복잡한 코드일수록 중첩된 클로저를 계속해서 작성해야 합니다.

두번째로, 중첩 클로저를 작성하며 에러코드 작성까지 완벽하게 하는 것은 힘들며 이로인해 프로그램 버그를 유발할 수 있습니다.

밑에 코드를 살펴보시죠.

해당 함수는 썸네일 이미지를 다운로드 받는 함수입니다.

이때, 이미지를 서버로부터 다운로드 받을때 escaping closure가 호출됩니다. 그리고 받은 데이터를 가지고 다시 prepareThumnail이라는 함수를 호출해야 하고 이때 한번 더 closure가 호출됩니다.

두번의 클로저 호출을 통해서 가독성이 점점 떨어지는 상황입니다. 클로저 호출시마다 에러처리 및 completion을 통해서 값을 방출하는 과정이 필요합니다. 이때 한번의 실수로 에러 케이스를 올바르게 처리하지 못한다면, 프로그램 전체에 에러를 발생시킬 수 있습니다.

Swift에서 제공하는 Result Type을 사용하여도 해당 문제를 피할 순 없습니다.

2. Swift Concurrency의 장점

그리하여 Swift는 기존의 복잡함을 덜어내고 async라는 키워드를 함수옆에 달아 간단하게 해결하였습니다.

async를 도입한 예시를 보며, 얼마나 더 간단해졌는지 확인해보겠습니다.

 

기준 20줄의 코드에서 6줄로 줄어들었으며, Depth가 사라졌습니다.

또한 사용자의 의도를 반영하여 더욱 읽기 쉽도록 변하였습니다.

에러는 함수 호출 statement의 바로 밑에서 처리되기에 실수 발생 가능성도 현저히 떨어집니다.

 

⇒ 결론적으로 async await의 사용으로 비동기 처리를 더욱 안전하고 ,짧고, 의도를 반영한 코드로 구현하는게 가능해졌다.

3. Swift Concurrency 사용방법

비동기 함수를 정의하기 위해선 async 키워드를 정의하는 함수에 붙여야 하며, 해당 함수를 호출하는 구문에서는 await라는 키워드를 사용해야 합니다.

func tenSecondsWaitFunction() async throws -> Int {
    try await Task.sleep(for: .seconds(10))
    return 10
}

해당 함수는 10초뒤에 10을 리턴하는 함수입니다. 해당 함수를 호출하기 위해선 두가지 방법이 존재합니다.

case 1. async 함수가 아닐때

만약 위의 함수를 async keyword가 아닌 함수가 호출하면 어떻게 될까요?

func normalFunction() {
	try await tenSecondsWaitFunction() 
}

컴파일러는 밑에와 같은 에러메세지를 출력합니다.

async call in a function that does not support concurrency

함수 내부 async call은 concurrency 지원을 하지 않는다

일반 함수는 async 함수를 await 키워드를 붙인다고 해서 호출할 수 없습니다.

이를 호출하기 위해서 Swift는 Task라는 instance를 제공하며 해당 클로저에서 비동기 작업을 수행할 수 있도록 합니다.

func normalFunction() {
	Task {
		try await tenSecondsWaitFunction() 
	}
}

Task를 사용함으로써 Normal 함수에서도 비동기 프로그래밍을 하는게 가능합니다.

Task는 error를 do, catch를 통해서 처리하지 않아도 에러를 발생시키지 않습니다.

Case 2. async 함수에서 호출할 때

func asyncFunction() async throws {
	try await tenSecondsWaitFunction() 
}

Async함수의 경우 async함수를 호출할때 await의 키워드를 붙이는 것만으로 가능합니다.

4. Swift Concurrency 원리

일반 함수의 작동 원리

그렇다면 Async함수를 OS에서는 어떻게 처리하고 있을까요?

그전에 쓰레드와, 일반적인 함수의 작동을 알아보겠습니다

일반 함수들이 호출된다면 쓰레드는 하나의 함수를 작업하는데 할당되었습니다.

그리하여 담당 쓰레드가 함수 작업을 완료할때까지 하나의 함수를 대신하여 작업 수행하는데 사용됩니다.

이때, 해당 함수가 쓰레드의 제어를 포기할 수 있는 방법은 “함수 종료”뿐입니다.

async함수의 작동원리

Async 함수의 작동원리는 일반 함수와 다른점이 존재합니다.

일단 async 함수를 호출할 때, 쓰레드는 해당 함수를 완전히 제어하고 있다는 점은 동일합니다.

하지만, async함수는 await와 같은 함수가 작동될때 suspend(중단)되고 쓰레드의 제어를 포기합니다. 함수에게 쓰레드 제어가 다시 돌아가는 것 대신, 시스템에게 쓰레드 제어권을 제공합니다.

 

이는 결국 함수는 suspend되어있고, 시스템이 위임된 수많은 Task중 가장 중요한 작업을 시스템이 선별해서 우선적으로 처리하는 것입니다.

그리하여, 시스템이 suspended된 async함수를 재개를 결정하고, 해당 async 함수는 다시 쓰레드 제어권 가지고 일을 처리할 수 있는 것입니다. (이는 OS에서 스케쥴러와 유사합니다. 프로세스간에 우선순위가 존재하고 이를 순차적으로 처리하는 방식과 유사합니다. )

 

⇒ 결과적으로 async 함수는 처음에 일반적인 함수처럼 작동됩니다. 그러던 중 await와 같은 비동기 함수를 만나게 된다면 function은 suspend되고 해당 제어권을 “시스템”이 가지고 있습니다. 그러던중 시스템이 해당 작업을 우선적으로 처리하려고 결정하면, 작업이 재개됩니다.

 

그림으로 나타내면 다음과 같습니다

 

하지만 이러한 방식으로 인해 반드시 async함수가 suspend되진 않으며, suspend될 가능성이 있다는 걸 의미합니다.(await또한 마찬가지입니다.)

결론적으로, 중단이 없는 상황이거나 재개가 이루어진 상황 모두 값과 Error와 함께 쓰레드 제어를 전달하며 함수가 종료되는건 노말 함수와 동일한 과정입니다. 

 

결론적으로, 장점은 컨트롤을 가진 작업들을 바로 시작하지 않아도 되기 때문에, 우선순위 따라 작업을 할 수 있다는 점입니다

이로서, 함수가 중단되는 동안 시스템이 자유롭게 작업을 할 수 있다.

Await와 Race Condition

그리하여 주의해야할 사실은 await keyword가 반드시 하나의 transaction으로 일어나지 않는다는 점을 알아야 한다

var url = "exampleImage.commmmm"
func someFunction() {
	Task {
		imageView.image = try await imageCache(url: url)
		anotherImageView.image = try await imageCache(url: url)
	}
}

func changeURL() {
		Task {
		  Task.sleep(for: .seconds(3))
			url = "exampleImage.nettt"
		}
}

func totalFunction() { 
	someFunction()
	changeURL()
}

우리는 imageView와 anotherImageView에 동일한 url에서 다운받은 이미지를 넣고싶어 한다.

하지만, changeURL 함수는 3초뒤에 url을 다른 url로 바꾸는 함수이다.

결론적으로, await를 연속적으로 사용한다고 해서 하나의 transaction처럼 작동하지 않는다.

함수가 중단되는 동안 앱의 상태가 바뀔 가능성을 항상 생각해야한다. 그리하여 함수 line 사이사이 다른 쓰레드에서 동작할 수 있다는 것을 가정하고, 앱의 상태또한 유의해야한다.

이러한 Data Race를 해결하기 위해서 Actor와 같은 개념이 나왔으니 더욱 보면 좋을듯 합니다!

결론

이번시간에는 Swift Concurrency의 기본원리 및 작동 방법에 대해 배웠습니다.

다음 시간에는 Swift Concurrency를 더욱 잘 처리하기 위한 try-catch에 대해 배우도록 하겠습니다.

출처

https://developer.apple.com/videos/play/wwdc2021/10132/

https://www.google.com/search?client=safari&rls=en&q=swift+org+concurrency&ie=UTF-8&oe=UTF-8

 

Comments