diff --git a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift index 5d72f146..961d6302 100644 --- a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift +++ b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift @@ -17,4 +17,9 @@ public extension TargetDependency.Projcet { target: "Data", path: .relativeToRoot("Projects/Data") ) + + static let SWMLogging = TargetDependency.project( + target: "SWMLogging", + path: .relativeToRoot("Projects/SWMLogging") + ) } diff --git a/Projects/App/Sources/DI/DomainAssembly.swift b/Projects/App/Sources/DI/DomainAssembly.swift index e58cfd05..8573214c 100644 --- a/Projects/App/Sources/DI/DomainAssembly.swift +++ b/Projects/App/Sources/DI/DomainAssembly.swift @@ -8,9 +8,12 @@ import Domain import Swinject +import SWMLogging public struct DomainAssembly: Assembly { + let logger: SWMLogger + public func assemble(container: Container) { // ------------------------ Common ------------------------ container.register(ExampleUseCase.self) { resolver in @@ -34,7 +37,7 @@ public struct DomainAssembly: Assembly { // ------------------------ First Tab ------------------------ container.register(LearningHomeUseCase.self) { resolver in let repository = resolver.resolve(ProblemRepository.self)! - return DefaultLearningHomeUseCase(repository: repository) + return DefaultLearningHomeUseCase(repository: repository, logger: logger) } container.register(ProblemDetailUseCase.self) { resolver in let repository = resolver.resolve(ProblemRepository.self)! @@ -46,7 +49,7 @@ public struct DomainAssembly: Assembly { } container.register(SolvingProblemListUseCase.self) { resolver in let repository = resolver.resolve(ProblemRepository.self)! - return DefaultSolvingProblemListUseCase(repository: repository) + return DefaultSolvingProblemListUseCase(repository: repository, logger: logger) } container.register(FavoriteProblemListUseCase.self) { resolver in let repository = resolver.resolve(ProblemRepository.self)! diff --git a/Projects/App/Sources/DI/PresentationAssembly.swift b/Projects/App/Sources/DI/PresentationAssembly.swift index f3ab3490..1763dfef 100644 --- a/Projects/App/Sources/DI/PresentationAssembly.swift +++ b/Projects/App/Sources/DI/PresentationAssembly.swift @@ -9,11 +9,13 @@ import Swinject import Domain import Presentation +import SWMLogging public struct PresentationAssembly: Assembly { let coordinator: Coordinator let toastHelper: ToastHelper + let logger: SWMLogger public func assemble(container: Container) { // ------------------------ Common ------------------------ @@ -79,7 +81,7 @@ public struct PresentationAssembly: Assembly { // SolvingProblemList container.register(SolvingProblemListViewModel.self) { resolver in let useCase = resolver.resolve(SolvingProblemListUseCase.self)! - return SolvingProblemListViewModel(useCase: useCase, coordinator: coordinator, toastHelper: toastHelper) + return SolvingProblemListViewModel(useCase: useCase, coordinator: coordinator, toastHelper: toastHelper, logger: logger) } container.register(SolvingProblemListView.self) { resolver in diff --git a/Projects/App/Sources/LitoApp.swift b/Projects/App/Sources/LitoApp.swift index 549ecc71..8b4b1689 100644 --- a/Projects/App/Sources/LitoApp.swift +++ b/Projects/App/Sources/LitoApp.swift @@ -4,6 +4,7 @@ import Presentation import Domain import Swinject import KakaoSDKCommon +import SWMLogging @main struct LitoApp: App { @@ -19,11 +20,14 @@ struct LitoApp: App { injector = DependencyInjector(container: Container()) toastHelper = ToastHelper() coordinator = Coordinator(.loginScene) - injector.assemble([DomainAssembly(), + // TODO: 우리 서버 API에 맞게 주소 변경 필요 + let logger = SWMLogger(serverUrl: "https://dev.swm-lgtm.com", serverPath: "/v1/log", OSNameAndVersion: "iOS 16", appVersion: "1.0") + injector.assemble([DomainAssembly(logger: logger), DataAssembly(), PresentationAssembly( coordinator: coordinator, - toastHelper: toastHelper + toastHelper: toastHelper, + logger: logger )]) coordinator.injector = injector if KeyChainManager.isPossibleAutoLogin { diff --git a/Projects/Domain/Project.swift b/Projects/Domain/Project.swift index 5ec1e88d..17abe016 100644 --- a/Projects/Domain/Project.swift +++ b/Projects/Domain/Project.swift @@ -14,5 +14,6 @@ let project = Project.makeModule( platform: .iOS, product: .staticFramework, dependencies: [ + .Projcet.SWMLogging ] ) diff --git a/Projects/Domain/Sources/Logging/LearningHomeRecommendedProblemClickedScheme.swift b/Projects/Domain/Sources/Logging/LearningHomeRecommendedProblemClickedScheme.swift new file mode 100644 index 00000000..f8f528f5 --- /dev/null +++ b/Projects/Domain/Sources/Logging/LearningHomeRecommendedProblemClickedScheme.swift @@ -0,0 +1,75 @@ +// +// ProblemClickedScheme.swift +// Domain +// +// Created by 김동락 on 2023/10/11. +// Copyright © 2023 com.lito. All rights reserved. +// + +import Foundation +import SWMLogging + +public struct LearningHomeRecommendedProblemClickedScheme: ClickScheme { + + public var eventLogName = "LearningHomeRecommendedProblemClicked" + public var screenName = "LearningHome" + public var logVersion = 1 + public var logData: [String: AnyEncodable] = [:] + + // 학습목표, 학습퍼센트, 풀던문제 존재 여부, 문제 id, 문제 카테고리, 문제 질문, 찜여부 + public init(learningGoal: Int?, learningPercent: Float?, isSolvingProblemExist: Bool?, problemId: Int?, problemCategory: String?, problemQuestion: String?, problemFavorite: Bool?) { + if let learningGoal = learningGoal { self.logData["learningGoal"] = .int(learningGoal)} + if let learningPercent = learningPercent { self.logData["learningPercent"] = .float(learningPercent)} + if let isSolvingProblemExist = isSolvingProblemExist { self.logData["isSolvingProblemExist"] = .bool(isSolvingProblemExist)} + if let problemId = problemId { self.logData["problemId"] = .int(problemId)} + if let problemCategory = problemCategory { self.logData["problemCategory"] = .string(problemCategory)} + if let problemQuestion = problemQuestion { self.logData["problemQuestion"] = .string(problemQuestion)} + if let problemFavorite = problemFavorite { self.logData["problemFavorite"] = .bool(problemFavorite)} + } + + public class Builder { + var learningGoal: Int? + var learningPercent: Float? + var isSolvingProblemExist: Bool? + var problemId: Int? + var problemCategory: String? + var problemQuestion: String? + var problemFavorite: Bool? + + public init() { } + + public func setLearningGoal(_ learningGoal: Int) -> Builder { + self.learningGoal = learningGoal + return self + } + public func setLearningPercent(_ learningPercent: Float) -> Builder { + self.learningPercent = learningPercent + return self + } + public func setIsSolvingProblemExist(_ isSolvingProblemExist: Bool) -> Builder { + self.isSolvingProblemExist = isSolvingProblemExist + return self + } + public func setProblemId(_ problemId: Int) -> Builder { + self.problemId = problemId + return self + } + public func setProblemCategory(_ problemCategory: String) -> Builder { + self.problemCategory = problemCategory + return self + } + public func setProblemQuestion(_ problemQuestion: String) -> Builder { + self.problemQuestion = problemQuestion + return self + } + public func setProblemFavorite(_ problemFavorite: Bool) -> Builder { + self.problemFavorite = problemFavorite + return self + } + public func build() -> SWMLoggingScheme { + return LearningHomeRecommendedProblemClickedScheme(learningGoal: learningGoal, learningPercent: learningPercent, isSolvingProblemExist: isSolvingProblemExist, problemId: problemId, problemCategory: problemCategory, problemQuestion: problemQuestion, problemFavorite: problemFavorite) + } + + } + +} diff --git a/Projects/Domain/Sources/Logging/LearningHomeSolvingProblemClickedScheme.swift b/Projects/Domain/Sources/Logging/LearningHomeSolvingProblemClickedScheme.swift new file mode 100644 index 00000000..e86bc746 --- /dev/null +++ b/Projects/Domain/Sources/Logging/LearningHomeSolvingProblemClickedScheme.swift @@ -0,0 +1,82 @@ +// +// LearningHomeSolvingProblemClickedScheme.swift +// Domain +// +// Created by 김동락 on 2023/10/11. +// Copyright © 2023 com.lito. All rights reserved. +// + +import Foundation +import SWMLogging + +public struct LearningHomeSolvingProblemClickedScheme: ClickScheme { + + public var eventLogName = "LearningHomeSolvingProblemClicked" + public var screenName = "LearningHome" + public var logVersion = 1 + public var logData: [String: AnyEncodable] = [:] + + // 학습목표, 학습퍼센트, 추천문제 개수, 추천문제 풀이한 개수, 문제 id, 문제 카테고리, 문제 질문, 찜여부 + public init(learningGoal: Int?, learningPercent: Float?, recommendedProblemsCount: Int?, recommendedProblemsSolvedCount: Int?, problemId: Int?, problemCategory: String?, problemQuestion: String?, problemFavorite: Bool?) { + if let learningGoal = learningGoal { self.logData["learningGoal"] = .int(learningGoal)} + if let learningPercent = learningPercent { self.logData["learningPercent"] = .float(learningPercent)} + if let recommendedProblemsCount = recommendedProblemsCount { self.logData["recommendedProblemsCount"] = .int(recommendedProblemsCount)} + if let recommendedProblemsSolvedCount = recommendedProblemsSolvedCount { self.logData["recommendedProblemsSolvedCount"] = .int(recommendedProblemsSolvedCount)} + if let problemId = problemId { self.logData["problemId"] = .int(problemId)} + if let problemCategory = problemCategory { self.logData["problemCategory"] = .string(problemCategory)} + if let problemQuestion = problemQuestion { self.logData["problemQuestion"] = .string(problemQuestion)} + if let problemFavorite = problemFavorite { self.logData["problemFavorite"] = .bool(problemFavorite)} + } + + public class Builder { + var learningGoal: Int? + var learningPercent: Float? + var recommendedProblemsCount: Int? + var recommendedProblemsSolvedCount: Int? + var problemId: Int? + var problemCategory: String? + var problemQuestion: String? + var problemFavorite: Bool? + + public init() { } + + public func setLearningGoal(_ learningGoal: Int) -> Builder { + self.learningGoal = learningGoal + return self + } + public func setLearningPercent(_ learningPercent: Float) -> Builder { + self.learningPercent = learningPercent + return self + } + public func setRecommendedProblemsCount(_ recommendedProblemsCount: Int) -> Builder { + self.recommendedProblemsCount = recommendedProblemsCount + return self + } + public func setrecommendedProblemsSolvedCount(_ recommendedProblemsSolvedCount: Int) -> Builder { + self.recommendedProblemsSolvedCount = recommendedProblemsSolvedCount + return self + } + public func setProblemId(_ problemId: Int) -> Builder { + self.problemId = problemId + return self + } + public func setProblemCategory(_ problemCategory: String) -> Builder { + self.problemCategory = problemCategory + return self + } + public func setProblemQuestion(_ problemQuestion: String) -> Builder { + self.problemQuestion = problemQuestion + return self + } + public func setProblemFavorite(_ problemFavorite: Bool) -> Builder { + self.problemFavorite = problemFavorite + return self + } + public func build() -> SWMLoggingScheme { + return LearningHomeSolvingProblemClickedScheme(learningGoal: learningGoal, learningPercent: learningPercent, recommendedProblemsCount: recommendedProblemsCount, recommendedProblemsSolvedCount: recommendedProblemsSolvedCount, problemId: problemId, problemCategory: problemCategory, problemQuestion: problemQuestion, problemFavorite: problemFavorite) + } + + } + +} + diff --git a/Projects/Domain/Sources/Logging/OrderClickedScheme.swift b/Projects/Domain/Sources/Logging/OrderClickedScheme.swift new file mode 100644 index 00000000..19111bde --- /dev/null +++ b/Projects/Domain/Sources/Logging/OrderClickedScheme.swift @@ -0,0 +1,57 @@ +// +// OrderClickedScheme.swift +// Domain +// +// Created by Lee Myeonghwan on 2023/10/02. +// Copyright © 2023 com.lito. All rights reserved. +// + +import Foundation +import SWMLogging + +public struct OrderClickedScheme: ClickScheme { + + public var eventLogName = "missionClick" + public var screenName = "SolvingProblemListView" + public var logVersion = 1 + public var logData: [String: AnyEncodable] = [:] + + public init(userId: Int?, gender: String?, age: Int?) { + + // logData 만들기 + self.logData["userId"] = .int(userId ?? -1) + self.logData["gender"] = .string(gender ?? "") + self.logData["age"] = .int(age ?? -1) + + } + + public class Builder { + var userId: Int? + var gender: String? + var age: Int? + + public func setGender(gender: String) -> Builder { + self.gender = gender + return self + } + public func setAge(age: Int) -> Builder { + self.age = age + return self + } + public func setUserId(userId: Int) -> Builder { + self.userId = userId + return self + } + + public func userId(id: Int) -> Builder { + self.userId = id + return self + } + + public func build() -> SWMLoggingScheme { + return OrderClickedScheme(userId: userId, gender: gender, age: age) + } + + } + +} diff --git a/Projects/Domain/Sources/UseCase/LearningHomeUseCase.swift b/Projects/Domain/Sources/UseCase/LearningHomeUseCase.swift index 5464f954..34c586a6 100644 --- a/Projects/Domain/Sources/UseCase/LearningHomeUseCase.swift +++ b/Projects/Domain/Sources/UseCase/LearningHomeUseCase.swift @@ -8,19 +8,23 @@ import Combine import Foundation +import SWMLogging public protocol LearningHomeUseCase { func getProfileAndProblems() -> AnyPublisher func toggleProblemFavorite(id: Int) -> AnyPublisher func setProblemGoalCount(problemGoalCount: Int) func getProblemGoalCount() -> Int + func fireLogging(scheme: SWMLogging.SWMLoggingScheme) } public final class DefaultLearningHomeUseCase: LearningHomeUseCase { private let repository: ProblemRepository + private let logger: SWMLogger - public init(repository: ProblemRepository) { + public init(repository: ProblemRepository, logger: SWMLogger) { self.repository = repository + self.logger = logger } public func getProfileAndProblems() -> AnyPublisher { @@ -38,4 +42,11 @@ public final class DefaultLearningHomeUseCase: LearningHomeUseCase { public func getProblemGoalCount() -> Int { repository.getProblemGoalCount() } + public func fireLogging(scheme: SWMLogging.SWMLoggingScheme) { + do { + try logger.shotLogging(scheme, authorization: KeyChainManager.read(key: .accessToken) ?? "") + } catch { + + } + } } diff --git a/Projects/Domain/Sources/UseCase/SovingProblemUseCase.swift b/Projects/Domain/Sources/UseCase/SovingProblemUseCase.swift index 3dc36556..def5e345 100644 --- a/Projects/Domain/Sources/UseCase/SovingProblemUseCase.swift +++ b/Projects/Domain/Sources/UseCase/SovingProblemUseCase.swift @@ -8,6 +8,7 @@ import Combine import Foundation +import SWMLogging public protocol SolvingProblemListUseCase { func toggleProblemFavorite(id: Int) -> AnyPublisher @@ -16,9 +17,11 @@ public protocol SolvingProblemListUseCase { public final class DefaultSolvingProblemListUseCase: SolvingProblemListUseCase { private let repository: ProblemRepository + private let logger: SWMLogger - public init(repository: ProblemRepository) { + public init(repository: ProblemRepository, logger: SWMLogger) { self.repository = repository + self.logger = logger } public func toggleProblemFavorite(id: Int) -> AnyPublisher { diff --git a/Projects/Presentation/Sources/Views/LearningHome/LearningHomeViewModel.swift b/Projects/Presentation/Sources/Views/LearningHome/LearningHomeViewModel.swift index ba1e0ae2..871df2ce 100644 --- a/Projects/Presentation/Sources/Views/LearningHome/LearningHomeViewModel.swift +++ b/Projects/Presentation/Sources/Views/LearningHome/LearningHomeViewModel.swift @@ -68,11 +68,44 @@ public final class LearningHomeViewModel: BaseViewModel { .store(in: cancelBag) goalCount = useCase.getProblemGoalCount() } + + private func recommendedProblemClickedLogging(id: Int, index: Int) { + let scheme = LearningHomeRecommendedProblemClickedScheme.Builder() + .setLearningGoal(goalCount) + .setLearningPercent(learningRate) + .setIsSolvingProblemExist(learningHomeVO?.processProblem != nil) + .setProblemId(id) + .setProblemCategory(recommendProblems[index].subjectName) + .setProblemQuestion(recommendProblems[index].question) + .setProblemFavorite(recommendProblems[index].favorite == .favorite) + .build() + useCase.fireLogging(scheme: scheme) + } + + private func solvingProblemClickedSchemeLogging(id: Int) { + guard let processProblem = processProblem else { return } + let scheme = LearningHomeSolvingProblemClickedScheme.Builder() + .setLearningGoal(goalCount) + .setLearningPercent(learningRate) + .setRecommendedProblemsCount(recommendProblems.count) + .setrecommendedProblemsSolvedCount(recommendProblems.filter{$0.problemStatus == .solved}.count) + .setProblemId(id) + .setProblemCategory(processProblem.subjectName) + .setProblemQuestion(processProblem.question) + .setProblemFavorite(processProblem.favorite == .favorite) + .build() + useCase.fireLogging(scheme: scheme) + } } extension LearningHomeViewModel: ProblemCellHandling { // 해당 문제 풀이 화면으로 이동하기 public func onProblemCellClicked(id: Int) { coordinator.push(.problemDetailScene(id: id)) + if let index = self.recommendProblems.firstIndex(where: { $0.problemId == id}) { + recommendedProblemClickedLogging(id: id, index: index) + } else { + solvingProblemClickedSchemeLogging(id: id) + } } // 찜하기 or 찜해제하기 diff --git a/Projects/Presentation/Sources/Views/SolvingProblemList/SolvingProblemListViewModel.swift b/Projects/Presentation/Sources/Views/SolvingProblemList/SolvingProblemListViewModel.swift index c026a25e..8371aae1 100644 --- a/Projects/Presentation/Sources/Views/SolvingProblemList/SolvingProblemListViewModel.swift +++ b/Projects/Presentation/Sources/Views/SolvingProblemList/SolvingProblemListViewModel.swift @@ -9,6 +9,7 @@ import SwiftUI import Domain import Combine +import SWMLogging public final class SolvingProblemListViewModel: BaseViewModel { private let useCase: SolvingProblemListUseCase @@ -17,9 +18,11 @@ public final class SolvingProblemListViewModel: BaseViewModel { private var problemTotalSize: Int? @Published private(set) var isLoading: Bool = false @Published var problemCellList: [SolvingProblemCellVO] = [] + private let logger: SWMLogger - public init(useCase: SolvingProblemListUseCase, coordinator: CoordinatorProtocol, toastHelper: ToastHelperProtocol) { + public init(useCase: SolvingProblemListUseCase, coordinator: CoordinatorProtocol, toastHelper: ToastHelperProtocol, logger: SWMLogger) { self.useCase = useCase + self.logger = logger super.init(coordinator: coordinator, toastHelper: toastHelper) } diff --git a/Projects/SWMLogging/Project.swift b/Projects/SWMLogging/Project.swift new file mode 100644 index 00000000..879b48c5 --- /dev/null +++ b/Projects/SWMLogging/Project.swift @@ -0,0 +1,19 @@ +// +// Project.swift +// ProjectDescriptionHelpers +// +// Created by Lee Myeonghwan on 2023/10/02. +// + +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: "SWMLogging", + product: .staticLibrary, + dependencies: [ + .SPM.Moya, + .SPM.CombineMoya + ] +) diff --git a/Projects/SWMLogging/Sources/API.swift b/Projects/SWMLogging/Sources/API.swift new file mode 100644 index 00000000..2948c0cc --- /dev/null +++ b/Projects/SWMLogging/Sources/API.swift @@ -0,0 +1,51 @@ +// +// API.swift +// SWMLogging +// +// Created by Lee Myeonghwan on 2023/10/02. +// Copyright © 2023 com.lito. All rights reserved. +// + +import Foundation +import Moya + +struct LoggingAPI: TargetType { + private let serverUrl: String + private let serverPath: String + private var authorization: String? + private var schemeData: Data = Data() + + public init(serverUrl: String, serverPath: String) { + self.serverUrl = serverUrl + self.serverPath = serverPath + } + + public mutating func setScheme(_ schemeData: Data) { + self.schemeData = schemeData + } + + public mutating func setAuthorization(_ authorization: String) { + self.authorization = authorization + } + + var baseURL: URL { + return URL(string: serverUrl)! + } + + var path: String { + return serverPath + } + + var method: Moya.Method { + return .post + } + + var task: Moya.Task { + return .requestData(schemeData) + } + + var headers: [String: String]? { + return ["Content-Type": "application/json", "Authorization": "Bearer \(authorization ?? "")"] + } + +} diff --git a/Projects/SWMLogging/Sources/AnyEncodable.swift b/Projects/SWMLogging/Sources/AnyEncodable.swift new file mode 100644 index 00000000..cde4d3bc --- /dev/null +++ b/Projects/SWMLogging/Sources/AnyEncodable.swift @@ -0,0 +1,28 @@ +// +// AnyEncodable.swift +// SWMLogging +// +// Created by 김동락 on 2023/10/09. +// Copyright © 2023 com.lito. All rights reserved. +// + +import Foundation + +// 필요한 encodable 타입 추가 가능 +public enum AnyEncodable: Encodable { + case bool(Bool), int(Int), string(String), float(Float) + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .bool(let value): + try container.encode(value) + case .int(let value): + try container.encode(value) + case .string(let value): + try container.encode(value) + case .float(let value): + try container.encode(value) + } + } +} diff --git a/Projects/SWMLogging/Sources/Logger.swift b/Projects/SWMLogging/Sources/Logger.swift new file mode 100644 index 00000000..cd3f0600 --- /dev/null +++ b/Projects/SWMLogging/Sources/Logger.swift @@ -0,0 +1,87 @@ +// +// Source.swift +// ProjectDescriptionHelpers +// +// Created by Lee Myeonghwan on 2023/10/02. +// + +import Foundation +import Combine +import Moya +import CombineMoya + +public protocol SWMLoggingScheme: Encodable { + var eventLogName: String { get set } + var screenName: String { get set } + var logVersion: Int { get set } + var logData: [String: AnyEncodable] { get set } +} + +extension SWMLoggingScheme { + func makeJson() throws -> Data { + return try SWMLogger.encoder.encode(self) + } +} + +public protocol ClickScheme: SWMLoggingScheme { + // Business Static Paramter +} + +public protocol ExposureScheme: SWMLoggingScheme { + // Business Static Paramter +} + +public class SWMLogger { + + static let encoder = JSONEncoder() + private let moyaProvider = MoyaProvider() + private let throttleLimit = 1.0 + + private let sessionId = UUID() + private let appVersion: String + private let OSNameAndVersion: String + + private var cancelBag = Set() + private var loggingAPI: LoggingAPI + private var latestLogName = "" + private var latestShotTime = Date() + + public init(serverUrl: String, serverPath: String, OSNameAndVersion: String, appVersion: String) { + self.OSNameAndVersion = OSNameAndVersion + self.appVersion = appVersion + loggingAPI = LoggingAPI(serverUrl: serverUrl, serverPath: serverPath) + } + + public func shotLogging(_ scheme: SWMLoggingScheme, authorization: String) throws { + do { + if throttleCondition(scheme.eventLogName) { + latestShotTime = Date() + latestLogName = scheme.eventLogName + loggingAPI.setScheme(try scheme.makeJson()) + loggingAPI.setAuthorization(authorization) + sendRequest(loggingAPI) + } + } catch { + throw error + } + } + + private func throttleCondition(_ eventLogName: String) -> Bool { + (eventLogName == latestLogName && Date().timeIntervalSince(latestShotTime) > throttleLimit) || + (eventLogName != latestLogName) + } + + private func sendRequest(_ api: LoggingAPI) { + moyaProvider.requestPublisher(loggingAPI) + .sink(receiveCompletion: { result in + switch result { + case let .failure(error): + print(error) + default: break + } + }, receiveValue: { _ in + print("suceess") + }) + .store(in: &cancelBag) + } +} diff --git a/Projects/SWMLogging/Tests/SWMLoggingTests.swift b/Projects/SWMLogging/Tests/SWMLoggingTests.swift new file mode 100644 index 00000000..350c82fa --- /dev/null +++ b/Projects/SWMLogging/Tests/SWMLoggingTests.swift @@ -0,0 +1,2 @@ + +import Foundation