From 052cc4cc5237dd90e0db10ab628db652afbf54ef Mon Sep 17 00:00:00 2001 From: Jihyun247 <59338503+Jihyun247@users.noreply.github.com> Date: Sat, 28 Dec 2024 01:14:59 +0900 Subject: [PATCH] =?UTF-8?q?[SCRUM-51]=20StreamListener=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20(#66)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interface/AnyStreamContinuation.swift | 16 ---------- .../Interface/StreamContinuation.swift | 25 ++++++++++++++++ .../Interface/StreamListenerInterface.swift | 18 ++++++++--- .../StreamListener/Interface/Streams.swift | 21 ------------- .../Sources/StreamListener.swift | 30 +++++++++++-------- .../Sources/ServerStateStream.swift | 22 ++++++++++++++ .../Sources/NamingCat/NamingCatCore.swift | 1 + 7 files changed, 80 insertions(+), 53 deletions(-) delete mode 100644 Projects/Core/StreamListener/Interface/AnyStreamContinuation.swift create mode 100644 Projects/Core/StreamListener/Interface/StreamContinuation.swift delete mode 100644 Projects/Core/StreamListener/Interface/Streams.swift create mode 100644 Projects/Domain/AppService/Sources/ServerStateStream.swift diff --git a/Projects/Core/StreamListener/Interface/AnyStreamContinuation.swift b/Projects/Core/StreamListener/Interface/AnyStreamContinuation.swift deleted file mode 100644 index 5fb3814..0000000 --- a/Projects/Core/StreamListener/Interface/AnyStreamContinuation.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Foundation - -public class AnyStreamContinuation { - private let _yield: (Any) -> Void - - public init(_ continuation: AsyncStream.Continuation) { - self._yield = { value in - guard let value = value as? T else { return } - continuation.yield(value) - } - } - - public func yield(_ value: Any) { - _yield(value) - } -} diff --git a/Projects/Core/StreamListener/Interface/StreamContinuation.swift b/Projects/Core/StreamListener/Interface/StreamContinuation.swift new file mode 100644 index 0000000..c010b8a --- /dev/null +++ b/Projects/Core/StreamListener/Interface/StreamContinuation.swift @@ -0,0 +1,25 @@ +import Foundation + +public class StreamContinuation { + private let _yield: (any StreamType) -> Void + private let _finish: () -> Void + + public init(_ continuation: AsyncStream.Continuation) { + self._yield = { value in + guard let value = value as? T else { return } + continuation.yield(value) + } + + self._finish = { + continuation.finish() + } + } + + public func yield(_ value: any StreamType) { + _yield(value) + } + + public func finish() { + _finish() + } +} diff --git a/Projects/Core/StreamListener/Interface/StreamListenerInterface.swift b/Projects/Core/StreamListener/Interface/StreamListenerInterface.swift index 3b8a473..16f0b82 100644 --- a/Projects/Core/StreamListener/Interface/StreamListenerInterface.swift +++ b/Projects/Core/StreamListener/Interface/StreamListenerInterface.swift @@ -10,9 +10,19 @@ import Foundation import Dependencies import DependenciesMacros +// MARK: - Stream releated Protocol + +public typealias StreamKey = String + +public protocol StreamType: Hashable { + static var key: StreamKey { get } +} + +// MARK: - StreamListener + public protocol StreamListenerProtocol { - func send(_ state: T) async - func receive(_ type: T.Type) -> AsyncStream + func send(_ state: T) async + func receive(_ type: T.Type) -> AsyncStream } @DependencyClient @@ -30,8 +40,8 @@ extension StreamListener: TestDependencyKey { } private struct StreamListenerTestImpl: StreamListenerProtocol { - func send(_ state: T) async {} - func receive(_ type: T.Type) -> AsyncStream { .never } + func send(_ state: T) async {} + func receive(_ type: T.Type) -> AsyncStream { .never } } /* diff --git a/Projects/Core/StreamListener/Interface/Streams.swift b/Projects/Core/StreamListener/Interface/Streams.swift deleted file mode 100644 index 8e8be2f..0000000 --- a/Projects/Core/StreamListener/Interface/Streams.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Foundation - -// MARK: StreamType - -public protocol StreamTypeProtocol: Hashable { - static var key: StreamType { get } -} - -public enum StreamType: Hashable { - case serverState - case retry - case toast -} - -public enum ServerState: StreamTypeProtocol { - public static var key: StreamType { .serverState } - case requestStarted - case requestCompleted - case errorOccured - case networkDisabled -} diff --git a/Projects/Core/StreamListener/Sources/StreamListener.swift b/Projects/Core/StreamListener/Sources/StreamListener.swift index 6eaf6f4..7ccd2b1 100644 --- a/Projects/Core/StreamListener/Sources/StreamListener.swift +++ b/Projects/Core/StreamListener/Sources/StreamListener.swift @@ -22,11 +22,11 @@ extension StreamListener: DependencyKey { final class StreamListenerImpl: StreamListenerProtocol { private let actor = StreamActor() - func send(_ state: T) async { - await actor.yield(type: T.key, value: T.self) + func send(_ state: T) async { + await actor.yield(key: T.key, value: state) } - func receive(_ type: T.Type) -> AsyncStream { + func receive(_ type: T.Type) -> AsyncStream { let (stream, continuation) = AsyncStream.makeStream() Task { await actor.register(key: T.key, continuation: continuation) @@ -36,18 +36,24 @@ final class StreamListenerImpl: StreamListenerProtocol { } private actor StreamActor { - private var streams: [StreamType: AnyStreamContinuation] = [:] - - func register(key: StreamType, continuation: AsyncStream.Continuation) { - streams[key] = AnyStreamContinuation(continuation) + private var streams: [StreamKey: [StreamContinuation]] = [:] + + func register(key: StreamKey, continuation: AsyncStream.Continuation) { + let newContinuation = StreamContinuation(continuation) + if streams[key] == nil { + streams[key] = [newContinuation] + } else { + streams[key]?.append(newContinuation) + } } - func yield(type: StreamType, value: T.Type) { - guard let continuation = streams[type] else { return } - continuation.yield(value) + func yield(key: StreamKey, value: T) { + guard let continuations = streams[key] else { return } + continuations.forEach { $0.yield(value) } } - func remove(type: StreamType) { - streams[type] = nil + func remove(key: StreamKey) { + streams[key]?.forEach { $0.finish() } + streams[key] = nil } } diff --git a/Projects/Domain/AppService/Sources/ServerStateStream.swift b/Projects/Domain/AppService/Sources/ServerStateStream.swift new file mode 100644 index 0000000..97a9742 --- /dev/null +++ b/Projects/Domain/AppService/Sources/ServerStateStream.swift @@ -0,0 +1,22 @@ +// +// ServerStateStream.swift +// AppService +// +// Created by 김지현 on 12/25/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import StreamListenerInterface + +public enum ServerState: StreamType { + public static var key: StreamKey { "serverState" } + case requestStarted + case requestCompleted + case errorOccured + case networkDisabled +} + +public struct RetryState: StreamType { + public static var key: StreamKey { "retryState" } + public var retry: Int? +} diff --git a/Projects/Feature/CatFeature/Sources/NamingCat/NamingCatCore.swift b/Projects/Feature/CatFeature/Sources/NamingCat/NamingCatCore.swift index cb52bb6..28bff8b 100644 --- a/Projects/Feature/CatFeature/Sources/NamingCat/NamingCatCore.swift +++ b/Projects/Feature/CatFeature/Sources/NamingCat/NamingCatCore.swift @@ -13,6 +13,7 @@ import DesignSystem import UserServiceInterface import DatabaseClientInterface import StreamListenerInterface +import AppService import ComposableArchitecture import RiveRuntime