Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CAT-104] UserDefaultsClient 모듈 추가 #16

Merged
merged 6 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified DependencyGraph/mohanyang_dev_graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified DependencyGraph/mohanyang_prod_graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ public enum Core: String, Modulable {
case KeychainClient
case UserNotificationClient
case DatabaseClient
case UserDefaultsClient
}
6 changes: 6 additions & 0 deletions Projects/Core/Core/Sources/Exports.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@
@_exported import APIClientInterface
@_exported import KeychainClient
@_exported import KeychainClientInterface
@_exported import UserDefaultsClient
@_exported import UserDefaultsClientInterface
@_exported import UserNotificationClient
@_exported import UserNotificationClientInterface
@_exported import DatabaseClient
@_exported import DatabaseClientInterface
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
/// Use create<T: Persistable>(object:).
public var create: @Sendable (any Persistable) async throws -> Void
/// Use read<T: Persistable>(_ type:).
public var read: @Sendable (any Persistable.Type) async throws -> [Object]
public var read: @Sendable (Object.Type) async throws -> [Object]
/// Use read<T: Persistable>(_ type:, predicateFormat:, args:).
public var readWithFilter: @Sendable (Object.Type, String, Any) async throws -> [Object]
/// Use update<T: Persistable>(_ object:)
Expand All @@ -36,7 +36,7 @@
}

public func read<T: Persistable>(_ type: T.Type) async throws -> [T] {
let results = try await self.read(type)
let results = try await self.read(type.ManagedObject)
return await Self.realmActor?.convertToPersistable(type: type, objects: results) ?? []
}

Expand Down Expand Up @@ -67,14 +67,14 @@
public static var realmActor: RealmActor?

public actor RealmActor {
public var realm: Realm!

Check warning on line 70 in Projects/Core/DatabaseClient/Interface/DatabaseClientInterface.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Implicitly unwrapped optionals should be avoided when possible (implicitly_unwrapped_optional)

public init(configuration: Realm.Configuration) async throws {
realm = try await Realm(configuration: configuration, actor: self)
}

func convertToPersistable<T: Persistable>(type: T.Type, objects: [Object]) -> [T] {
return objects.compactMap { type.init(managedObject: $0 as! T.ManagedObject) }

Check warning on line 77 in Projects/Core/DatabaseClient/Interface/DatabaseClientInterface.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Force casts should be avoided (force_cast)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Projects/Core/DatabaseClient/Sources/DatabaseClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
},
read: { type in
if let realmActor {
let results = await realmActor.read(type.ManagedObject as! Object.Type)
let results = await realmActor.read(type as! Object.Type)

Check warning on line 34 in Projects/Core/DatabaseClient/Sources/DatabaseClient.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Force casts should be avoided (force_cast)
return results
} else {
throw(NSError(domain: "Realm is not initialized", code: 0))
Expand Down
3 changes: 1 addition & 2 deletions Projects/Core/KeychainClient/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ let project: Project = .makeTMABasedProject(
targets: [
.sources,
.interface,
.tests,
.testing
.tests
],
dependencies: [
.interface: [
Expand Down
30 changes: 0 additions & 30 deletions Projects/Core/KeychainClient/Testing/KeychainClientTesting.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// UserDefaultsClientInterface.swift
// UserDefaultsClient
//
// Created by devMinseok on 8/4/24.
//

import Foundation

import Dependencies
import DependenciesMacros

@DependencyClient
public struct UserDefaultsClient {
// MARK: - Create, Update

public var setString: @Sendable (String?, _ key: String) async -> Void
public var setBool: @Sendable (Bool, _ key: String) async -> Void
public var setData: @Sendable (Data?, _ key: String) async -> Void
public var setDouble: @Sendable (Double, _ key: String) async -> Void
public var setInteger: @Sendable (Int, _ key: String) async -> Void

// MARK: - Read

public var stringForKey: @Sendable (String) -> String?
public var boolForKey: @Sendable (String) -> Bool = { _ in false }
public var dataForKey: @Sendable (String) -> Data?
public var doubleForKey: @Sendable (String) -> Double = { _ in 0.0 }
public var integerForKey: @Sendable (String) -> Int = { _ in 0 }


// MARK: - Delete

public var remove: @Sendable (String) async -> Void

// MARK: - Reset

public var removePersistentDomain: @Sendable (_ bundleId: String) -> Void
}

extension UserDefaultsClient: TestDependencyKey {
public static var testValue = Self()
public static var previewValue = Self()
}
28 changes: 28 additions & 0 deletions Projects/Core/UserDefaultsClient/Project.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// UserDefaultsClientTesting.swift
// UserDefaultsClientManifests
//
// Created by devMinseok on 8/4/24.
//

import ProjectDescription
import ProjectDescriptionHelpers

@_spi(Core)
@_spi(Shared)
import DependencyPlugin

let project: Project = .makeTMABasedProject(
module: Core.UserDefaultsClient,
scripts: [],
targets: [
.sources,
.interface,
.tests
],
dependencies: [
.interface: [
.dependency(rootModule: Shared.self)
]
]
)
34 changes: 34 additions & 0 deletions Projects/Core/UserDefaultsClient/Sources/UserDefaultsClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// UserDefaultsClient.swift
// UserDefaultsClient
//
// Created by devMinseok on 8/4/24.
//

import Foundation

import Dependencies

import UserDefaultsClientInterface

extension UserDefaultsClient: DependencyKey {
public static let liveValue: UserDefaultsClient = .live()

public static func live() -> UserDefaultsClient {
let userDefaults = { UserDefaults.standard }
return .init(
setString: { userDefaults().set($0, forKey: $1) },
setBool: { userDefaults().set($0, forKey: $1) },
setData: { userDefaults().set($0, forKey: $1) },
setDouble: { userDefaults().set($0, forKey: $1) },
setInteger: { userDefaults().set($0, forKey: $1) },
stringForKey: { userDefaults().string(forKey: $0) },
boolForKey: { userDefaults().bool(forKey: $0) },
dataForKey: { userDefaults().data(forKey: $0) },
doubleForKey: { userDefaults().double(forKey: $0) },
integerForKey: { userDefaults().integer(forKey: $0) },
remove: { userDefaults().removeObject(forKey: $0) },
removePersistentDomain: { userDefaults().removePersistentDomain(forName: $0) }
)
}
}
129 changes: 129 additions & 0 deletions Projects/Core/UserDefaultsClient/Tests/UserDefaultsClientTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//
// UserDefaultsClientTests.swift
// UserDefaultsClient
//
// Created by devMinseok on 8/4/24.
//

import XCTest

import UserDefaultsClient
import UserDefaultsClientInterface

import Dependencies

final class UserDefaultsClientTests: XCTestCase {
@Dependency(UserDefaultsClient.self) var userDefaultsClient

override func tearDown() {
withDependencies {
$0[UserDefaultsClient.self] = UserDefaultsClient.live()
} operation: {
userDefaultsClient.removePersistentDomain(bundleId: Bundle.main.bundleIdentifier ?? "")
}
super.tearDown()
}

func testSetString() async {
await withDependencies {
$0[UserDefaultsClient.self] = UserDefaultsClient.live()
} operation: {
// given
let testKey: String = "TestKey"
let testValue: String = "TestString"

// when
await userDefaultsClient.setString(testValue, key: testKey)

// then
let result = userDefaultsClient.stringForKey(testKey)
XCTAssertEqual(result, testValue)
}
}

func testSetBool() async {
await withDependencies {
$0[UserDefaultsClient.self] = UserDefaultsClient.live()
} operation: {
// given
let testKey: String = "TestKeyBool"
let testValue: Bool = true

// when
await userDefaultsClient.setBool(testValue, testKey)

// then
let result = userDefaultsClient.boolForKey(testKey)
XCTAssertEqual(result, testValue)
}
}

func testSetData() async {
await withDependencies {
$0[UserDefaultsClient.self] = UserDefaultsClient.live()
} operation: {
// given
let testKey: String = "TestKeyData"
let testValue: Data = "TestData".data(using: .utf8)!

Check warning on line 67 in Projects/Core/UserDefaultsClient/Tests/UserDefaultsClientTests.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Force unwrapping should be avoided (force_unwrapping)

// when
await userDefaultsClient.setData(testValue, testKey)

// then
let result = userDefaultsClient.dataForKey(testKey)
XCTAssertEqual(result, testValue)
}
}

func testSetDouble() async {
await withDependencies {
$0[UserDefaultsClient.self] = UserDefaultsClient.live()
} operation: {
// given
let testKey: String = "TestKeyDouble"
let testValue: Double = 123.456

// when
await userDefaultsClient.setDouble(testValue, testKey)

// then
let result = userDefaultsClient.doubleForKey(testKey)
XCTAssertEqual(result, testValue)
}
}

func testSetInteger() async {
await withDependencies {
$0[UserDefaultsClient.self] = UserDefaultsClient.live()
} operation: {
// given
let testKey: String = "TestKeyInteger"
let testValue: Int = 123

// when
await userDefaultsClient.setInteger(testValue, testKey)

// then
let result = userDefaultsClient.integerForKey(testKey)
XCTAssertEqual(result, testValue)
}
}

func testRemove() async {
await withDependencies {
$0[UserDefaultsClient.self] = UserDefaultsClient.live()
} operation: {
// given
let testKey: String = "TestKeyRemove"
let testValue: String = "TestString"
await userDefaultsClient.setString(testValue, testKey)

// when
await userDefaultsClient.remove(testKey)

// then
let result = userDefaultsClient.stringForKey(testKey)
XCTAssertNil(result)
}
}
}
12 changes: 10 additions & 2 deletions Projects/Core/UserNotificationClient/Sources/Implementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ extension UserNotificationClient {
withCompletionHandler completionHandler: @escaping () -> Void
) {
self.continuation.yield(
.didReceiveResponse(.init(rawValue: response)) { completionHandler() }
.didReceiveResponse(.init(rawValue: response)) {
Task { @MainActor in
completionHandler()
}
}
)
}

Expand All @@ -96,7 +100,11 @@ extension UserNotificationClient {
@escaping (UNNotificationPresentationOptions) -> Void
) {
self.continuation.yield(
.willPresentNotification(.init(rawValue: notification)) { completionHandler($0) }
.willPresentNotification(.init(rawValue: notification)) { options in
Task { @MainActor in
completionHandler(options)
}
}
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Projects/Domain/Domain/Sources/Exports.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
//

@_exported import AppService
@_exported import AppServiceInterface
@_exported import PushService
2 changes: 1 addition & 1 deletion Projects/Feature/Feature/Sources/AppCore.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// AppCore.swift
// AppServiceInterface
// Feature
//
// Created by devMinseok on 7/22/24.
// Copyright © 2024 PomoNyang. All rights reserved.
Expand Down
2 changes: 1 addition & 1 deletion Projects/Feature/Feature/Sources/AppDelegateCore.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// AppDelegateCore.swift
// AppServiceInterface
// Feature
//
// Created by devMinseok on 7/22/24.
// Copyright © 2024 PomoNyang. All rights reserved.
Expand Down
2 changes: 1 addition & 1 deletion Projects/Feature/Feature/Sources/AppView.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// AppServcieView.swift
// AppServiceInterface
// Feature
//
// Created by devMinseok on 7/22/24.
// Copyright © 2024 PomoNyang. All rights reserved.
Expand Down
Loading