swift DispatchGroup 과 DispatchSemaphore 실무에 사용+ 실험
ios 앱 개발할때 DispatchGroup과 semaphore 를 사용할 기회가 많이 없는데, 이번에 둘다 사용해 보게되었다.
먼저, 개발 중인 앱에는 결제 프로세스가 있다. 첫번째 요구사항 정의는 모든 결제는 한번에 하나만 할 수 있다 였다. 즉, 장바구니 개념이 없고, 결제 한번에 하나의 품목만 결제를 할 수 있다.
결제 api 는 1. 결제 api 호출 , 2. 결제 확인 api 호출 순으로 총 2번 순서대로 호출해야했다. 호출이 완료되면 다음 화면을 push 해주면 되었다.
그래서 아래와 같이 1.결제처리 함수, 2. 결제확인함수를 만들어 주었다.
import UIKit
import Foundation
/// [1] 결제 처리 api
func doPay(_ data: Int, delay: Double, completionHandler: @escaping (String)->()) {
print("doPay - 진입 :\(data) - delay:\(delay)")
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
print("doPay - 처리[**] :\(data) ")
completionHandler("\(data)")
}
print("doPay - 종료 ")
}
/// [2] 결제 처리 확인응답 api
func payResponse(_ data: Int, delay: Double, completionHandler: @escaping (String)->()) {
print("payResponse - 진입 :\(data) - delay:\(delay)")
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
print("payResponse - 처리[***] :\(data)\n")
completionHandler("\(data)")
}
print("payResponse - 종료 :\(data)")
}
그리고 1.결제 api 호출 후 2.결제 확인 api 를 호출해 주어야 해서 1.결제 api 가 응답되고 호출 되는 콜백 함수 안에서 2. 결제 확인 api 를 호출 해 주었다. 그리고 DispatchGroup을 사용해서 두 api 가 모두 호출되면 notify 함수를 이용해서 다음 화면을 push 해주었다.
let group = DispatchGroup()
var text = ""
group.enter()
/// [1] 결제처리
doPay(0, delay: Double.random(in: 0.1...3)) {_ in
text += "💶pay0- "
///[2] 확인처리
payResponse(0, delay: Double.random(in: 0.1...3)) {_ in
text += "📡res0- "
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
print("📲 끝- 다음화면으로 : \(text)")
}
결과는 아래와 같이 순서대로 작동이 된다.
doPay - 진입 :0 - delay:0.4925298720140009
doPay - 종료
doPay - 처리[**] :0
payResponse - 진입 :0 - delay:0.3396354870342088
payResponse - 종료 :0
payResponse - 처리[***] :0
📲 끝- 다음화면으로 : 💶pay0- 📡res0-
🧨 문제는 얼마지나지 않아 발생했다. 결제에 장바구니 기능이 추가되었다. 사용자는 1건이 아니라 여러건 결제를 할 수 있다. 나는 단순하게 for 문을 사용하면 되지 않나라고 너무나 너무나 황당한 생각을 해버리고 만다. 일단 실험을 해보았다. for 문을 사용해서 결제 3건, 즉 3번을 돌렸다.
let group = DispatchGroup()
var text = ""
// 결제 3건
for i in 0..<3 {
group.enter()
/// [1] 결제처리
doPay(i, delay: Double.random(in: 0.1...3)) {_ in
text += "💶pay\(i)- "
///[2] 확인처리
payResponse(i, delay: Double.random(in: 0.1...3)) {_ in
text += "📡res\(i)- "
group.leave()
}
}
}
group.notify(queue: DispatchQueue.main) {
print("📲 끝- 다음화면으로 : \(text)")
}
결과는 아래와 같이 💶pay n- 📡res n .... 순으로 출력되지 않았다. 그리고 💶pay api 만 먼저 3번 호출되었다.!
당연한 결과였다. for문이 엄청 빠르게 , 1번 결제 api를 호출하고(2-3초걸림).... ... 호출한 api 응답을 안기다리고... 또 1번결제 api를 호출해버리고(2-3초걸림)... 즉 비동기 방식으로 연속적으로 처리되기 때문이었다. 이건 요구사항에 어긋난다.
doPay - 진입 :0 - delay:2.862823393883068
doPay - 종료
doPay - 진입 :1 - delay:2.7782999587612998
doPay - 종료
doPay - 진입 :2 - delay:1.5717905658122133
doPay - 종료
doPay - 처리[**] :2
payResponse - 진입 :2 - delay:2.2812485210527207
payResponse - 종료 :2
doPay - 처리[**] :1
payResponse - 진입 :1 - delay:0.5266285057299884
payResponse - 종료 :1
doPay - 처리[**] :0
payResponse - 진입 :0 - delay:1.6996043597976123
payResponse - 종료 :0
payResponse - 처리[***] :1
payResponse - 처리[***] :2
payResponse - 처리[***] :0
📲 끝- 다음화면으로 : 💶pay2- 💶pay1- 💶pay0- 📡res1- 📡res2- 📡res0-
요구사항은 결제가 한건이든 두건이던 여러건이든 1.결제 api 가 호출되고, 그 후에 2.결제확인 api 가 순서대로 호출되어야 한다. 순서대로! 이 순서대로를 만족시킬 수 있는 녀석은 뭘까? 바로 세마포어다. 세마포어는 첫번재 for문 안의 내용이 다 끝날때 까지 기다린다. 그 후 두번째 for 문을 실행한다. 그럼 세마포어는 어디다 넣어야 할까? for 문 끝나는 지점에서 wait를 시키고, 2.결제 확인 api 가 종료되는 지점에서 signal을 보내면 된다.
var text = ""
let semaphore = DispatchSemaphore(value: 0)
let group = DispatchGroup()
/// semaphore 처리는 async 안에서
DispatchQueue.global().async {
for i in 0..<3 {
group.enter()
/// [1] 결제처리
doPay(i, delay: Double.random(in: 0.1...3)) {_ in
text += "💶pay\(i) - "
///[2] 확인처리
payResponse(i, delay: Double.random(in: 0.1...3)) {_ in
text += "📡res\(i) - "
/// [!!!] 아래서 기다리고 있는 semaphore 에게 종료되었다고 알림 [!!!]
semaphore.signal()
group.leave()
}
}
///[!!!]다음 for문 넘어가서 api call 하기전까지 이전에 payResponse 가 응답되도록 기다림[!!!]
semaphore.wait()
}
group.notify(queue: DispatchQueue.main) {
print("📲 끝- 다음화면으로 : \(text)")
}
}
위의 코드를 실행시키면 어떤 결과가 나올까? 의도했던 대로 순서대로 출력이 되었다.
doPay - 진입 :0 - delay:1.3257324019562897
doPay - 종료
doPay - 처리[**] :0
payResponse - 진입 :0 - delay:0.3535576416484567
payResponse - 종료 :0
payResponse - 처리[***] :0
doPay - 진입 :1 - delay:2.9804089428142873
doPay - 종료
doPay - 처리[**] :1
payResponse - 진입 :1 - delay:2.1649215839054174
payResponse - 종료 :1
payResponse - 처리[***] :1
doPay - 진입 :2 - delay:0.24332565453172345
doPay - 종료
doPay - 처리[**] :2
payResponse - 진입 :2 - delay:0.10224742295913322
payResponse - 종료 :2
payResponse - 처리[***] :2
📲 끝- 다음화면으로 : 💶pay0 - 📡res0 - 💶pay1 - 📡res1 - 💶pay2 - 📡res2 -
DispatchGroup과 DispatchSemaphore 에 대한 개념과 코드는 아래 포스트에 잘 설명해 놓았다.
swift DispatchGroup 과 DispatchSemaphore - 1
swift DispatchGroup 과 DispatchSemaphore - 2
'아이폰 개발 > Swift' 카테고리의 다른 글
swift 메소드 정리 (0) | 2022.10.31 |
---|---|
swift custom loading view, custom indicator (0) | 2021.10.25 |
swift AVAudioPlayer 로 음악앱 만들기 (0) | 2021.10.10 |
swift network conection 체크 - 인터넷 연결 체크 (0) | 2021.10.06 |
swift stackview 안에 view 동적으로 생성하기 (0) | 2021.10.05 |