티스토리 뷰

공부

Swift Concurrency와 Combine

Zedd0202 2021. 9. 4. 23:46
반응형

 

안녕하세요 :) Zedd입니다.

제가 SwiftUI, iOS 15+ ) onAppear()대신 task() 글을 쓰다가 문득 든 생각들을 정리해보려고 합니다 


# 예제 

let url = "https://zeddios.tistory.com"

let (data, _) = try ✅ await ✅ URLSession.shared.data(from: URL(string: url)!)
let str = String(data: data, encoding: .utf8)!.suffix(50)

DispatchQueue.main.async {
    self.htmls.append(String(str))
}

 SwiftUI, iOS 15+ ) onAppear()대신 task() 글에서 사용한 예제 중 ViewModel 일부 코드인데요. 

 

저는 SwiftUI 사용 + 네트워크 요청이 필요할 때는 무조건 Combine을 사용했는데요. 

그냥 concurrency가 새로 나왔으니..async/await을 사용해서 예제를 작성했습니다. 

그런데 이 예제를 작성하면서 Combine의 필요성이 전혀 느껴지지 않았어요.

(워낙 간단한 예제라서 그런것도 있겠지만요!!)

Combine으로 했다면 publisher에 sink를 달고 sink안에서 viewModel의 htmls를 업데이트 해줬을 것 같은데요.

async/await을 사용하면서 sink, cancellable에 store해주는 코드 이런것들이 전혀 필요하지 않게되었어요. 

 

그래서 문득 'Combine은 어떻게되는거지..?'라는 생각이 들었어요. 

물론 Publisher에 Operator들이 많이 있어서 데이터가공 측면에서(?) Combine이 더 나은 선택일때도 있겠지만요.

그리고 위 예제에서 

DispatchQueue.main.async {
      self.htmls.append(String(str))
}


이렇게 해준것도, 이렇게 해주지 않으면

Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.


이런 warning이 나더라구요. 사실 Combine이었다면 receive(on:)으로 간단히 해결했을텐데 ㅎㅎ

async/await에서는 어떻게 해야할지 잘 모르겠어서..일단은 

DispatchQueue.main.async {
      self.htmls.append(String(str))
}


이렇게 한거였어요!

그런데 그냥 Swift에서 Concurrency의 등장이 Combine에 어떤 영향이 있을지가 궁금해졌습니다. 

 

# Concurrency와 Combine

저처럼 Combine은 어떻게 되는거지? 라고 생각한 사람이 많은 것 같더라구요!

Donny, John Sundell 의 블로그 글을 참고했어요 ㅎ

 

물론 원글을 보면 좋겠지만 두 글을 제 생각 + 요약해보겠습니다.

애플이 말하는 Combine 프레임워크의 정의는 다음과 같습니다.

Customize handling of asynchronous events by combining event-processing operators.


제가 토이/사이드 프로젝트말고 찐 회사 프로젝트에 Combine을 적용해본적은 없지만...!!!!!

제가 토이/사이드 프로젝트에 Combine을 적용한 부분은 100% 네트워킹 + 데이터 밀어넣기를 위해 사용했어요. 

API 호출 부분()
.sink { response in 
  // response를 @Published 변수에 넣어줌.
}

대충 이런식ㅇ...아마도 대부분 그렇게 사용하지 않을까 싶은데요.

Donny과 John Sundell은 이것이 Combine을 이용한 최상의 case라고 생각하지 않는다는 입장입니다.

Combine은 task runner로 의도된적이 없으며
(애초에 Combine은 비동기 프로그래밍을 위한 프레임워크가 아님),

Combine은 상태 변경으로 인한 값의 흐름에 반응하기 위한 것이다. 

 


라고 합니다. (@Published 같은 것들이 Combine으로 구현되어있다..라는 사실을 생각하면 이해가 빠를 것 같아요) 

물론 Combine을  

API 호출 부분()
.sink { response in 
  // response를 @Published 변수에 넣어줌.
}

이런식으로만 사용하는 것이 나쁜것은 아니죠!

저는 Combine의 정의에서 볼 수 있듯이..

Customize handling of asynchronous events by combining event-processing operators.


네트워킹 작업도 asynchronous events의 일종이라고 생각해요. 👀..

하지만 네트워크 호출과 같은 단일 독립 실행형 작업(stand-alone tasks)을 수행하는 데는 Combine보다 

async/await이 더 나은 선택일 수도 있다고 합니다. 

 

물론

private extension SearchViewModel {
    func configureDataPipeline() {
        $query
            .dropFirst()
            .debounce(for: 0.5, scheduler: DispatchQueue.main)
            .removeDuplicates()
            .combineLatest($filter)
            .map { [loader] query, filter in
                loader.loadResults(
                    forQuery: query,
                    filter: filter
                )
                .asResult()
            }
            .switchToLatest()
            .receive(on: DispatchQueue.main)
            .assign(to: &$output)
    }
}

[출처 : https://wwdcbysundell.com/2021/the-future-of-combine/]

이런식으로 데이터 파이프라인을 구성할 때는 Combine이 정말 강력하게 동작할 수 있겠죠.

(위 코드를 AsyncSequence, AsyncStream를 사용하여 작성할 수 있다고 하는데..요 부분은 제가 잘 몰라서 ㅎ)

 

🤔 : 그래서 Combine 공부 안해도 된다는 소리??

https://github.com/apple/swift/pull/39051

 

Add an option to build the concurrency library for back deployment. by DougGregor · Pull Request #39051 · apple/swift

Introduce an additional build product to build-script to build back-deployable concurrency libraries. These libraries would need to be embedded in apps deployed prior to macOS 12/iOS 15 to support ...

github.com

위 PR의 기능이 추가된다면,  Concurrency 관련 코드들이 iOS/macOS하위버전에서도 돌아가는건데..(위 PR은 옵션만 추가한거) 

물론 Combine도 iOS 13이 필요하긴 하다만..ㅎ

🧑‍💻 : SwiftUI의 많은 상태관리 메커니즘이 모두 Combine에 의해 구동된다는 점을 생각하면..

굳이 지금 async/await이 나왔으니 Combine은 안해도 되겠지! 라고 생각할 필요가 있나?! 

 

...

결론은

Combine은 (아직은) 그래도 계속 쓰일거고..자신이 처한 상황에 맞게 뭐든 잘 쓰면 된다~!!!


두 사람의 글을 보고 + 저도 글을 쓰면서 확실히 네트워킹 관련해서는 async/await이 더 적합하구나..라는 생각이 들었어요. 말 그대로 쓰임이 적합하다는 의미...

그냥 얘네는 정의 자체가 비동기 작업을 위한거잖아요? 

근데 Combine은 뭔가 @Published 같은.......정말 반응형을 위한...그런 st..

그리고 제가 지금 너무.. async/await - Combine을 분리해서 생각하는 것도 있는데,

하고자 한다면 두 기능 모두 결합해서 사용할 수 도 있다는것!

John Sundell의 Calling async functions within a Combine pipeline글 참고해보면 좋을 것 같아요.

(근데 저는 두 기능을 결합해서 사용하는게 맞는 접근인지 잘 모르겠어요 ㅎ..)

 

 

제가 쓴 내용중에 틀린 내용이 있거나 async/await & Combine관련해서 의견 같은 것들 있으시면 댓글 달아주세요! 

반응형

'공부' 카테고리의 다른 글

(Static/Dynamic) Library  (12) 2021.09.24
Bundle  (0) 2021.09.19
IDFA 관련 기록  (0) 2021.08.01
WWDC 21 ) Meet TextKit 2  (2) 2021.07.18
MetricKit 톺아보기  (3) 2021.07.11
댓글
  • 프로필사진 oscar 본문(및 링크)에 다 이야기가 되어 있는 거 같긴 한데... 제 생각은, 시퀀셜한 비동기 작업은 async await로 기술하고, 이런 이벤트 스트림을 조합하는 건 reactive(combine, rx 등)를 쓰는 것도 별로 이상할 건 없다고 봅니다. 반대로 reactive의 이벤트 스트림을 조합한 것을 다시 async await로 넘기는 것도 가능하고요.

    reactive나 async await를 처음 소개한 c#의 경우에도 이런 논의가 있었는데, 너무 오래전이라... 글을 못 찾겠네요;;; 10년 좀 넘은 듯.

    저도 ios 개발할 때 말고는 async await를 자주 쓰는지라, ios 13 이상에서 좀 쓸 수 있으면 좋겠네요. ㅋㅋ 해당 pr은 아직 옵션만 추가한거라 그 뒤로 올라오는 pr을 보고 있는 중입니다.
    2021.09.05 10:43
  • 프로필사진 Favicon of https://zeddios.tistory.com BlogIcon Zedd0202 앗 옵션만 추가된거군요 ㅜㅜ.. 글 수정했습니다 ㅎㅎ 하위버전에서도 꼭꼭 사용할 수 있었으면 좋겠네요 🙏🏻.. 2021.09.05 15:52 신고
  • 프로필사진 sqoo 꽤 시간이 지난글이라, 이미 인지하시고 계시겠지만...

    DispatchQueue.main.async {
    self.htmls.append(String(str))
    }

    await MainActor.run {
    self.htmls.append(String(str))
    }

    로 대체할 수 있을거에요 ㅎㅎ
    MainActor.run(_:) 을 통해서 메인스레드에서 실행되도록 해줄 수 있습니다.
    2022.03.17 10:34
댓글쓰기 폼
반응형
Total
4,137,991
Today
752
Yesterday
2,828