본문 바로가기
iOS/Concurrency

AsyncPublisher (구독을 하지 않고 데이터의 변화를 확인)

by 지금갑시다 2022. 12. 26.

AsyncPublisher란, 관찰하고 싶은 객체에 대한 구독을 하지 않고 그 value들을 확인 할 수 있다.

 

class AsyncPublisherDataManager {
    
    @Published var myData: [String] = []
    
    // 2초 마다 myData에 데이터를 넣어주는 함수
    func addData() async {
        myData.append("Apple")
        try? await Task.sleep(nanoseconds: 2_000_000_000)
        myData.append("Banana")
        try? await Task.sleep(nanoseconds: 2_000_000_000)
        myData.append("Orange")
        try? await Task.sleep(nanoseconds: 2_000_000_000)
        myData.append("Water")
        
    }
    
}

class AsyncPublisherViewModel: ObservableObject {
    
    // UI의 변화와 관련있으므로 MainActor를 추가해줌
    @MainActor @Published var dataArray: [String] = []
    let manager = AsyncPublisherDataManager()
    
    init() {
        addSubscribers()
    }
    
    private func addSubscribers() {
        
        Task {
            for await value in manager.$myData.values {
                await MainActor.run(body: {
                    self.dataArray = value
                })
            }
            
            await MainActor.run(body: {
                self.dataArray = ["Two"]
            })
        }
    }
    
    func start() async {
        await manager.addData()
    }
    
}

 AsyncPublisherViewModel의 addSubscribers()에서 manager의 myData값을 바인딩해주어 myData의 value값의 변화가 있다면, 바로 for문에서 기다리고 있던 value로 값이 들어오고, dataArray에 값을 넣어주게 된다.

 

다만 위와 같이 진행되면, 언제 myData의 변화가 끝나는지 모르기 때문에 for문이 끝나지 않고, 프로그램이 계속 머물고 있기에, for문 바깥의 ["Two"]가 실행될 일이 없다.

 

따라서 비동기 작업이 완료 되었다면(myData의 변화가 완료되었다면), break를 활용해 이후의 ["Two"] 작업이 진행 될 수 있게 해준다.

for await value in manager.$myData.values {
    await MainActor.run(body: {
        self.dataArray = value
    })
    break
}
            
await MainActor.run(body: {
    self.dataArray = ["Two"]
})

 

 AsyncPublisher의 경우 구독이 필요없이 위와 같이 바인딩한 값의 values를 직접 관찰해서 값의 변화를 가져 올 수 있지만, 이를 Combine을 사용해 구독하는 방식으로는, 아래와 같이 만들어 줄 수 있다.

class AsyncPublisherViewModel: ObservableObject {
    
    @MainActor @Published var dataArray: [String] = []
    let manager = AsyncPublisherDataManager()
    var cancellables = Set<AnyCancellable>()
    
    init() {
        addSubscribers()
    }
    
    private func addSubscribers() {
        
        Task {            
            for await value in manager.$myData.values {
                await MainActor.run(body: {
                    self.dataArray = value
                })
                break
            }
        }
        
        // Combine을 활용
        manager.$myData
            .receive(on: DispatchQueue.main)
            .sink { dataArray in
                self.dataArray = dataArray
            }
            .store(in: &cancellables)
    }
    
    func start() async {
        await manager.addData()
    }
    
}

 

끗!

 

REF: https://www.youtube.com/watch?v=ePPm2ftSVqw&list=PLwvDm4Vfkdphr2Dl4sY4rS9PLzPdyi8PM&index=13

'iOS > Concurrency' 카테고리의 다른 글

@MainActor, @globalActor 조금 더 이해하기  (1) 2022.12.23
Sendable 프로토콜에 대해!  (0) 2022.12.20
class대신 actor가 요긴한 상황!  (0) 2022.12.19
Async Await  (0) 2022.12.03