├── .github ├── FUNDING.yml └── workflows │ ├── docc.yml │ ├── macOS.yml │ ├── ubuntu.yml │ └── windows.yml ├── .gitignore ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── AppState │ ├── Application │ ├── Application+internal.swift │ ├── Application+public.swift │ ├── Application.swift │ └── Types │ │ ├── Dependency │ │ ├── Application+Dependency.swift │ │ └── Slice │ │ │ └── Application+DependencySlice.swift │ │ ├── Helper │ │ ├── Application+ApplicationPreview.swift │ │ ├── Application+Scope.swift │ │ ├── ApplicationLogger.swift │ │ ├── FileManager+AppState.swift │ │ ├── FileManaging.swift │ │ ├── Loggable.swift │ │ ├── MutableApplicationState.swift │ │ ├── UbiquitousKeyValueStoreManaging.swift │ │ └── UserDefaultsManaging.swift │ │ └── State │ │ ├── Application+FileState.swift │ │ ├── Application+SecureState.swift │ │ ├── Application+State.swift │ │ ├── Application+StoredState.swift │ │ ├── Application+SyncState.swift │ │ └── Slice │ │ ├── Application+OptionalSlice.swift │ │ ├── Application+OptionalSliceOptionalValue.swift │ │ └── Application+Slice.swift │ ├── Dependencies │ └── Keychain.swift │ └── PropertyWrappers │ ├── Dependency │ ├── AppDependency.swift │ ├── ObservedDependency.swift │ └── Slice │ │ ├── DependencyConstant.swift │ │ └── DependencySlice.swift │ └── State │ ├── AppState.swift │ ├── FileState.swift │ ├── SecureState.swift │ ├── Slice │ ├── Constant.swift │ ├── OptionalConstant.swift │ ├── OptionalSlice.swift │ └── Slice.swift │ ├── StoredState.swift │ └── SyncState.swift ├── Tests └── AppStateTests │ ├── AppDependencyTests.swift │ ├── AppStateTests.swift │ ├── ApplicationTests.swift │ ├── DependencySliceTests.swift │ ├── FileManagerExtensionTests.swift │ ├── FileStateTests.swift │ ├── KeychainTests.swift │ ├── ObservedDependencyTests.swift │ ├── OptionalSliceTests.swift │ ├── SecureStateTests.swift │ ├── SliceTests.swift │ ├── StoredStateTests.swift │ └── SyncStateTests.swift └── documentation ├── README.de.md ├── README.es.md ├── README.fr.md ├── README.hi.md ├── README.pt.md ├── README.ru.md ├── README.zh-CN.md ├── de ├── advanced-usage.md ├── best-practices.md ├── contributing.md ├── faq.md ├── installation.md ├── migration-considerations.md ├── starting-to-use-syncstate.md ├── syncstate-implementation.md ├── usage-constant.md ├── usage-filestate.md ├── usage-observeddependency.md ├── usage-overview.md ├── usage-securestate.md ├── usage-slice.md ├── usage-state-dependency.md ├── usage-storedstate.md └── usage-syncstate.md ├── en ├── advanced-usage.md ├── best-practices.md ├── contributing.md ├── faq.md ├── installation.md ├── migration-considerations.md ├── starting-to-use-syncstate.md ├── syncstate-implementation.md ├── usage-constant.md ├── usage-filestate.md ├── usage-observeddependency.md ├── usage-overview.md ├── usage-securestate.md ├── usage-slice.md ├── usage-state-dependency.md ├── usage-storedstate.md └── usage-syncstate.md ├── es ├── advanced-usage.md ├── best-practices.md ├── contributing.md ├── faq.md ├── installation.md ├── migration-considerations.md ├── starting-to-use-syncstate.md ├── syncstate-implementation.md ├── usage-constant.md ├── usage-filestate.md ├── usage-observeddependency.md ├── usage-overview.md ├── usage-securestate.md ├── usage-slice.md ├── usage-state-dependency.md ├── usage-storedstate.md └── usage-syncstate.md ├── fr ├── advanced-usage.md ├── best-practices.md ├── contributing.md ├── faq.md ├── installation.md ├── migration-considerations.md ├── starting-to-use-syncstate.md ├── syncstate-implementation.md ├── usage-constant.md ├── usage-filestate.md ├── usage-observeddependency.md ├── usage-overview.md ├── usage-securestate.md ├── usage-slice.md ├── usage-state-dependency.md ├── usage-storedstate.md └── usage-syncstate.md ├── hi ├── advanced-usage.md ├── best-practices.md ├── contributing.md ├── faq.md ├── installation.md ├── migration-considerations.md ├── starting-to-use-syncstate.md ├── syncstate-implementation.md ├── usage-constant.md ├── usage-filestate.md ├── usage-observeddependency.md ├── usage-overview.md ├── usage-securestate.md ├── usage-slice.md ├── usage-state-dependency.md ├── usage-storedstate.md └── usage-syncstate.md ├── pt ├── advanced-usage.md ├── best-practices.md ├── contributing.md ├── faq.md ├── installation.md ├── migration-considerations.md ├── starting-to-use-syncstate.md ├── syncstate-implementation.md ├── usage-constant.md ├── usage-filestate.md ├── usage-observeddependency.md ├── usage-overview.md ├── usage-securestate.md ├── usage-slice.md ├── usage-state-dependency.md ├── usage-storedstate.md └── usage-syncstate.md ├── ru ├── advanced-usage.md ├── best-practices.md ├── contributing.md ├── faq.md ├── installation.md ├── migration-considerations.md ├── starting-to-use-syncstate.md ├── syncstate-implementation.md ├── usage-constant.md ├── usage-filestate.md ├── usage-observeddependency.md ├── usage-overview.md ├── usage-securestate.md ├── usage-slice.md ├── usage-state-dependency.md ├── usage-storedstate.md └── usage-syncstate.md └── zh-CN ├── advanced-usage.md ├── best-practices.md ├── contributing.md ├── faq.md ├── installation.md ├── migration-considerations.md ├── starting-to-use-syncstate.md ├── syncstate-implementation.md ├── usage-constant.md ├── usage-filestate.md ├── usage-observeddependency.md ├── usage-overview.md ├── usage-securestate.md ├── usage-slice.md ├── usage-state-dependency.md ├── usage-storedstate.md └── usage-syncstate.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [0xLeif] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/docc.yml: -------------------------------------------------------------------------------- 1 | name: docc 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | 7 | permissions: 8 | contents: read 9 | pages: write 10 | id-token: write 11 | 12 | concurrency: 13 | group: pages 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | pages: 18 | environment: 19 | name: github-pages 20 | url: '${{ steps.deployment.outputs.page_url }}' 21 | runs-on: macos-latest 22 | 23 | steps: 24 | - name: Checkout code 25 | uses: actions/checkout@v3 26 | 27 | - name: Setup Xcode 28 | uses: maxim-lobanov/setup-xcode@v1 29 | with: 30 | xcode-version: 16.0 31 | 32 | - name: Set up Swift 33 | uses: swift-actions/setup-swift@v2 34 | with: 35 | swift-version: '6.1.0' 36 | 37 | - name: Build and Export DocC 38 | run: | 39 | swift package --allow-writing-to-directory docs \ 40 | generate-documentation --target AppState \ 41 | --output-path docs \ 42 | --transform-for-static-hosting \ 43 | --hosting-base-path AppState 44 | 45 | echo '' > docs/index.html 46 | 47 | - name: Configure Pages 48 | uses: actions/configure-pages@v5 49 | 50 | - name: Upload Pages artifact 51 | uses: actions/upload-pages-artifact@v3 52 | with: 53 | path: docs 54 | 55 | - name: Deploy to GitHub Pages 56 | id: deployment 57 | uses: actions/deploy-pages@v4 58 | -------------------------------------------------------------------------------- /.github/workflows/macOS.yml: -------------------------------------------------------------------------------- 1 | name: macOS 2 | 3 | on: 4 | push: 5 | branches: ["**"] 6 | 7 | jobs: 8 | build: 9 | runs-on: macos-latest 10 | steps: 11 | - uses: maxim-lobanov/setup-xcode@v1 12 | with: 13 | xcode-version: 16.0 14 | - name: Set up Swift 15 | uses: swift-actions/setup-swift@v2 16 | with: 17 | swift-version: '6.1.0' 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: swift build -v 21 | - name: Run tests 22 | run: swift test -v 23 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Swift project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift 3 | 4 | name: Ubuntu 5 | 6 | on: 7 | push: 8 | branches: ["**"] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Set up Swift 16 | uses: swift-actions/setup-swift@v2 17 | with: 18 | swift-version: '6.1.0' 19 | - uses: actions/checkout@v3 20 | - name: Build for release 21 | run: swift build -v -c release 22 | - name: Test 23 | run: swift test -v 24 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | branches: ["**"] 6 | 7 | jobs: 8 | build: 9 | runs-on: windows-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | # ① Install Swift for Windows 14 | - name: Set up Swift 6.1 15 | uses: compnerd/gha-setup-swift@main 16 | with: 17 | branch: swift-6.1-release # release branch 18 | tag: 6.1-RELEASE # exact toolchain tag 19 | 20 | # ② Build & test 21 | - run: swift --version # sanity-check 22 | - run: swift build 23 | - run: swift test 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/ 7 | .netrc 8 | Package.resolved -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Zach Eriksen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "AppState", 7 | platforms: [ 8 | .iOS(.v15), 9 | .watchOS(.v8), 10 | .macOS(.v11), 11 | .tvOS(.v15), 12 | .visionOS(.v1) 13 | ], 14 | products: [ 15 | .library( 16 | name: "AppState", 17 | targets: ["AppState"] 18 | ) 19 | ], 20 | dependencies: [ 21 | .package(url: "https://github.com/0xLeif/Cache", from: "2.0.0"), 22 | .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.4.0") 23 | ], 24 | targets: [ 25 | .target( 26 | name: "AppState", 27 | dependencies: [ 28 | "Cache" 29 | ] 30 | ), 31 | .testTarget( 32 | name: "AppStateTests", 33 | dependencies: ["AppState"] 34 | ) 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /Sources/AppState/Application/Types/Dependency/Application+Dependency.swift: -------------------------------------------------------------------------------- 1 | extension Application { 2 | var dependencyPromotions: State<[DependencyOverride]> { 3 | state(initial: []) 4 | } 5 | 6 | /// `Dependency` struct encapsulates dependencies used throughout the app. 7 | public struct Dependency: Sendable, Loggable { 8 | /// The dependency value. 9 | var value: Value 10 | 11 | /// The scope in which this state exists. 12 | let scope: Scope 13 | 14 | /** 15 | Initializes a new dependency within a given scope with an initial value. 16 | 17 | A Dependency allows for defining services or shared objects across the app. It is designed to be read-only and can only be changed by re-initializing it, ensuring thread-safety in your app. 18 | 19 | - Parameters: 20 | - value: The initial value of the dependency. 21 | - scope: The scope in which the dependency exists. 22 | */ 23 | init( 24 | _ value: Value, 25 | scope: Scope 26 | ) { 27 | self.value = value 28 | self.scope = scope 29 | } 30 | 31 | public var logValue: String { 32 | "Dependency<\(Value.self)>(\(value)) (\(scope.key))" 33 | } 34 | } 35 | 36 | /// `DependencyOverride` provides a handle to revert a dependency override operation. 37 | public final class DependencyOverride: Sendable { 38 | /// Closure to be invoked when the dependency override is cancelled. This closure typically contains logic to revert the overrides on the dependency. 39 | private let cancelOverride: @Sendable () async -> Void 40 | 41 | /** 42 | Initializes a `DependencyOverride` instance. 43 | 44 | - Parameter cancelOverride: The closure to be invoked when the 45 | dependency override is cancelled. 46 | */ 47 | init(cancelOverride: @Sendable @escaping () async -> Void) { 48 | self.cancelOverride = cancelOverride 49 | } 50 | 51 | /// Cancels the override and resets the Dependency back to its value before the override. 52 | public func cancel() async { 53 | await cancelOverride() 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/AppState/Application/Types/Dependency/Slice/Application+DependencySlice.swift: -------------------------------------------------------------------------------- 1 | extension Application { 2 | /// `DependencySlice` allows access and modification to a specific part of an AppState's dependencies. Supports `Dependency`. 3 | public struct DependencySlice< 4 | Value: Sendable, 5 | SliceValue, 6 | SliceKeyPath: KeyPath 7 | > { 8 | /// A private backing storage for the dependency. 9 | private var dependency: Dependency 10 | private let keyPath: SliceKeyPath 11 | 12 | @MainActor 13 | init( 14 | _ stateKeyPath: KeyPath>, 15 | value valueKeyPath: SliceKeyPath 16 | ) { 17 | self.dependency = shared.value(keyPath: stateKeyPath) 18 | self.keyPath = valueKeyPath 19 | } 20 | } 21 | } 22 | 23 | extension Application.DependencySlice where SliceKeyPath == KeyPath { 24 | /// The current dependency value. 25 | @MainActor 26 | public var value: SliceValue { 27 | dependency.value[keyPath: keyPath] 28 | } 29 | } 30 | 31 | extension Application.DependencySlice where SliceKeyPath == WritableKeyPath { 32 | /// The current dependency value. 33 | @MainActor 34 | public var value: SliceValue { 35 | get { dependency.value[keyPath: keyPath] } 36 | set { 37 | #if !os(Linux) && !os(Windows) 38 | Application.shared.objectWillChange.send() 39 | #endif 40 | dependency.value[keyPath: keyPath] = newValue 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/AppState/Application/Types/Helper/Application+ApplicationPreview.swift: -------------------------------------------------------------------------------- 1 | #if !os(Linux) && !os(Windows) 2 | import SwiftUI 3 | 4 | extension Application { 5 | struct ApplicationPreview: View { 6 | let dependencyOverrides: [DependencyOverride] 7 | let content: () -> Content 8 | 9 | init( 10 | dependencyOverrides: [DependencyOverride], 11 | content: @escaping () -> Content 12 | ) { 13 | self.dependencyOverrides = dependencyOverrides 14 | self.content = content 15 | } 16 | 17 | var body: some View { 18 | content() 19 | } 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/AppState/Application/Types/Helper/Application+Scope.swift: -------------------------------------------------------------------------------- 1 | extension Application { 2 | /** 3 | `Scope` represents a specific context in your application, defined by a name and an id. It's mainly used to maintain a specific state or behavior in your application. 4 | 5 | For example, it could be used to scope a state to a particular screen or user interaction flow. 6 | */ 7 | public struct Scope: Sendable { 8 | /// The name of the scope context 9 | public let name: String 10 | 11 | /// The specific id for this scope context 12 | public let id: String 13 | 14 | /// Key computed property which builds a unique key for a given scope by combining `name` and `id` separated by "/" 15 | public var key: String { 16 | "\(name)/\(id)" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/AppState/Application/Types/Helper/ApplicationLogger.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) || os(Windows) 2 | import Foundation 3 | 4 | /// `ApplicationLogger` is a struct that provides logging functionalities for Linux and Windows operating systems using closures. 5 | public struct ApplicationLogger: Sendable { 6 | public enum LoggerError: Error { 7 | case generalError 8 | } 9 | 10 | private var debugClosure: @Sendable (String) -> Void 11 | private var errorClosure: @Sendable (Error, String?) -> Void 12 | 13 | /// Initializes the `ApplicationLogger` struct with custom behaviors for each closure. 14 | /// - Parameters: 15 | /// - debug: A closure for logging debug messages. 16 | /// - debugWithClosure: A closure for logging debug messages using a closure. 17 | /// - errorWithMessage: A closure for logging error messages with an optional custom message. 18 | /// - errorWithString: A closure for logging error messages as strings. 19 | public init( 20 | debug: @Sendable @escaping (String) -> Void = { print($0) }, 21 | error: @Sendable @escaping (Error, String?) -> Void = { error, message in 22 | if let message = message { 23 | print("\(message) (Error: \(error.localizedDescription))") 24 | } else { 25 | print("Error: \(error.localizedDescription)") 26 | } 27 | } 28 | ) { 29 | self.debugClosure = debug 30 | self.errorClosure = error 31 | } 32 | 33 | /// Prints a debug message. 34 | /// - Parameter message: The message to be logged. 35 | public func debug(_ message: String) { 36 | debugClosure(message) 37 | } 38 | 39 | /// Prints a debug message using a closure. 40 | /// - Parameter message: A closure that returns the message to be logged. 41 | public func debug(_ message: () -> String) { 42 | debug(message()) 43 | } 44 | 45 | /// Logs an error message. 46 | /// - Parameters: 47 | /// - error: The error that occurred. 48 | /// - message: An optional custom message to accompany the error. 49 | public func error(_ error: Error, _ message: String? = nil) { 50 | errorClosure(error, message) 51 | } 52 | 53 | /// Logs a general error message. 54 | /// - Parameters: 55 | /// - message: An custom message to accompany the error. 56 | public func error(_ message: String) { 57 | errorClosure(LoggerError.generalError, message) 58 | } 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /Sources/AppState/Application/Types/Helper/FileManaging.swift: -------------------------------------------------------------------------------- 1 | /// A protocol that provides methods for reading, writing, and deleting files in a type-safe, sendable manner. 2 | public protocol FileManaging: Sendable { 3 | /// Reads a file from the given path and decodes its contents into the specified type. 4 | /// - Parameters: 5 | /// - path: The directory path where the file is located. Defaults to the current directory `"."`. 6 | /// - filename: The name of the file to read. 7 | /// - Returns: The decoded value of the file's content as the specified type. 8 | /// - Throws: An error if the file cannot be found or decoded. 9 | func `in`(path: String, filename: String) throws -> Value 10 | 11 | /// Encodes and writes the given value to a file at the specified path. 12 | /// - Parameters: 13 | /// - value: The value to encode and write to the file. It must conform to `Encodable`. 14 | /// - path: The directory path where the file will be written. Defaults to the current directory `"."`. 15 | /// - filename: The name of the file to write. 16 | /// - base64Encoded: Whether to encode the content as Base64. Defaults to `true`. 17 | /// - Throws: An error if the file cannot be written. 18 | func `out`(_ value: Value, path: String, filename: String, base64Encoded: Bool) throws 19 | 20 | /// Deletes a file at the specified path. 21 | /// - Parameters: 22 | /// - path: The directory path where the file is located. Defaults to the current directory `"."`. 23 | /// - filename: The name of the file to delete. 24 | /// - Throws: An error if the file cannot be deleted. 25 | func `delete`(path: String, filename: String) throws 26 | 27 | /// Removes a file or directory at the specified path. 28 | /// - Parameter path: The full path of the file or directory to remove. 29 | /// - Throws: An error if the item cannot be removed. 30 | func removeItem(atPath path: String) throws 31 | } 32 | -------------------------------------------------------------------------------- /Sources/AppState/Application/Types/Helper/Loggable.swift: -------------------------------------------------------------------------------- 1 | protocol Loggable { 2 | @MainActor 3 | var logValue: String { get } 4 | } 5 | -------------------------------------------------------------------------------- /Sources/AppState/Application/Types/Helper/MutableApplicationState.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A protocol that represents a mutable application state. 3 | 4 | This protocol defines a type, `Value`, and a mutable property, `value`, of that type. It serves as the blueprint for any type that needs to represent a mutable state within an application. 5 | */ 6 | public protocol MutableApplicationState { 7 | associatedtype Value 8 | 9 | /// An emoji to use when logging about this state. 10 | static var emoji: Character { get } 11 | 12 | /// The actual value that this state holds. It can be both retrieved and modified. 13 | @MainActor 14 | var value: Value { get set } 15 | 16 | /// The function used to reset the state to its initial value. 17 | @MainActor 18 | mutating func reset() 19 | } 20 | 21 | extension MutableApplicationState { 22 | static var emoji: Character { "❓" } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/AppState/Application/Types/Helper/UbiquitousKeyValueStoreManaging.swift: -------------------------------------------------------------------------------- 1 | #if !os(Linux) && !os(Windows) 2 | import Foundation 3 | 4 | /// A protocol that provides a thread-safe interface for interacting with `NSUbiquitousKeyValueStore`, 5 | /// which synchronizes key-value data across the user's iCloud-enabled devices. 6 | public protocol UbiquitousKeyValueStoreManaging: Sendable { 7 | /// Retrieves data stored in iCloud for the specified key. 8 | /// - Parameter key: The key used to retrieve the associated data from the `NSUbiquitousKeyValueStore`. 9 | /// - Returns: The `Data` object associated with the key, or `nil` if no data is found. 10 | func data(forKey key: String) -> Data? 11 | 12 | /// Sets a `Data` object for the specified key in iCloud's key-value store. 13 | /// - Parameters: 14 | /// - value: The `Data` object to store. Pass `nil` to remove the data associated with the key. 15 | /// - key: The key with which to associate the data. 16 | func set(_ value: Data?, forKey key: String) 17 | 18 | /// Removes the value associated with the specified key from iCloud's key-value store. 19 | /// - Parameter key: The key whose associated value should be removed. 20 | func removeObject(forKey key: String) 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/AppState/Application/Types/Helper/UserDefaultsManaging.swift: -------------------------------------------------------------------------------- 1 | /// A protocol that provides a thread-safe interface for interacting with `UserDefaults`, 2 | /// allowing the storage, retrieval, and removal of user preferences and data. 3 | public protocol UserDefaultsManaging: Sendable { 4 | /// Retrieves an object from `UserDefaults` for the given key. 5 | /// - Parameter key: The key used to retrieve the associated value from `UserDefaults`. 6 | /// - Returns: The value stored in `UserDefaults` for the given key, or `nil` if no value is associated with the key. 7 | func object(forKey key: String) -> Any? 8 | 9 | /// Removes the value associated with the specified key from `UserDefaults`. 10 | /// - Parameter key: The key whose associated value should be removed. 11 | func removeObject(forKey key: String) 12 | 13 | /// Sets the value for the specified key in `UserDefaults`. 14 | /// - Parameters: 15 | /// - value: The value to store in `UserDefaults`. Can be `nil` to remove the value associated with the key. 16 | /// - key: The key with which to associate the value. 17 | func set(_ value: Any?, forKey key: String) 18 | } 19 | -------------------------------------------------------------------------------- /Sources/AppState/Application/Types/State/Application+SecureState.swift: -------------------------------------------------------------------------------- 1 | #if !os(Linux) && !os(Windows) 2 | import Security 3 | import Foundation 4 | 5 | extension Application { 6 | /// The default `Keychain` instance. 7 | public var keychain: Dependency { 8 | dependency(Keychain()) 9 | } 10 | 11 | /// The SecureState structure provides secure and persistent key-value string storage backed by the Keychain that can be used across the application. 12 | public struct SecureState: MutableApplicationState { 13 | public static var emoji: Character { "🔑" } 14 | 15 | @AppDependency(\.keychain) private var keychain: Keychain 16 | 17 | /// The initial value of the state. 18 | private var initial: () -> String? 19 | 20 | /// The current state value. 21 | /// Reading this value will return the stored string value in the keychain if it exists, otherwise, it will return the initial value. 22 | /// Writing a new string value to the state, it will be stored securely in the keychain. Writing `nil` will remove the corresponding key-value pair from the keychain store. 23 | public var value: String? { 24 | get { 25 | guard 26 | let storedValue = keychain.get(scope.key, as: String.self) 27 | else { return initial() } 28 | 29 | return storedValue 30 | } 31 | set { 32 | shared.objectWillChange.send() 33 | 34 | guard let newValue else { 35 | return keychain.remove(scope.key) 36 | } 37 | 38 | keychain.set(value: newValue, forKey: scope.key) 39 | } 40 | } 41 | 42 | /// The scope in which this state exists. 43 | let scope: Scope 44 | 45 | /** 46 | Creates a new state within a given scope initialized with the provided value. 47 | 48 | - Parameters: 49 | - value: The initial value of the state 50 | - scope: The scope in which the state exists 51 | */ 52 | init( 53 | initial: @escaping @autoclosure () -> String?, 54 | scope: Scope 55 | ) { 56 | self.initial = initial 57 | self.scope = scope 58 | } 59 | 60 | /// Resets the state value to the initial value and store it in the keychain. 61 | @MainActor 62 | public mutating func reset() { 63 | value = initial() 64 | } 65 | } 66 | } 67 | #endif 68 | -------------------------------------------------------------------------------- /Sources/AppState/Application/Types/State/Slice/Application+OptionalSlice.swift: -------------------------------------------------------------------------------- 1 | extension Application { 2 | /// `OptionalSlice` allows access and modification to a specific part of an AppState's state. Supports `State`, `SyncState`, and `StorageState`. 3 | public struct OptionalSlice< 4 | SlicedState: MutableApplicationState, 5 | Value, 6 | SliceValue, 7 | SliceKeyPath: KeyPath 8 | > where SlicedState.Value == Value? { 9 | /// A private backing storage for the value. 10 | private var state: SlicedState 11 | private let keyPath: SliceKeyPath 12 | 13 | @MainActor 14 | init( 15 | _ stateKeyPath: KeyPath, 16 | value valueKeyPath: SliceKeyPath 17 | ) { 18 | self.state = shared.value(keyPath: stateKeyPath) 19 | self.keyPath = valueKeyPath 20 | } 21 | } 22 | } 23 | 24 | extension Application.OptionalSlice where SliceKeyPath == KeyPath { 25 | /// The current state value. 26 | @MainActor 27 | public var value: SliceValue? { 28 | state.value?[keyPath: keyPath] 29 | } 30 | } 31 | 32 | extension Application.OptionalSlice where SliceKeyPath == WritableKeyPath { 33 | /// The current state value. 34 | @MainActor 35 | public var value: SliceValue? { 36 | get { 37 | guard let sliceState = state.value else { return nil } 38 | 39 | return sliceState[keyPath: keyPath] 40 | } 41 | set { 42 | guard var sliceState = state.value else { return } 43 | 44 | if let newValue { 45 | sliceState[keyPath: keyPath] = newValue 46 | } 47 | 48 | state.value = sliceState 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/AppState/Application/Types/State/Slice/Application+OptionalSliceOptionalValue.swift: -------------------------------------------------------------------------------- 1 | extension Application { 2 | /// `OptionalSliceOptionalValue` allows access and modification to a specific part of an AppState's state. Supports `State`, `SyncState`, and `StorageState`. 3 | public struct OptionalSliceOptionalValue< 4 | SlicedState: MutableApplicationState, 5 | Value, 6 | SliceValue, 7 | SliceKeyPath: KeyPath 8 | > where SlicedState.Value == Value? { 9 | /// A private backing storage for the value. 10 | private var state: SlicedState 11 | private let keyPath: SliceKeyPath 12 | 13 | @MainActor 14 | init( 15 | _ stateKeyPath: KeyPath, 16 | value valueKeyPath: SliceKeyPath 17 | ) { 18 | self.state = shared.value(keyPath: stateKeyPath) 19 | self.keyPath = valueKeyPath 20 | } 21 | } 22 | } 23 | 24 | extension Application.OptionalSliceOptionalValue where SliceKeyPath == KeyPath { 25 | /// The current state value. 26 | @MainActor 27 | public var value: SliceValue? { 28 | state.value?[keyPath: keyPath] 29 | } 30 | } 31 | 32 | extension Application.OptionalSliceOptionalValue where SliceKeyPath == WritableKeyPath { 33 | /// The current state value. 34 | @MainActor 35 | public var value: SliceValue? { 36 | get { 37 | guard let sliceState = state.value else { return nil } 38 | 39 | return sliceState[keyPath: keyPath] 40 | } 41 | set { 42 | guard var sliceState = state.value else { return } 43 | 44 | sliceState[keyPath: keyPath] = newValue 45 | 46 | state.value = sliceState 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/AppState/Application/Types/State/Slice/Application+Slice.swift: -------------------------------------------------------------------------------- 1 | extension Application { 2 | /// `Slice` allows access and modification to a specific part of an AppState's state. Supports `State`, `SyncState`, and `StorageState`. 3 | public struct Slice< 4 | SlicedState: MutableApplicationState, 5 | Value, 6 | SliceValue, 7 | SliceKeyPath: KeyPath 8 | > where SlicedState.Value == Value { 9 | /// A private backing storage for the value. 10 | private var state: SlicedState 11 | private let keyPath: SliceKeyPath 12 | 13 | @MainActor 14 | init( 15 | _ stateKeyPath: KeyPath, 16 | value valueKeyPath: SliceKeyPath 17 | ) { 18 | self.state = shared.value(keyPath: stateKeyPath) 19 | self.keyPath = valueKeyPath 20 | } 21 | } 22 | } 23 | 24 | extension Application.Slice where SliceKeyPath == KeyPath { 25 | /// The current state value. 26 | @MainActor 27 | public var value: SliceValue { 28 | state.value[keyPath: keyPath] 29 | } 30 | } 31 | 32 | extension Application.Slice where SliceKeyPath == WritableKeyPath { 33 | /// The current state value. 34 | @MainActor 35 | public var value: SliceValue { 36 | get { state.value[keyPath: keyPath] } 37 | set { state.value[keyPath: keyPath] = newValue } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/AppState/PropertyWrappers/Dependency/AppDependency.swift: -------------------------------------------------------------------------------- 1 | /// The `@AppDependency` property wrapper is a feature provided by AppState, intended to simplify dependency handling throughout your application. It makes it easy to access, share, and manage dependencies in a neat and Swift idiomatic way. 2 | @propertyWrapper public struct AppDependency { 3 | /// Path for accessing `Dependency` from Application. 4 | private let keyPath: KeyPath> 5 | 6 | private let fileID: StaticString 7 | private let function: StaticString 8 | private let line: Int 9 | private let column: Int 10 | 11 | /// Represents the current value of the `Dependency`. 12 | @MainActor 13 | public var wrappedValue: Value { 14 | Application.dependency( 15 | keyPath, 16 | fileID, 17 | function, 18 | line, 19 | column 20 | ) 21 | } 22 | 23 | /** 24 | Initializes the AppDependency with a `keyPath` for accessing `Dependency` in Application. 25 | 26 | - Parameter keyPath: The `KeyPath` for accessing `Dependency` in Application. 27 | */ 28 | public init( 29 | _ keyPath: KeyPath>, 30 | _ fileID: StaticString = #fileID, 31 | _ function: StaticString = #function, 32 | _ line: Int = #line, 33 | _ column: Int = #column 34 | ) { 35 | self.keyPath = keyPath 36 | self.fileID = fileID 37 | self.function = function 38 | self.line = line 39 | self.column = column 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/AppState/PropertyWrappers/Dependency/ObservedDependency.swift: -------------------------------------------------------------------------------- 1 | #if !os(Linux) && !os(Windows) 2 | import SwiftUI 3 | 4 | /// The `@ObservedDependency` property wrapper is a feature provided by AppState, intended to simplify dependency handling throughout your application. It makes it easy to access, share, and manage dependencies in a neat and Swift idiomatic way. It works the same as `@AppDependency`, but comes with the power of the `@ObservedObject` property wrapper. 5 | @MainActor 6 | @propertyWrapper public struct ObservedDependency: DynamicProperty where Value: ObservableObject { 7 | /// Path for accessing `ObservedDependency` from Application. 8 | private let keyPath: KeyPath> 9 | 10 | @ObservedObject private var observedObject: Value 11 | 12 | private let fileID: StaticString 13 | private let function: StaticString 14 | private let line: Int 15 | private let column: Int 16 | 17 | /// Represents the current value of the `ObservedDependency`. 18 | @MainActor 19 | public var wrappedValue: Value { 20 | Application.log( 21 | debug: "🔗 Getting ObservedDependency \(String(describing: keyPath))", 22 | fileID: fileID, 23 | function: function, 24 | line: line, 25 | column: column 26 | ) 27 | 28 | return observedObject 29 | } 30 | 31 | /// A binding to the `ObservedDependency`'s value, which can be used with SwiftUI views. 32 | @MainActor 33 | public var projectedValue: ObservedObject.Wrapper { 34 | Application.log( 35 | debug: "🔗 Getting ObservedDependency \(String(describing: keyPath))", 36 | fileID: fileID, 37 | function: function, 38 | line: line, 39 | column: column 40 | ) 41 | 42 | return $observedObject 43 | } 44 | 45 | /** 46 | Initializes the ObservedDependency. 47 | 48 | - Parameter keyPath: The `KeyPath` for accessing `Dependency` in Application. 49 | */ 50 | @MainActor 51 | public init( 52 | _ keyPath: KeyPath>, 53 | _ fileID: StaticString = #fileID, 54 | _ function: StaticString = #function, 55 | _ line: Int = #line, 56 | _ column: Int = #column 57 | ) { 58 | self.keyPath = keyPath 59 | self.fileID = fileID 60 | self.function = function 61 | self.line = line 62 | self.column = column 63 | 64 | self.observedObject = Application.dependency( 65 | keyPath, 66 | fileID, 67 | function, 68 | line, 69 | column 70 | ) 71 | } 72 | } 73 | #endif 74 | -------------------------------------------------------------------------------- /Tests/AppStateTests/AppDependencyTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AppState 3 | 4 | fileprivate protocol Networking: Sendable { 5 | func fetch() 6 | } 7 | 8 | fileprivate final class NetworkService: Networking { 9 | func fetch() { 10 | fatalError() 11 | } 12 | } 13 | 14 | fileprivate final class MockNetworking: Networking { 15 | func fetch() { /* no-op */ } 16 | } 17 | 18 | @MainActor 19 | fileprivate class ComposableService { 20 | @AppDependency(\.networking) var networking: Networking 21 | } 22 | 23 | fileprivate extension Application { 24 | var networking: Dependency { 25 | dependency(NetworkService()) 26 | } 27 | 28 | @MainActor 29 | var composableService: Dependency { 30 | dependency(ComposableService()) 31 | } 32 | } 33 | 34 | fileprivate struct ExampleDependencyWrapper { 35 | @AppDependency(\.networking) private var networking 36 | 37 | @MainActor 38 | func fetch() { 39 | networking.fetch() 40 | } 41 | } 42 | 43 | @MainActor 44 | final class AppDependencyTests: XCTestCase { 45 | override func setUp() async throws { 46 | Application 47 | .logging(isEnabled: true) 48 | .promote(\.networking, with: MockNetworking()) 49 | } 50 | 51 | override func tearDown() async throws { 52 | let applicationDescription = Application.description 53 | 54 | Application.dependency(\.logger).debug("AppDependencyTests \(applicationDescription)") 55 | } 56 | 57 | func testComposableDependencies() { 58 | let composableService = Application.dependency(\.composableService) 59 | 60 | composableService.networking.fetch() 61 | } 62 | 63 | func testDependency() async { 64 | Application.promote(\.networking, with: NetworkService()) 65 | 66 | let networkingOverride = Application.override(\.networking, with: MockNetworking()) 67 | 68 | let mockNetworking = Application.dependency(\.networking) 69 | 70 | XCTAssertNotNil(mockNetworking as? MockNetworking) 71 | 72 | mockNetworking.fetch() 73 | 74 | let example = ExampleDependencyWrapper() 75 | 76 | example.fetch() 77 | 78 | await networkingOverride.cancel() 79 | 80 | let networkingService = Application.dependency(\.networking) 81 | 82 | XCTAssertNotNil(networkingService as? NetworkService) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Tests/AppStateTests/ApplicationTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AppState 3 | 4 | fileprivate class SomeApplication: Application { 5 | static func someFunction() { /* no-op */ } 6 | } 7 | 8 | final class ApplicationTests: XCTestCase { 9 | func testCustomFunction() async throws { 10 | let applicationType = await Application.logging(isEnabled: true) 11 | .load(dependency: \.userDefaults) 12 | .promote(to: SomeApplication.self) 13 | 14 | applicationType.someFunction() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Tests/AppStateTests/DependencySliceTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if !os(Linux) && !os(Windows) 3 | import SwiftUI 4 | #endif 5 | import XCTest 6 | @testable import AppState 7 | 8 | @MainActor 9 | fileprivate class ExampleViewModel: Sendable { 10 | var username: String? = nil 11 | var isLoading: Bool = false 12 | let value: String = "Hello, World!" 13 | var mutableValue: String = "..." 14 | } 15 | 16 | #if !os(Linux) && !os(Windows) 17 | extension ExampleViewModel: ObservableObject { } 18 | #endif 19 | 20 | fileprivate extension Application { 21 | var exampleViewModel: Dependency { 22 | dependency(ExampleViewModel()) 23 | } 24 | } 25 | 26 | @MainActor 27 | fileprivate struct ExampleView { 28 | @DependencySlice(\.exampleViewModel, \.username) var username 29 | @DependencySlice(\.exampleViewModel, \.isLoading) var isLoading 30 | @DependencyConstant(\.exampleViewModel, \.value) var constantValue 31 | @DependencyConstant(\.exampleViewModel, \.mutableValue) var constantMutableValue 32 | 33 | func testPropertyWrappers() { 34 | username = "Hello, ExampleView" 35 | #if !os(Linux) && !os(Windows) 36 | _ = Toggle(isOn: $isLoading) { 37 | Text(constantMutableValue) 38 | } 39 | #endif 40 | } 41 | } 42 | 43 | final class DependencySliceTests: XCTestCase { 44 | @MainActor 45 | override func setUp() async throws { 46 | Application.logging(isEnabled: true) 47 | } 48 | 49 | @MainActor 50 | override func tearDown() async throws { 51 | let applicationDescription = Application.description 52 | 53 | Application.dependency(\.logger).debug("DependencySliceTests \(applicationDescription)") 54 | } 55 | 56 | @MainActor 57 | func testApplicationSliceFunction() async { 58 | var exampleSlice = Application.dependencySlice(\.exampleViewModel, \.username) 59 | 60 | exampleSlice.value = "New Value!" 61 | 62 | XCTAssertEqual(exampleSlice.value, "New Value!") 63 | XCTAssertEqual(Application.dependencySlice(\.exampleViewModel, \.username).value, "New Value!") 64 | XCTAssertEqual(Application.dependency(\.exampleViewModel).username, "New Value!") 65 | 66 | exampleSlice.value = "Leif" 67 | 68 | XCTAssertEqual(exampleSlice.value, "Leif") 69 | XCTAssertEqual(Application.dependencySlice(\.exampleViewModel, \.username).value, "Leif") 70 | XCTAssertEqual(Application.dependency(\.exampleViewModel).username, "Leif") 71 | } 72 | 73 | @MainActor 74 | func testPropertyWrappers() async { 75 | let exampleView = ExampleView() 76 | 77 | XCTAssertEqual(exampleView.username, "Leif") 78 | 79 | exampleView.testPropertyWrappers() 80 | 81 | XCTAssertEqual(exampleView.username, "Hello, ExampleView") 82 | XCTAssertEqual(exampleView.constantValue, "Hello, World!") 83 | XCTAssertEqual(exampleView.constantMutableValue, "...") 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Tests/AppStateTests/FileManagerExtensionTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AppState 3 | 4 | final class FileManagerExtensionTests: XCTestCase { 5 | func testWriteAndReadCodable() throws { 6 | let path = "./FileManagerTests" 7 | let filename = "test.json" 8 | let text = "Hello" 9 | 10 | try FileManager.default.out(text, path: path, filename: filename, base64Encoded: false) 11 | let readText: String = try FileManager.default.in(path: path, filename: filename) 12 | XCTAssertEqual(readText, text) 13 | 14 | try FileManager.default.delete(path: path, filename: filename) 15 | try FileManager.default.removeItem(atPath: path) 16 | } 17 | 18 | func testWriteAndReadData() throws { 19 | let path = "./FileManagerTestsData" 20 | let filename = "data.txt" 21 | let data = Data("Data".utf8) 22 | 23 | try FileManager.default.out(data: data, path: path, filename: filename, base64Encoded: true) 24 | let readData = try FileManager.default.data(path: path, filename: filename) 25 | XCTAssertEqual(readData, data) 26 | 27 | try FileManager.default.delete(path: path, filename: filename) 28 | try FileManager.default.removeItem(atPath: path) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/AppStateTests/FileStateTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if !os(Linux) && !os(Windows) 3 | import SwiftUI 4 | #endif 5 | import XCTest 6 | @testable import AppState 7 | 8 | fileprivate extension Application { 9 | @MainActor 10 | var storedValue: FileState { 11 | fileState(filename: "storedValue") 12 | } 13 | 14 | @MainActor 15 | var storedString: FileState { 16 | fileState(filename: "storedString", isBase64Encoded: false) 17 | } 18 | } 19 | 20 | @MainActor 21 | fileprivate struct ExampleStoredValue { 22 | @FileState(\.storedValue) var count 23 | @FileState(\.storedString) var storedString 24 | } 25 | 26 | @MainActor 27 | fileprivate class ExampleStoringViewModel { 28 | @FileState(\.storedValue) var count 29 | @FileState(\.storedString) var storedString 30 | 31 | func testPropertyWrapper() { 32 | count = 27 33 | storedString = "Hello" 34 | #if !os(Linux) && !os(Windows) 35 | _ = TextField( 36 | value: $count, 37 | format: .number, 38 | label: { Text("Count") } 39 | ) 40 | #endif 41 | } 42 | } 43 | 44 | #if !os(Linux) && !os(Windows) 45 | extension ExampleStoringViewModel: ObservableObject { } 46 | #endif 47 | 48 | final class FileStateTests: XCTestCase { 49 | @MainActor 50 | override func setUp() async throws { 51 | Application.logging(isEnabled: true) 52 | 53 | FileManager.defaultFileStatePath = "./AppStateTests" 54 | } 55 | 56 | @MainActor 57 | override func tearDown() async throws { 58 | let applicationDescription = Application.description 59 | 60 | Application.dependency(\.logger).debug("FileStateTests \(applicationDescription)") 61 | 62 | try? Application.dependency(\.fileManager).removeItem(atPath: "./AppStateTests") 63 | } 64 | 65 | @MainActor 66 | func testFileState() async { 67 | XCTAssertEqual(FileManager.defaultFileStatePath, "./AppStateTests") 68 | XCTAssertNil(Application.fileState(\.storedValue).value) 69 | XCTAssertNil(Application.fileState(\.storedString).value) 70 | 71 | let storedValue = ExampleStoredValue() 72 | 73 | XCTAssertEqual(storedValue.count, nil) 74 | XCTAssertEqual(storedValue.storedString, nil) 75 | 76 | storedValue.count = 1 77 | storedValue.storedString = "Hello" 78 | 79 | XCTAssertEqual(storedValue.count, 1) 80 | XCTAssertEqual(storedValue.storedString, "Hello") 81 | 82 | Application.dependency(\.logger).debug("FileStateTests \(Application.description)") 83 | 84 | storedValue.count = nil 85 | storedValue.storedString = nil 86 | 87 | XCTAssertNil(Application.fileState(\.storedValue).value) 88 | XCTAssertNil(Application.fileState(\.storedString).value) 89 | } 90 | 91 | @MainActor 92 | func testStoringViewModel() async { 93 | XCTAssertEqual(FileManager.defaultFileStatePath, "./AppStateTests") 94 | XCTAssertNil(Application.fileState(\.storedValue).value) 95 | XCTAssertNil(Application.fileState(\.storedString).value) 96 | 97 | let viewModel = ExampleStoringViewModel() 98 | 99 | XCTAssertEqual(viewModel.count, nil) 100 | XCTAssertEqual(viewModel.storedString, nil) 101 | 102 | viewModel.testPropertyWrapper() 103 | 104 | XCTAssertEqual(viewModel.count, 27) 105 | XCTAssertEqual(viewModel.storedString, "Hello") 106 | 107 | Application.reset(fileState: \.storedValue) 108 | Application.reset(fileState: \.storedString) 109 | 110 | XCTAssertNil(viewModel.count) 111 | XCTAssertNil(viewModel.storedString) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Tests/AppStateTests/KeychainTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(Linux) && !os(Windows) 2 | import XCTest 3 | @testable import AppState 4 | 5 | final class KeychainTests: XCTestCase { 6 | @MainActor 7 | func testKeychainInitKeys() async throws { 8 | let keychain = Keychain(keys: ["key"]) 9 | 10 | XCTAssertThrowsError(try keychain.resolve("key")) 11 | } 12 | 13 | @MainActor 14 | func testKeychainInitValues() async throws { 15 | let keychain = Keychain(keys: ["key"]) 16 | 17 | keychain.set(value: "abc", forKey: "key") 18 | 19 | let value = try keychain.resolve("key") 20 | 21 | XCTAssertEqual(value, "abc") 22 | 23 | keychain.remove("key") 24 | } 25 | 26 | @MainActor 27 | func testKeychainContains() async throws { 28 | let keychain = Keychain() 29 | 30 | XCTAssertFalse(keychain.contains("key")) 31 | 32 | keychain.set(value: "abc", forKey: "key") 33 | 34 | XCTAssertTrue(keychain.contains("key")) 35 | 36 | keychain.remove("key") 37 | } 38 | 39 | @MainActor 40 | func testKeychainRequiresSuccess() async throws { 41 | let keychain = Keychain(keys: ["key"]) 42 | 43 | keychain.set(value: "abs", forKey: "key") 44 | 45 | XCTAssertNoThrow(try keychain.require("key")) 46 | 47 | keychain.remove("key") 48 | } 49 | 50 | @MainActor 51 | func testKeychainRequiresFailure() async throws { 52 | let keychain = Keychain() 53 | 54 | XCTAssertThrowsError(try keychain.require("key")) 55 | } 56 | 57 | @MainActor 58 | func testKeychainValues() async throws { 59 | let keychain = Keychain(keys: ["key"]) 60 | 61 | keychain.set(value: "abc", forKey: "key") 62 | 63 | let values = keychain.values() 64 | let secureValue = keychain.get("key") 65 | 66 | XCTAssertEqual(values.count, 1) 67 | XCTAssertNotNil(secureValue) 68 | XCTAssertEqual(secureValue, "abc") 69 | 70 | keychain.remove("key") 71 | 72 | XCTAssertEqual(keychain.values().count, 0) 73 | XCTAssertNil(keychain.get("key")) 74 | } 75 | } 76 | #endif 77 | -------------------------------------------------------------------------------- /Tests/AppStateTests/ObservedDependencyTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(Linux) && !os(Windows) 2 | import SwiftUI 3 | import XCTest 4 | @testable import AppState 5 | 6 | @MainActor 7 | fileprivate class ObservableService: ObservableObject { 8 | @Published var count: Int 9 | 10 | init() { 11 | count = 0 12 | } 13 | } 14 | 15 | fileprivate extension Application { 16 | var test: Dependency { 17 | dependency("!!!") 18 | } 19 | 20 | @MainActor 21 | var observableService: Dependency { 22 | dependency(ObservableService()) 23 | } 24 | } 25 | 26 | @MainActor 27 | fileprivate struct ExampleDependencyWrapper { 28 | @ObservedDependency(\.observableService) var service 29 | 30 | func test() { 31 | service.count += 1 32 | 33 | _ = Picker("", selection: $service.count, content: EmptyView.init) 34 | } 35 | } 36 | 37 | final class ObservedDependencyTests: XCTestCase { 38 | @MainActor 39 | override func setUp() async throws { 40 | Application.logging(isEnabled: true) 41 | } 42 | 43 | @MainActor 44 | override func tearDown() async throws { 45 | let applicationDescription = Application.description 46 | 47 | Application.dependency(\.logger).debug("ObservedDependencyTests \(applicationDescription)") 48 | } 49 | 50 | @MainActor 51 | func testDependency() async { 52 | let example = ExampleDependencyWrapper() 53 | 54 | XCTAssertEqual(example.service.count, 0) 55 | 56 | example.test() 57 | 58 | XCTAssertEqual(example.service.count, 1) 59 | } 60 | } 61 | #endif 62 | -------------------------------------------------------------------------------- /Tests/AppStateTests/SecureStateTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(Linux) && !os(Windows) 2 | import SwiftUI 3 | import XCTest 4 | @testable import AppState 5 | 6 | fileprivate extension Application { 7 | var secureValue: SecureState { 8 | secureState(id: "secureState") 9 | } 10 | } 11 | 12 | @MainActor 13 | fileprivate struct ExampleSecureValue { 14 | @SecureState(\.secureValue) var token: String? 15 | } 16 | 17 | @MainActor 18 | fileprivate class ExampleSecureViewModel: ObservableObject { 19 | @SecureState(\.secureValue) var token: String? 20 | 21 | func testPropertyWrapper() { 22 | token = "QWERTY" 23 | _ = Picker("Picker", selection: $token, content: EmptyView.init) 24 | } 25 | } 26 | 27 | final class SecureStateTests: XCTestCase { 28 | @MainActor 29 | override func setUp() async throws { 30 | Application 31 | .logging(isEnabled: true) 32 | .load(dependency: \.keychain) 33 | } 34 | 35 | @MainActor 36 | override func tearDown() async throws { 37 | let applicationDescription = Application.description 38 | 39 | Application.dependency(\.logger).debug("SecureStateTests \(applicationDescription)") 40 | } 41 | 42 | @MainActor 43 | func testSecureState() async { 44 | XCTAssertNil(Application.secureState(\.secureValue).value) 45 | 46 | let secureValue = ExampleSecureValue() 47 | 48 | XCTAssertEqual(secureValue.token, nil) 49 | 50 | secureValue.token = "QWERTY" 51 | 52 | XCTAssertEqual(secureValue.token, "QWERTY") 53 | 54 | secureValue.token = UUID().uuidString 55 | 56 | XCTAssertNotEqual(secureValue.token, "QWERTY") 57 | 58 | Application.dependency(\.logger).debug("SecureStateTests \(Application.description)") 59 | 60 | secureValue.token = nil 61 | 62 | XCTAssertNil(Application.secureState(\.secureValue).value) 63 | } 64 | 65 | @MainActor 66 | func testStoringViewModel() async { 67 | XCTAssertNil(Application.secureState(\.secureValue).value) 68 | 69 | let viewModel = ExampleSecureViewModel() 70 | 71 | XCTAssertEqual(viewModel.token, nil) 72 | 73 | viewModel.testPropertyWrapper() 74 | 75 | XCTAssertEqual(viewModel.token, "QWERTY") 76 | 77 | Application.reset(secureState: \.secureValue) 78 | 79 | XCTAssertNil(viewModel.token) 80 | } 81 | } 82 | #endif 83 | -------------------------------------------------------------------------------- /Tests/AppStateTests/SliceTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if !os(Linux) && !os(Windows) 3 | import SwiftUI 4 | #endif 5 | import XCTest 6 | @testable import AppState 7 | 8 | fileprivate struct ExampleValue { 9 | var username: String? 10 | var isLoading: Bool 11 | let value: String 12 | var mutableValue: String 13 | } 14 | 15 | fileprivate extension Application { 16 | var exampleValue: State { 17 | state( 18 | initial: ExampleValue( 19 | username: "Leif", 20 | isLoading: false, 21 | value: "value", 22 | mutableValue: "" 23 | ) 24 | ) 25 | } 26 | } 27 | 28 | @MainActor 29 | fileprivate class ExampleViewModel { 30 | @Slice(\.exampleValue, \.username) var username 31 | @Constant(\.exampleValue, \.value) var value 32 | 33 | func testPropertyWrapper() { 34 | username = "Hello, ExampleView" 35 | } 36 | } 37 | 38 | #if !os(Linux) && !os(Windows) 39 | extension ExampleViewModel: ObservableObject { } 40 | #endif 41 | 42 | @MainActor 43 | fileprivate struct ExampleView { 44 | @Slice(\.exampleValue, \.username) var username 45 | @Slice(\.exampleValue, \.isLoading) var isLoading 46 | @Constant(\.exampleValue, \.mutableValue) var constantMutableValue 47 | 48 | func testPropertyWrappers() { 49 | username = "Hello, ExampleView" 50 | #if !os(Linux) && !os(Windows) 51 | _ = Toggle(isOn: $isLoading) { 52 | Text(constantMutableValue) 53 | } 54 | #endif 55 | } 56 | } 57 | 58 | final class SliceTests: XCTestCase { 59 | @MainActor 60 | override func setUp() async throws { 61 | Application.logging(isEnabled: true) 62 | } 63 | 64 | @MainActor 65 | override func tearDown() async throws { 66 | let applicationDescription = Application.description 67 | 68 | Application.dependency(\.logger).debug("AppStateTests \(applicationDescription)") 69 | } 70 | 71 | @MainActor 72 | func testApplicationSliceFunction() async { 73 | var exampleSlice = Application.slice(\.exampleValue, \.username) 74 | 75 | exampleSlice.value = "New Value!" 76 | 77 | XCTAssertEqual(exampleSlice.value, "New Value!") 78 | XCTAssertEqual(Application.slice(\.exampleValue, \.username).value, "New Value!") 79 | XCTAssertEqual(Application.state(\.exampleValue).value.username, "New Value!") 80 | 81 | exampleSlice.value = "Leif" 82 | 83 | XCTAssertEqual(exampleSlice.value, "Leif") 84 | XCTAssertEqual(Application.slice(\.exampleValue, \.username).value, "Leif") 85 | XCTAssertEqual(Application.state(\.exampleValue).value.username, "Leif") 86 | } 87 | 88 | @MainActor 89 | func testPropertyWrappers() async { 90 | let exampleView = ExampleView() 91 | 92 | XCTAssertEqual(exampleView.username, "Leif") 93 | 94 | exampleView.testPropertyWrappers() 95 | 96 | XCTAssertEqual(exampleView.username, "Hello, ExampleView") 97 | 98 | let viewModel = ExampleViewModel() 99 | 100 | XCTAssertEqual(viewModel.username, "Hello, ExampleView") 101 | 102 | viewModel.username = "Hello, ViewModel" 103 | 104 | XCTAssertEqual(viewModel.username, "Hello, ViewModel") 105 | 106 | XCTAssertEqual(viewModel.value, "value") 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Tests/AppStateTests/StoredStateTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if !os(Linux) && !os(Windows) 3 | import SwiftUI 4 | #endif 5 | import XCTest 6 | @testable import AppState 7 | 8 | fileprivate extension Application { 9 | var storedValue: StoredState { 10 | storedState(id: "storedValue") 11 | } 12 | } 13 | 14 | @MainActor 15 | fileprivate struct ExampleStoredValue { 16 | @StoredState(\.storedValue) var count 17 | } 18 | 19 | @MainActor 20 | fileprivate class ExampleStoringViewModel { 21 | @StoredState(\.storedValue) var count 22 | 23 | func testPropertyWrapper() { 24 | count = 27 25 | #if !os(Linux) && !os(Windows) 26 | _ = TextField( 27 | value: $count, 28 | format: .number, 29 | label: { Text("Count") } 30 | ) 31 | #endif 32 | } 33 | } 34 | 35 | #if !os(Linux) && !os(Windows) 36 | extension ExampleStoringViewModel: ObservableObject { } 37 | #endif 38 | 39 | final class StoredStateTests: XCTestCase { 40 | @MainActor 41 | override func setUp() async throws { 42 | Application.logging(isEnabled: true) 43 | } 44 | 45 | @MainActor 46 | override func tearDown() async throws { 47 | let applicationDescription = Application.description 48 | 49 | Application.dependency(\.logger).debug("StoredStateTests \(applicationDescription)") 50 | } 51 | 52 | @MainActor 53 | func testStoredState() async { 54 | XCTAssertNil(Application.storedState(\.storedValue).value) 55 | 56 | let storedValue = ExampleStoredValue() 57 | 58 | XCTAssertEqual(storedValue.count, nil) 59 | 60 | storedValue.count = 1 61 | 62 | XCTAssertEqual(storedValue.count, 1) 63 | 64 | Application.dependency(\.logger).debug("StoredStateTests \(Application.description)") 65 | 66 | storedValue.count = nil 67 | 68 | XCTAssertNil(Application.storedState(\.storedValue).value) 69 | } 70 | 71 | @MainActor 72 | func testStoringViewModel() async { 73 | XCTAssertNil(Application.storedState(\.storedValue).value) 74 | 75 | let viewModel = ExampleStoringViewModel() 76 | 77 | XCTAssertEqual(viewModel.count, nil) 78 | 79 | viewModel.testPropertyWrapper() 80 | 81 | XCTAssertEqual(viewModel.count, 27) 82 | 83 | Application.reset(storedState: \.storedValue) 84 | 85 | XCTAssertNil(viewModel.count) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Tests/AppStateTests/SyncStateTests.swift: -------------------------------------------------------------------------------- 1 | #if !os(Linux) && !os(Windows) 2 | import SwiftUI 3 | import XCTest 4 | @testable import AppState 5 | 6 | @available(watchOS 9.0, *) 7 | fileprivate extension Application { 8 | var syncValue: SyncState { 9 | syncState(id: "syncValue") 10 | } 11 | 12 | var syncFailureValue: SyncState { 13 | syncState(initial: -1, id: "syncValue") 14 | } 15 | } 16 | 17 | @available(watchOS 9.0, *) 18 | @MainActor 19 | fileprivate struct ExampleSyncValue { 20 | @SyncState(\.syncValue) var count 21 | } 22 | 23 | 24 | @available(watchOS 9.0, *) 25 | @MainActor 26 | fileprivate struct ExampleFailureSyncValue { 27 | @SyncState(\.syncFailureValue) var count 28 | } 29 | 30 | 31 | @available(watchOS 9.0, *) 32 | @MainActor 33 | fileprivate class ExampleStoringViewModel: ObservableObject { 34 | @SyncState(\.syncValue) var count 35 | 36 | func testPropertyWrapper() { 37 | count = 27 38 | _ = TextField( 39 | value: $count, 40 | format: .number, 41 | label: { Text("Count") } 42 | ) 43 | } 44 | } 45 | 46 | 47 | @available(watchOS 9.0, *) 48 | final class SyncStateTests: XCTestCase { 49 | @MainActor 50 | override func setUp() async throws { 51 | Application 52 | .logging(isEnabled: true) 53 | .load(dependency: \.icloudStore) 54 | } 55 | 56 | @MainActor 57 | override func tearDown() async throws { 58 | let applicationDescription = Application.description 59 | 60 | Application.dependency(\.logger).debug("SyncStateTests \(applicationDescription)") 61 | } 62 | 63 | @MainActor 64 | func testSyncState() async { 65 | XCTAssertNil(Application.syncState(\.syncValue).value) 66 | 67 | let syncValue = ExampleSyncValue() 68 | 69 | XCTAssertEqual(syncValue.count, nil) 70 | 71 | syncValue.count = 1 72 | 73 | XCTAssertEqual(syncValue.count, 1) 74 | 75 | Application.dependency(\.logger).debug("SyncStateTests \(Application.description)") 76 | 77 | syncValue.count = nil 78 | 79 | XCTAssertNil(Application.syncState(\.syncValue).value) 80 | } 81 | 82 | @MainActor 83 | func testFailEncodingSyncState() async{ 84 | XCTAssertNotNil(Application.syncState(\.syncFailureValue).value) 85 | 86 | let syncValue = ExampleFailureSyncValue() 87 | 88 | XCTAssertEqual(syncValue.count, -1) 89 | 90 | syncValue.count = Double.infinity 91 | 92 | XCTAssertEqual(syncValue.count, Double.infinity) 93 | } 94 | 95 | @MainActor 96 | func testStoringViewModel() async { 97 | XCTAssertNil(Application.syncState(\.syncValue).value) 98 | 99 | let viewModel = ExampleStoringViewModel() 100 | 101 | XCTAssertEqual(viewModel.count, nil) 102 | 103 | viewModel.testPropertyWrapper() 104 | 105 | XCTAssertEqual(viewModel.count, 27) 106 | 107 | Application.reset(syncState: \.syncValue) 108 | 109 | XCTAssertNil(viewModel.count) 110 | } 111 | } 112 | #endif 113 | -------------------------------------------------------------------------------- /documentation/de/contributing.md: -------------------------------------------------------------------------------- 1 | # Zu AppState beitragen 2 | 3 | Vielen Dank, dass Sie in Betracht ziehen, zu **AppState** beizutragen! Ihre Beiträge helfen, dieses Projekt für alle besser zu machen. 4 | 5 | ## Wie man beitragen kann 6 | 7 | ### 1. Fehler melden 8 | 9 | Wenn Sie auf Fehler stoßen, öffnen Sie bitte ein Issue auf GitHub. Wenn Sie einen Fehler melden, geben Sie bitte Folgendes an: 10 | 11 | - Einen klaren und beschreibenden Titel. 12 | - Eine detaillierte Beschreibung des Fehlers, einschließlich der Schritte, um ihn zu reproduzieren. 13 | - Das erwartete Verhalten und was tatsächlich passiert ist. 14 | - Die Version von **AppState**, die Sie verwenden. 15 | - Alle relevanten Screenshots oder Protokolle. 16 | 17 | ### 2. Funktionen vorschlagen 18 | 19 | Neue Ideen sind willkommen! Wenn Sie eine Funktion haben, die Sie gerne in **AppState** sehen würden, öffnen Sie bitte ein Issue und beschreiben Sie: 20 | 21 | - Das Problem, das die Funktion lösen würde. 22 | - Wie Sie denken, dass die Funktion funktionieren sollte. 23 | - Jeder zusätzliche Kontext oder Beispiele, die helfen würden, Ihre Idee zu veranschaulichen. 24 | 25 | ### 3. Pull Requests einreichen 26 | 27 | Wenn Sie Code zu **AppState** beitragen möchten, befolgen Sie diese Schritte: 28 | 29 | 1. **Das Repository forken**: Erstellen Sie einen persönlichen Fork des **AppState**-Repositorys auf GitHub. 30 | 2. **Ihren Fork klonen**: Klonen Sie Ihren Fork auf Ihren lokalen Computer: 31 | ```bash 32 | git clone https://github.com/your-username/AppState.git 33 | ``` 34 | 3. **Einen neuen Branch erstellen**: Erstellen Sie einen neuen Branch für Ihre Funktion oder Fehlerbehebung: 35 | ```bash 36 | git checkout -b mein-feature-branch 37 | ``` 38 | 4. **Änderungen vornehmen**: Implementieren Sie Ihre Änderungen im neuen Branch. 39 | 5. **Ihre Änderungen testen**: Stellen Sie sicher, dass Ihre Änderungen alle Tests bestehen. Fügen Sie bei Bedarf neue Tests hinzu. 40 | 6. **Ihre Änderungen committen**: Committen Sie Ihre Änderungen mit einer beschreibenden Commit-Nachricht: 41 | ```bash 42 | git commit -m "Meine neue Funktion hinzufügen" 43 | ``` 44 | 7. **Auf GitHub pushen**: Pushen Sie Ihren Branch zu Ihrem GitHub-Fork: 45 | ```bash 46 | git push origin mein-feature-branch 47 | ``` 48 | 8. **Einen Pull-Request erstellen**: Gehen Sie zum **AppState**-Repository auf GitHub und erstellen Sie einen Pull-Request aus Ihrem Branch. 49 | 50 | ### 4. Code-Stil 51 | 52 | Bitte befolgen Sie die im **AppState**-Projekt verwendeten Richtlinien für den Codierungsstil. Ein einheitlicher Code-Stil trägt dazu bei, die Codebasis wartbarer und leichter überprüfbar zu machen. 53 | 54 | ### 5. Lizenz 55 | 56 | Indem Sie zu **AppState** beitragen, stimmen Sie zu, dass Ihre Beiträge unter derselben Lizenz wie das Projekt lizenziert werden: [LIZENZ](https://github.com/0xLeif/AppState/blob/main/LICENSE). 57 | 58 | ## Vielen Dank! 59 | 60 | Ihre Beiträge werden sehr geschätzt und gewürdigt. Vielen Dank, dass Sie zur Verbesserung von **AppState** beitragen! 61 | 62 | --- 63 | Diese Übersetzung wurde automatisch generiert und kann Fehler enthalten. Wenn Sie Muttersprachler sind, freuen wir uns über Ihre Korrekturvorschläge per Pull Request. 64 | -------------------------------------------------------------------------------- /documentation/de/faq.md: -------------------------------------------------------------------------------- 1 | # Häufig gestellte Fragen 2 | 3 | Diese kurze FAQ beantwortet häufig gestellte Fragen, die Entwickler bei der Verwendung von **AppState** haben können. 4 | 5 | ## Wie setze ich einen Zustandswert zurück? 6 | 7 | Für persistente Zustände wie `StoredState`, `FileState` und `SyncState` können Sie sie auf ihre Anfangswerte zurücksetzen, indem Sie die statischen `reset`-Funktionen für den `Application`-Typ verwenden. 8 | 9 | Um beispielsweise einen `StoredState` zurückzusetzen: 10 | ```swift 11 | extension Application { 12 | var hasCompletedOnboarding: StoredState { storedState(initial: false, id: "onboarding_complete") } 13 | } 14 | 15 | // Irgendwo in Ihrem Code 16 | Application.reset(storedState: \.hasCompletedOnboarding) 17 | ``` 18 | Dadurch wird der Wert in `UserDefaults` auf `false` zurückgesetzt. Ähnliche `reset`-Funktionen gibt es für `FileState`, `SyncState` und `SecureState`. 19 | 20 | Für einen nicht-persistenten `State` können Sie ihn auf die gleiche Weise wie persistente Zustände zurücksetzen: 21 | ```swift 22 | extension Application { 23 | var counter: State { state(initial: 0) } 24 | } 25 | 26 | // Irgendwo in Ihrem Code 27 | Application.reset(\.counter) 28 | ``` 29 | 30 | ## Kann ich AppState mit asynchronen Aufgaben verwenden? 31 | 32 | Ja. `State`- und Abhängigkeitswerte sind threadsicher und funktionieren nahtlos mit Swift Concurrency. Sie können sie innerhalb von `async`-Funktionen ohne zusätzliche Sperren aufrufen und ändern. 33 | 34 | ## Wo sollte ich Zustände und Abhängigkeiten definieren? 35 | 36 | Bewahren Sie alle Ihre Zustände und Abhängigkeiten in `Application`-Erweiterungen auf. Dies gewährleistet eine einzige Quelle der Wahrheit und erleichtert das Auffinden aller verfügbaren Werte. 37 | 38 | ## Ist AppState mit Combine kompatibel? 39 | 40 | Sie können AppState zusammen mit Combine verwenden, indem Sie `State`-Änderungen an Publisher überbrücken. Beobachten Sie einen `State`-Wert und senden Sie bei Bedarf Aktualisierungen über einen `PassthroughSubject` oder einen anderen Combine-Publisher. 41 | 42 | --- 43 | Diese Übersetzung wurde automatisch generiert und kann Fehler enthalten. Wenn Sie Muttersprachler sind, freuen wir uns über Ihre Korrekturvorschläge per Pull Request. 44 | -------------------------------------------------------------------------------- /documentation/de/installation.md: -------------------------------------------------------------------------------- 1 | # Installationsanleitung 2 | 3 | Diese Anleitung führt Sie durch den Prozess der Installation von **AppState** in Ihr Swift-Projekt mit dem Swift Package Manager. 4 | 5 | ## Swift Package Manager 6 | 7 | **AppState** kann einfach mit dem Swift Package Manager in Ihr Projekt integriert werden. Befolgen Sie die folgenden Schritte, um **AppState** als Abhängigkeit hinzuzufügen. 8 | 9 | ### Schritt 1: Aktualisieren Sie Ihre `Package.swift`-Datei 10 | 11 | Fügen Sie **AppState** zum `dependencies`-Abschnitt Ihrer `Package.swift`-Datei hinzu: 12 | 13 | ```swift 14 | dependencies: [ 15 | .package(url: "https://github.com/0xLeif/AppState.git", from: "2.2.0") 16 | ] 17 | ``` 18 | 19 | ### Schritt 2: Fügen Sie AppState zu Ihrem Ziel hinzu 20 | 21 | Schließen Sie AppState in die Abhängigkeiten Ihres Ziels ein: 22 | 23 | ```swift 24 | .target( 25 | name: "YourTarget", 26 | dependencies: ["AppState"] 27 | ) 28 | ``` 29 | 30 | ### Schritt 3: Erstellen Sie Ihr Projekt 31 | 32 | Nachdem Sie AppState zu Ihrer `Package.swift`-Datei hinzugefügt haben, erstellen Sie Ihr Projekt, um die Abhängigkeit abzurufen und in Ihre Codebasis zu integrieren. 33 | 34 | ``` 35 | swift build 36 | ``` 37 | 38 | ### Schritt 4: Importieren Sie AppState in Ihren Code 39 | 40 | Jetzt können Sie AppState in Ihrem Projekt verwenden, indem Sie es am Anfang Ihrer Swift-Dateien importieren: 41 | 42 | ```swift 43 | import AppState 44 | ``` 45 | 46 | ## Xcode 47 | 48 | Wenn Sie **AppState** lieber direkt über Xcode hinzufügen möchten, befolgen Sie diese Schritte: 49 | 50 | ### Schritt 1: Öffnen Sie Ihr Xcode-Projekt 51 | 52 | Öffnen Sie Ihr Xcode-Projekt oder Ihren Arbeitsbereich. 53 | 54 | ### Schritt 2: Fügen Sie eine Swift-Paketabhängigkeit hinzu 55 | 56 | 1. Navigieren Sie zum Projektnavigator und wählen Sie Ihre Projektdatei aus. 57 | 2. Wählen Sie im Projekteditor Ihr Ziel aus und gehen Sie dann zum Tab "Swift Packages". 58 | 3. Klicken Sie auf die Schaltfläche "+", um eine Paketabhängigkeit hinzuzufügen. 59 | 60 | ### Schritt 3: Geben Sie die Repository-URL ein 61 | 62 | Geben Sie im Dialogfeld "Choose Package Repository" die folgende URL ein: `https://github.com/0xLeif/AppState.git` 63 | 64 | Klicken Sie dann auf "Weiter". 65 | 66 | ### Schritt 4: Geben Sie die Version an 67 | 68 | Wählen Sie die Version aus, die Sie verwenden möchten. Es wird empfohlen, die Option "Bis zur nächsten Hauptversion" auszuwählen und `2.0.0` als Untergrenze anzugeben. Klicken Sie dann auf "Weiter". 69 | 70 | ### Schritt 5: Fügen Sie das Paket hinzu 71 | 72 | Xcode ruft das Paket ab und bietet Ihnen Optionen zum Hinzufügen von **AppState** zu Ihrem Ziel an. Stellen Sie sicher, dass Sie das richtige Ziel auswählen, und klicken Sie auf "Fertig stellen". 73 | 74 | ### Schritt 6: Importieren Sie `AppState` in Ihren Code 75 | 76 | Sie können **AppState** jetzt am Anfang Ihrer Swift-Dateien importieren: 77 | 78 | ```swift 79 | import AppState 80 | ``` 81 | 82 | ## Nächste Schritte 83 | 84 | Nach der Installation von AppState können Sie zur [Verwendungsübersicht](usage-overview.md) übergehen, um zu sehen, wie Sie die wichtigsten Funktionen in Ihrem Projekt implementieren. 85 | 86 | --- 87 | Diese Übersetzung wurde automatisch generiert und kann Fehler enthalten. Wenn Sie Muttersprachler sind, freuen wir uns über Ihre Korrekturvorschläge per Pull Request. 88 | -------------------------------------------------------------------------------- /documentation/de/starting-to-use-syncstate.md: -------------------------------------------------------------------------------- 1 | Um SyncState zu nutzen, müssen Sie zuerst die iCloud-Fähigkeiten und -Berechtigungen in Ihrem Xcode-Projekt einrichten. Hier ist eine Einführung, die Sie durch den Prozess führt: 2 | 3 | ### Einrichten der iCloud-Fähigkeiten: 4 | 5 | 1. Öffnen Sie Ihr Xcode-Projekt und passen Sie die Bundle-Identifikatoren für die macOS- und iOS-Ziele an Ihre eigenen an. 6 | 2. Als Nächstes müssen Sie die iCloud-Fähigkeit zu Ihrem Projekt hinzufügen. Wählen Sie dazu Ihr Projekt im Projektnavigator aus und wählen Sie dann Ihr Ziel aus. Klicken Sie in der Tab-Leiste oben im Editorbereich auf "Signing & Capabilities". 7 | 3. Aktivieren Sie im Bereich "Capabilities" iCloud, indem Sie auf den Schalter in der iCloud-Zeile klicken. Sie sollten sehen, wie sich der Schalter in die Ein-Position bewegt. 8 | 4. Sobald Sie iCloud aktiviert haben, müssen Sie den Schlüssel-Wert-Speicher aktivieren. Dies können Sie tun, indem Sie das Kontrollkästchen "Key-Value storage" aktivieren. 9 | 10 | ### Aktualisieren der Berechtigungen: 11 | 12 | 1. Sie müssen nun Ihre Berechtigungsdatei aktualisieren. Öffnen Sie die Berechtigungsdatei für Ihr Ziel. 13 | 2. Stellen Sie sicher, dass der Wert des iCloud-Schlüssel-Wert-Speichers mit Ihrer eindeutigen Schlüssel-Wert-Speicher-ID übereinstimmt. Ihre eindeutige ID sollte dem Format `$(TeamIdentifierPrefix)` folgen. Der Standardwert sollte etwa `$(TeamIdentifierPrefix)$(CFBundleIdentifier)` lauten. Dies ist für Einzelplattform-Apps in Ordnung, aber wenn Ihre App auf mehreren Apple-Betriebssystemen läuft, ist es wichtig, dass die Teile der Schlüssel-Wert-Speicher-ID für beide Ziele gleich sind. 14 | 15 | ### Konfigurieren der Geräte: 16 | 17 | Zusätzlich zur Konfiguration des Projekts selbst müssen Sie auch die Geräte vorbereiten, auf denen das Projekt ausgeführt wird. 18 | 19 | - Stellen Sie sicher, dass iCloud Drive sowohl auf iOS- als auch auf macOS-Geräten aktiviert ist. 20 | - Melden Sie sich auf beiden Geräten mit demselben iCloud-Konto an. 21 | 22 | Wenn Sie Fragen haben oder auf Probleme stoßen, können Sie sich gerne an uns wenden oder ein Problem melden. 23 | 24 | --- 25 | Diese Übersetzung wurde automatisch generiert und kann Fehler enthalten. Wenn Sie Muttersprachler sind, freuen wir uns über Ihre Korrekturvorschläge per Pull Request. 26 | -------------------------------------------------------------------------------- /documentation/de/usage-constant.md: -------------------------------------------------------------------------------- 1 | # Verwendung von Konstanten 2 | 3 | `Constant` in der **AppState**-Bibliothek bietet schreibgeschützten Zugriff auf Werte im Zustand Ihrer Anwendung. Es funktioniert ähnlich wie `Slice`, stellt jedoch sicher, dass die zugegriffenen Werte unveränderlich sind. Dies macht `Constant` ideal für den Zugriff auf Werte, die andernfalls veränderbar sein könnten, aber in bestimmten Kontexten schreibgeschützt bleiben sollen. 4 | 5 | ## Hauptmerkmale 6 | 7 | - **Schreibgeschützter Zugriff**: Konstanten bieten Zugriff auf veränderbare Zustände, aber die Werte können nicht geändert werden. 8 | - **Auf die Anwendung beschränkt**: Wie `Slice` wird `Constant` innerhalb der `Application`-Erweiterung definiert und ist auf den Zugriff auf bestimmte Teile des Zustands beschränkt. 9 | - **Threadsicher**: `Constant` gewährleistet einen sicheren Zugriff auf den Zustand in nebenläufigen Umgebungen. 10 | 11 | ## Anwendungsbeispiel 12 | 13 | ### Definieren einer Konstante in der Anwendung 14 | 15 | So definieren Sie eine `Constant` in der `Application`-Erweiterung, um auf einen schreibgeschützten Wert zuzugreifen: 16 | 17 | ```swift 18 | import AppState 19 | import SwiftUI 20 | 21 | struct ExampleValue { 22 | var username: String? 23 | var isLoading: Bool 24 | let value: String 25 | var mutableValue: String 26 | } 27 | 28 | extension Application { 29 | var exampleValue: State { 30 | state( 31 | initial: ExampleValue( 32 | username: "Leif", 33 | isLoading: false, 34 | value: "value", 35 | mutableValue: "" 36 | ) 37 | ) 38 | } 39 | } 40 | ``` 41 | 42 | ### Zugriff auf die Konstante in einer SwiftUI-Ansicht 43 | 44 | In einer SwiftUI-Ansicht können Sie den `@Constant`-Property-Wrapper verwenden, um auf den konstanten Zustand schreibgeschützt zuzugreifen: 45 | 46 | ```swift 47 | import AppState 48 | import SwiftUI 49 | 50 | struct ExampleView: View { 51 | @Constant(\.exampleValue, \.value) var constantValue: String 52 | 53 | var body: some View { 54 | Text("Konstanter Wert: \(constantValue)") 55 | } 56 | } 57 | ``` 58 | 59 | ### Schreibgeschützter Zugriff auf veränderbaren Zustand 60 | 61 | Auch wenn der Wert an anderer Stelle veränderbar ist, wird der Wert bei Zugriff über `@Constant` unveränderlich: 62 | 63 | ```swift 64 | import AppState 65 | import SwiftUI 66 | 67 | struct ExampleView: View { 68 | @Constant(\.exampleValue, \.mutableValue) var constantMutableValue: String 69 | 70 | var body: some View { 71 | Text("Schreibgeschützter veränderbarer Wert: \(constantMutableValue)") 72 | } 73 | } 74 | ``` 75 | 76 | ## Bewährte Praktiken 77 | 78 | - **Verwendung für schreibgeschützten Zugriff**: Verwenden Sie `Constant`, um auf Teile des Zustands zuzugreifen, die in bestimmten Kontexten nicht geändert werden sollen, auch wenn sie an anderer Stelle veränderbar sind. 79 | - **Threadsicher**: Wie andere AppState-Komponenten gewährleistet `Constant` einen threadsicheren Zugriff auf den Zustand. 80 | - **Verwenden Sie `OptionalConstant` für optionale Werte**: Wenn der Teil des Zustands, auf den Sie zugreifen, `nil` sein kann, verwenden Sie `OptionalConstant`, um das Fehlen eines Werts sicher zu behandeln. 81 | 82 | ## Fazit 83 | 84 | `Constant` und `OptionalConstant` bieten eine effiziente Möglichkeit, auf bestimmte Teile des Zustands Ihrer App schreibgeschützt zuzugreifen. Sie stellen sicher, dass Werte, die andernfalls veränderbar sein könnten, bei Zugriff innerhalb einer Ansicht als unveränderlich behandelt werden, was Sicherheit und Klarheit in Ihrem Code gewährleistet. 85 | 86 | --- 87 | Diese Übersetzung wurde automatisch generiert und kann Fehler enthalten. Wenn Sie Muttersprachler sind, freuen wir uns über Ihre Korrekturvorschläge per Pull Request. 88 | -------------------------------------------------------------------------------- /documentation/de/usage-securestate.md: -------------------------------------------------------------------------------- 1 | # Verwendung von SecureState 2 | 3 | `SecureState` ist eine Komponente der **AppState**-Bibliothek, mit der Sie sensible Daten sicher im Schlüsselbund speichern können. Es eignet sich am besten zum Speichern kleiner Datenmengen wie Token oder Passwörter, die sicher verschlüsselt werden müssen. 4 | 5 | ## Hauptmerkmale 6 | 7 | - **Sichere Speicherung**: Mit `SecureState` gespeicherte Daten werden verschlüsselt und sicher im Schlüsselbund gespeichert. 8 | - **Persistenz**: Die Daten bleiben über App-Starts hinweg erhalten, was eine sichere Wiederherstellung sensibler Werte ermöglicht. 9 | 10 | ## Einschränkungen des Schlüsselbunds 11 | 12 | Obwohl `SecureState` sehr sicher ist, gibt es bestimmte Einschränkungen: 13 | 14 | - **Begrenzte Speichergröße**: Der Schlüsselbund ist für kleine Datenmengen ausgelegt. Er ist nicht zum Speichern großer Dateien oder Datensätze geeignet. 15 | - **Leistung**: Der Zugriff auf den Schlüsselbund ist langsamer als der Zugriff auf `UserDefaults`. Verwenden Sie ihn daher nur, wenn es erforderlich ist, sensible Daten sicher zu speichern. 16 | 17 | ## Anwendungsbeispiel 18 | 19 | ### Speichern eines sicheren Tokens 20 | 21 | ```swift 22 | import AppState 23 | import SwiftUI 24 | 25 | extension Application { 26 | var userToken: SecureState { 27 | secureState(id: "userToken") 28 | } 29 | } 30 | 31 | struct SecureView: View { 32 | @SecureState(\.userToken) var userToken: String? 33 | 34 | var body: some View { 35 | VStack { 36 | if let token = userToken { 37 | Text("Benutzertoken: \(token)") 38 | } else { 39 | Text("Kein Token gefunden.") 40 | } 41 | Button("Token festlegen") { 42 | userToken = "secure_token_value" 43 | } 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | ### Umgang mit dem Fehlen sicherer Daten 50 | 51 | Beim ersten Zugriff auf den Schlüsselbund oder wenn kein Wert gespeichert ist, gibt `SecureState` `nil` zurück. Stellen Sie sicher, dass Sie dieses Szenario ordnungsgemäß behandeln: 52 | 53 | ```swift 54 | if let token = userToken { 55 | print("Token: \(token)") 56 | } else { 57 | print("Kein Token verfügbar.") 58 | } 59 | ``` 60 | 61 | ## Bewährte Praktiken 62 | 63 | - **Verwendung für kleine Daten**: Der Schlüsselbund sollte zum Speichern kleiner sensibler Informationen wie Token, Passwörter und Schlüssel verwendet werden. 64 | - **Vermeiden Sie große Datensätze**: Wenn Sie große Datensätze sicher speichern müssen, sollten Sie eine dateibasierte Verschlüsselung oder andere Methoden in Betracht ziehen, da der Schlüsselbund nicht für die Speicherung großer Datenmengen ausgelegt ist. 65 | - **Behandeln Sie nil**: Behandeln Sie immer Fälle, in denen der Schlüsselbund `nil` zurückgibt, wenn kein Wert vorhanden ist. 66 | 67 | --- 68 | Diese Übersetzung wurde automatisch generiert und kann Fehler enthalten. Wenn Sie Muttersprachler sind, freuen wir uns über Ihre Korrekturvorschläge per Pull Request. 69 | -------------------------------------------------------------------------------- /documentation/en/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to AppState 2 | 3 | Thank you for considering contributing to **AppState**! Your contributions help make this project better for everyone. 4 | 5 | ## How to Contribute 6 | 7 | ### 1. Reporting Bugs 8 | 9 | If you encounter any bugs, please open an issue on GitHub. When reporting a bug, please include: 10 | 11 | - A clear and descriptive title. 12 | - A detailed description of the bug, including steps to reproduce it. 13 | - The expected behavior and what actually happened. 14 | - The version of **AppState** you are using. 15 | - Any relevant screenshots or logs. 16 | 17 | ### 2. Suggesting Features 18 | 19 | New ideas are welcome! If you have a feature you'd like to see added to **AppState**, please open an issue and describe: 20 | 21 | - The problem the feature would solve. 22 | - How you think the feature should work. 23 | - Any additional context or examples that would help illustrate your idea. 24 | 25 | ### 3. Submitting Pull Requests 26 | 27 | If you'd like to contribute code to **AppState**, follow these steps: 28 | 29 | 1. **Fork the Repository**: Create a personal fork of the **AppState** repository on GitHub. 30 | 2. **Clone Your Fork**: Clone your fork to your local machine: 31 | ```bash 32 | git clone https://github.com/your-username/AppState.git 33 | ``` 34 | 3. **Create a New Branch**: Create a new branch for your feature or bugfix: 35 | ```bash 36 | git checkout -b my-feature-branch 37 | ``` 38 | 4. **Make Changes**: Implement your changes in the new branch. 39 | 5. **Test Your Changes**: Ensure your changes pass all tests. Add new tests if necessary. 40 | 6. **Commit Your Changes**: Commit your changes with a descriptive commit message: 41 | ```bash 42 | git commit -m "Add my new feature" 43 | ``` 44 | 7. **Push to GitHub**: Push your branch to your GitHub fork: 45 | ```bash 46 | git push origin my-feature-branch 47 | ``` 48 | 8. **Create a Pull Request**: Go to the **AppState** repository on GitHub and create a pull request from your branch. 49 | 50 | ### 4. Code Style 51 | 52 | Please follow the coding style guidelines used in the **AppState** project. Consistent code style helps make the codebase more maintainable and easier to review. 53 | 54 | ### 5. License 55 | 56 | By contributing to **AppState**, you agree that your contributions will be licensed under the same license as the project: [LICENSE](https://github.com/0xLeif/AppState/blob/main/LICENSE). 57 | 58 | ## Thank You! 59 | 60 | Your contributions are highly valued and appreciated. Thank you for helping improve **AppState**! 61 | -------------------------------------------------------------------------------- /documentation/en/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | This short FAQ addresses common questions developers may have when using **AppState**. 4 | 5 | ## How do I reset a state value? 6 | 7 | For persistent states like `StoredState`, `FileState`, and `SyncState`, you can reset them to their initial values using the static `reset` functions on the `Application` type. 8 | 9 | For example, to reset a `StoredState`: 10 | ```swift 11 | extension Application { 12 | var hasCompletedOnboarding: StoredState { storedState(initial: false, id: "onboarding_complete") } 13 | } 14 | 15 | // Somewhere in your code 16 | Application.reset(storedState: \.hasCompletedOnboarding) 17 | ``` 18 | This will reset the value in `UserDefaults` back to `false`. Similar `reset` functions exist for `FileState`, `SyncState`, and `SecureState`. 19 | 20 | For non-persistent `State`, you can reset it the same way as persistent states: 21 | ```swift 22 | extension Application { 23 | var counter: State { state(initial: 0) } 24 | } 25 | 26 | // Somewhere in your code 27 | Application.reset(\.counter) 28 | ``` 29 | 30 | ## Can I use AppState with asynchronous tasks? 31 | 32 | Yes. `State` and dependency values are thread-safe and work seamlessly with Swift Concurrency. You can access and modify them inside `async` functions without additional locking. 33 | 34 | ## Where should I define states and dependencies? 35 | 36 | Keep all your states and dependencies in `Application` extensions. This ensures a single source of truth and makes it easier to discover all available values. 37 | 38 | ## Is AppState compatible with Combine? 39 | 40 | You can use AppState alongside Combine by bridging `State` changes to publishers. Observe a `State` value and send updates through a `PassthroughSubject` or other Combine publisher if needed. 41 | 42 | -------------------------------------------------------------------------------- /documentation/en/installation.md: -------------------------------------------------------------------------------- 1 | # Installation Guide 2 | 3 | This guide will walk you through the process of installing **AppState** into your Swift project using Swift Package Manager. 4 | 5 | ## Swift Package Manager 6 | 7 | **AppState** can be easily integrated into your project using Swift Package Manager. Follow the steps below to add **AppState** as a dependency. 8 | 9 | ### Step 1: Update Your `Package.swift` File 10 | 11 | Add **AppState** to the `dependencies` section of your `Package.swift` file: 12 | 13 | ```swift 14 | dependencies: [ 15 | .package(url: "https://github.com/0xLeif/AppState.git", from: "2.2.0") 16 | ] 17 | ``` 18 | 19 | ### Step 2: Add AppState to Your Target 20 | 21 | Include AppState in your target’s dependencies: 22 | 23 | ```swift 24 | .target( 25 | name: "YourTarget", 26 | dependencies: ["AppState"] 27 | ) 28 | ``` 29 | 30 | ### Step 3: Build Your Project 31 | 32 | Once you’ve added AppState to your `Package.swift` file, build your project to fetch the dependency and integrate it into your codebase. 33 | 34 | ``` 35 | swift build 36 | ``` 37 | 38 | ### Step 4: Import AppState in Your Code 39 | 40 | Now, you can start using AppState in your project by importing it at the top of your Swift files: 41 | 42 | ```swift 43 | import AppState 44 | ``` 45 | 46 | ## Xcode 47 | 48 | If you prefer to add **AppState** directly through Xcode, follow these steps: 49 | 50 | ### Step 1: Open Your Xcode Project 51 | 52 | Open your Xcode project or workspace. 53 | 54 | ### Step 2: Add a Swift Package Dependency 55 | 56 | 1. Navigate to the project navigator and select your project file. 57 | 2. In the project editor, select your target, and then go to the "Swift Packages" tab. 58 | 3. Click the "+" button to add a package dependency. 59 | 60 | ### Step 3: Enter the Repository URL 61 | 62 | In the "Choose Package Repository" dialog, enter the following URL: `https://github.com/0xLeif/AppState.git` 63 | 64 | Then click "Next." 65 | 66 | ### Step 4: Specify the Version 67 | 68 | Choose the version you wish to use. It's recommended to select the "Up to Next Major Version" option and specify `2.0.0` as the lower bound. Then click "Next." 69 | 70 | ### Step 5: Add the Package 71 | 72 | Xcode will fetch the package and present you with options to add **AppState** to your target. Make sure to select the correct target and click "Finish." 73 | 74 | ### Step 6: Import `AppState` in Your Code 75 | 76 | You can now import **AppState** at the top of your Swift files: 77 | 78 | ```swift 79 | import AppState 80 | ``` 81 | 82 | ## Next Steps 83 | 84 | With AppState installed, you can move on to the [Usage Overview](usage-overview.md) to see how to implement the key features in your project. 85 | -------------------------------------------------------------------------------- /documentation/en/starting-to-use-syncstate.md: -------------------------------------------------------------------------------- 1 | To utilize SyncState, you will first need to set up iCloud capabilities and entitlements in your Xcode project. Here's an introduction to guide you through the process: 2 | 3 | ### Setting Up iCloud Capabilities: 4 | 5 | 1. Open your Xcode project and adjust the Bundle Identifiers for both macOS and iOS targets to match your own. 6 | 2. Next, you need to add the iCloud capability to your project. To do this, select your project in the Project Navigator, then select your target. In the tab bar at the top of the editor area, click on "Signing & Capabilities". 7 | 3. In the Capabilities pane, turn on iCloud by clicking the switch in the iCloud row. You should see the switch move to the On position. 8 | 4. Once you have enabled iCloud, you need to enable Key-Value storage. You can do this by checking the "Key-Value storage" checkbox. 9 | 10 | ### Updating the Entitlements: 11 | 12 | 1. You will now need to update your entitlements file. Open the entitlements file for your target. 13 | 2. Make sure the iCloud Key-Value Store value matches your unique key-value store ID. Your unique ID should follow the format `$(TeamIdentifierPrefix)`. The default value should be something like, `$(TeamIdentifierPrefix)$(CFBundleIdentifier)`. This is fine for single platform apps, but if your app is on multiple Apple OSs, it’s important that the key-value store ID portions are the same for both targets. 14 | 15 | ### Configuring the Devices: 16 | 17 | In addition to configuring the project itself, you also need to prepare the devices that will run the project. 18 | 19 | - Ensure that iCloud Drive is enabled on both iOS and macOS devices. 20 | - Log into both devices using the same iCloud account. 21 | 22 | If you have any questions or run into any issues, feel free to reach out or submit an issue. 23 | -------------------------------------------------------------------------------- /documentation/en/syncstate-implementation.md: -------------------------------------------------------------------------------- 1 | # SyncState Implementation in AppState 2 | 3 | This guide covers how to set up and configure SyncState in your application, including setting up iCloud capabilities and understanding potential limitations. 4 | 5 | ## 1. Setting Up iCloud Capabilities 6 | 7 | To use SyncState in your application, you first need to enable iCloud in your project and configure Key-Value storage. 8 | 9 | ### Steps to Enable iCloud and Key-Value Storage: 10 | 11 | 1. Open your Xcode project and navigate to your project settings. 12 | 2. Under the "Signing & Capabilities" tab, select your target (iOS or macOS). 13 | 3. Click the "+ Capability" button and choose "iCloud" from the list. 14 | 4. Enable the "Key-Value storage" option under iCloud settings. This allows your app to store and sync small amounts of data using iCloud. 15 | 16 | ### Entitlements File Configuration: 17 | 18 | 1. In your Xcode project, find or create the **entitlements file** for your app. 19 | 2. Ensure that the iCloud Key-Value Store is correctly set up in the entitlements file with the correct iCloud container. 20 | 21 | Example in the entitlements file: 22 | 23 | ```xml 24 | com.apple.developer.ubiquity-kvstore-identifier 25 | $(TeamIdentifierPrefix)com.yourdomain.app 26 | ``` 27 | 28 | Make sure that the string value matches the iCloud container associated with your project. 29 | 30 | ## 2. Using SyncState in Your Application 31 | 32 | Once iCloud is enabled, you can use `SyncState` in your application to synchronize data across devices. 33 | 34 | ### Example of SyncState in Use: 35 | 36 | ```swift 37 | import AppState 38 | import SwiftUI 39 | 40 | extension Application { 41 | var syncValue: SyncState { 42 | syncState(id: "syncValue") 43 | } 44 | } 45 | 46 | struct ContentView: View { 47 | @SyncState(\.syncValue) private var syncValue: Int? 48 | 49 | var body: some View { 50 | VStack { 51 | if let syncValue = syncValue { 52 | Text("SyncValue: \(syncValue)") 53 | } else { 54 | Text("No SyncValue") 55 | } 56 | 57 | Button("Update SyncValue") { 58 | syncValue = Int.random(in: 0..<100) 59 | } 60 | } 61 | } 62 | } 63 | ``` 64 | 65 | In this example, the sync state will be saved to iCloud and synchronized across devices logged into the same iCloud account. 66 | 67 | ## 3. Limitations and Best Practices 68 | 69 | SyncState uses `NSUbiquitousKeyValueStore`, which has some limitations: 70 | 71 | - **Storage Limit**: SyncState is designed for small amounts of data. The total storage limit is 1 MB, and each key-value pair is limited to around 1 MB. 72 | - **Synchronization**: Changes made to the SyncState are not instantly synchronized across devices. There can be a slight delay in synchronization, and iCloud syncing may occasionally be affected by network conditions. 73 | 74 | ### Best Practices: 75 | 76 | - **Use SyncState for Small Data**: Ensure that only small data like user preferences or settings are synchronized using SyncState. 77 | - **Handle SyncState Failures Gracefully**: Use default values or error handling mechanisms to account for potential sync delays or failures. 78 | 79 | ## 4. Conclusion 80 | 81 | By properly configuring iCloud and understanding the limitations of SyncState, you can leverage its power to sync data across devices. Make sure you only use SyncState for small, critical pieces of data to avoid potential issues with iCloud storage limits. 82 | -------------------------------------------------------------------------------- /documentation/en/usage-constant.md: -------------------------------------------------------------------------------- 1 | # Constant Usage 2 | 3 | `Constant` in the **AppState** library provides read-only access to values within your application's state. It works similarly to `Slice`, but ensures that the accessed values are immutable. This makes `Constant` ideal for accessing values that may otherwise be mutable but should remain read-only in certain contexts. 4 | 5 | ## Key Features 6 | 7 | - **Read-Only Access**: Constants provide access to mutable state, but the values cannot be modified. 8 | - **Scoped to Application**: Like `Slice`, `Constant` is defined within the `Application` extension and scoped to access specific parts of the state. 9 | - **Thread-Safe**: `Constant` ensures safe access to state in concurrent environments. 10 | 11 | ## Example Usage 12 | 13 | ### Defining a Constant in Application 14 | 15 | Here’s how you define a `Constant` in the `Application` extension to access a read-only value: 16 | 17 | ```swift 18 | import AppState 19 | import SwiftUI 20 | 21 | struct ExampleValue { 22 | var username: String? 23 | var isLoading: Bool 24 | let value: String 25 | var mutableValue: String 26 | } 27 | 28 | extension Application { 29 | var exampleValue: State { 30 | state( 31 | initial: ExampleValue( 32 | username: "Leif", 33 | isLoading: false, 34 | value: "value", 35 | mutableValue: "" 36 | ) 37 | ) 38 | } 39 | } 40 | ``` 41 | 42 | ### Accessing the Constant in a SwiftUI View 43 | 44 | In a SwiftUI view, you can use the `@Constant` property wrapper to access the constant state in a read-only manner: 45 | 46 | ```swift 47 | import AppState 48 | import SwiftUI 49 | 50 | struct ExampleView: View { 51 | @Constant(\.exampleValue, \.value) var constantValue: String 52 | 53 | var body: some View { 54 | Text("Constant Value: \(constantValue)") 55 | } 56 | } 57 | ``` 58 | 59 | ### Read-Only Access to Mutable State 60 | 61 | Even if the value is mutable elsewhere, when accessed through `@Constant`, the value becomes immutable: 62 | 63 | ```swift 64 | import AppState 65 | import SwiftUI 66 | 67 | struct ExampleView: View { 68 | @Constant(\.exampleValue, \.mutableValue) var constantMutableValue: String 69 | 70 | var body: some View { 71 | Text("Read-Only Mutable Value: \(constantMutableValue)") 72 | } 73 | } 74 | ``` 75 | 76 | ## Best Practices 77 | 78 | - **Use for Read-Only Access**: Use `Constant` to access parts of the state that should not be modified within certain contexts, even if they are mutable elsewhere. 79 | - **Thread-Safe**: Like other AppState components, `Constant` ensures thread-safe access to state. 80 | - **Use `OptionalConstant` for Optional Values**: If the part of the state you're accessing may be `nil`, use `OptionalConstant` to safely handle the absence of a value. 81 | 82 | ## Conclusion 83 | 84 | `Constant` and `OptionalConstant` provide an efficient way to access specific parts of your app's state in a read-only manner. They ensure that values which may otherwise be mutable are treated as immutable when accessed within a view, ensuring safety and clarity in your code. 85 | -------------------------------------------------------------------------------- /documentation/en/usage-securestate.md: -------------------------------------------------------------------------------- 1 | # SecureState Usage 2 | 3 | `SecureState` is a component of the **AppState** library that allows you to store sensitive data securely in the Keychain. It's best suited for storing small pieces of data like tokens or passwords that need to be securely encrypted. 4 | 5 | ## Key Features 6 | 7 | - **Secure Storage**: Data stored using `SecureState` is encrypted and securely saved in the Keychain. 8 | - **Persistence**: The data remains persistent across app launches, allowing secure retrieval of sensitive values. 9 | 10 | ## Keychain Limitations 11 | 12 | While `SecureState` is very secure, it has certain limitations: 13 | 14 | - **Limited Storage Size**: Keychain is designed for small pieces of data. It is not suitable for storing large files or datasets. 15 | - **Performance**: Accessing the Keychain is slower than accessing `UserDefaults`, so use it only when necessary to securely store sensitive data. 16 | 17 | ## Example Usage 18 | 19 | ### Storing a Secure Token 20 | 21 | ```swift 22 | import AppState 23 | import SwiftUI 24 | 25 | extension Application { 26 | var userToken: SecureState { 27 | secureState(id: "userToken") 28 | } 29 | } 30 | 31 | struct SecureView: View { 32 | @SecureState(\.userToken) var userToken: String? 33 | 34 | var body: some View { 35 | VStack { 36 | if let token = userToken { 37 | Text("User token: \(token)") 38 | } else { 39 | Text("No token found.") 40 | } 41 | Button("Set Token") { 42 | userToken = "secure_token_value" 43 | } 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | ### Handling Absence of Secure Data 50 | 51 | When accessing the Keychain for the first time, or if there’s no value stored, `SecureState` will return `nil`. Ensure you handle this scenario properly: 52 | 53 | ```swift 54 | if let token = userToken { 55 | print("Token: \(token)") 56 | } else { 57 | print("No token available.") 58 | } 59 | ``` 60 | 61 | ## Best Practices 62 | 63 | - **Use for Small Data**: Keychain should be used for storing small pieces of sensitive information like tokens, passwords, and keys. 64 | - **Avoid Large Datasets**: If you need to store large datasets securely, consider using file-based encryption or other methods, as Keychain is not designed for large data storage. 65 | - **Handle nil**: Always handle cases where the Keychain returns `nil` when no value is present. 66 | -------------------------------------------------------------------------------- /documentation/en/usage-slice.md: -------------------------------------------------------------------------------- 1 | # Slice and OptionalSlice Usage 2 | 3 | `Slice` and `OptionalSlice` are components of the **AppState** library that allow you to access specific parts of your application’s state. They are useful when you need to manipulate or observe a part of a more complex state structure. 4 | 5 | ## Overview 6 | 7 | - **Slice**: Allows you to access and modify a specific part of an existing `State` object. 8 | - **OptionalSlice**: Works similarly to `Slice` but is designed to handle optional values, such as when part of your state may or may not be `nil`. 9 | 10 | ### Key Features 11 | 12 | - **Selective State Access**: Access only the part of the state that you need. 13 | - **Thread Safety**: Just like with other state management types in **AppState**, `Slice` and `OptionalSlice` are thread-safe. 14 | - **Reactiveness**: SwiftUI views update when the slice of the state changes, ensuring your UI remains reactive. 15 | 16 | ## Example Usage 17 | 18 | ### Using Slice 19 | 20 | In this example, we use `Slice` to access and update a specific part of the state—in this case, the `username` from a more complex `User` object stored in the app state. 21 | 22 | ```swift 23 | import AppState 24 | import SwiftUI 25 | 26 | struct User { 27 | var username: String 28 | var email: String 29 | } 30 | 31 | extension Application { 32 | var user: State { 33 | state(initial: User(username: "Guest", email: "guest@example.com")) 34 | } 35 | } 36 | 37 | struct SlicingView: View { 38 | @Slice(\.user, \.username) var username: String 39 | 40 | var body: some View { 41 | VStack { 42 | Text("Username: \(username)") 43 | Button("Update Username") { 44 | username = "NewUsername" 45 | } 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | ### Using OptionalSlice 52 | 53 | `OptionalSlice` is useful when part of your state may be `nil`. In this example, the `User` object itself may be `nil`, so we use `OptionalSlice` to safely handle this case. 54 | 55 | ```swift 56 | import AppState 57 | import SwiftUI 58 | 59 | extension Application { 60 | var user: State { 61 | state(initial: nil) 62 | } 63 | } 64 | 65 | struct OptionalSlicingView: View { 66 | @OptionalSlice(\.user, \.username) var username: String? 67 | 68 | var body: some View { 69 | VStack { 70 | if let username = username { 71 | Text("Username: \(username)") 72 | } else { 73 | Text("No username available") 74 | } 75 | Button("Set Username") { 76 | username = "UpdatedUsername" 77 | } 78 | } 79 | } 80 | } 81 | ``` 82 | 83 | ## Best Practices 84 | 85 | - **Use `Slice` for non-optional state**: If your state is guaranteed to be non-optional, use `Slice` to access and update it. 86 | - **Use `OptionalSlice` for optional state**: If your state or part of the state is optional, use `OptionalSlice` to handle cases where the value may be `nil`. 87 | - **Thread Safety**: Just like with `State`, `Slice` and `OptionalSlice` are thread-safe and designed to work with Swift’s concurrency model. 88 | 89 | ## Conclusion 90 | 91 | `Slice` and `OptionalSlice` provide powerful ways to access and modify specific parts of your state in a thread-safe manner. By leveraging these components, you can simplify state management in more complex applications, ensuring your UI stays reactive and up-to-date. 92 | -------------------------------------------------------------------------------- /documentation/en/usage-storedstate.md: -------------------------------------------------------------------------------- 1 | # StoredState Usage 2 | 3 | `StoredState` is a component of the **AppState** library that allows you to store and persist small amounts of data using `UserDefaults`. It is ideal for storing lightweight, non-sensitive data that should persist across app launches. 4 | 5 | ## Overview 6 | 7 | - **StoredState** is built on top of `UserDefaults`, which means it’s fast and efficient for storing small amounts of data (such as user preferences or app settings). 8 | - Data saved in **StoredState** persists across app sessions, allowing you to restore application state on launch. 9 | 10 | ### Key Features 11 | 12 | - **Persistent Storage**: Data saved in `StoredState` remains available between app launches. 13 | - **Small Data Handling**: Best used for lightweight data like preferences, toggles, or small configurations. 14 | - **Thread-Safe**: `StoredState` ensures that data access remains safe in concurrent environments. 15 | 16 | ## Example Usage 17 | 18 | ### Defining a StoredState 19 | 20 | You can define a **StoredState** by extending the `Application` object and declaring the state property: 21 | 22 | ```swift 23 | import AppState 24 | 25 | extension Application { 26 | var userPreferences: StoredState { 27 | storedState(initial: "Default Preferences", id: "userPreferences") 28 | } 29 | } 30 | ``` 31 | 32 | ### Accessing and Modifying StoredState in a View 33 | 34 | You can access and modify **StoredState** values within SwiftUI views using the `@StoredState` property wrapper: 35 | 36 | ```swift 37 | import AppState 38 | import SwiftUI 39 | 40 | struct PreferencesView: View { 41 | @StoredState(\.userPreferences) var userPreferences: String 42 | 43 | var body: some View { 44 | VStack { 45 | Text("Preferences: \(userPreferences)") 46 | Button("Update Preferences") { 47 | userPreferences = "Updated Preferences" 48 | } 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | ## Handling Data Migration 55 | 56 | As your app evolves, you may update the models that are persisted via **StoredState**. When updating your data model, ensure backward compatibility. For example, you might add new fields or version your model to handle migration. 57 | 58 | For more information, refer to the [Migration Considerations Guide](migration-considerations.md). 59 | 60 | ### Migration Considerations 61 | 62 | - **Adding New Non-Optional Fields**: Ensure new fields are either optional or have default values to maintain backward compatibility. 63 | - **Versioning Models**: If your data model changes over time, include a `version` field to manage different versions of your persisted data. 64 | 65 | ## Best Practices 66 | 67 | - **Use for Small Data**: Store lightweight, non-sensitive data that needs to persist across app launches, like user preferences. 68 | - **Consider Alternatives for Larger Data**: If you need to store large amounts of data, consider using **FileState** instead. 69 | 70 | ## Conclusion 71 | 72 | **StoredState** is a simple and efficient way to persist small pieces of data using `UserDefaults`. It is ideal for saving preferences and other small settings across app launches while providing safe access and easy integration with SwiftUI. For more complex persistence needs, explore other **AppState** features like [FileState](usage-filestate.md) or [SyncState](usage-syncstate.md). 73 | -------------------------------------------------------------------------------- /documentation/es/contributing.md: -------------------------------------------------------------------------------- 1 | # Contribuir a AppState 2 | 3 | ¡Gracias por considerar contribuir a **AppState**! Sus contribuciones ayudan a que este proyecto sea mejor para todos. 4 | 5 | ## Cómo Contribuir 6 | 7 | ### 1. Reportar Errores 8 | 9 | Si encuentra algún error, por favor abra un issue en GitHub. Al reportar un error, por favor incluya: 10 | 11 | - Un título claro y descriptivo. 12 | - Una descripción detallada del error, incluyendo los pasos para reproducirlo. 13 | - El comportamiento esperado y lo que realmente sucedió. 14 | - La versión de **AppState** que está utilizando. 15 | - Cualquier captura de pantalla o registro relevante. 16 | 17 | ### 2. Sugerir Características 18 | 19 | ¡Las nuevas ideas son bienvenidas! Si tiene una característica que le gustaría ver agregada a **AppState**, por favor abra un issue y describa: 20 | 21 | - El problema que la característica resolvería. 22 | - Cómo cree que debería funcionar la característica. 23 | - Cualquier contexto o ejemplo adicional que ayude a ilustrar su idea. 24 | 25 | ### 3. Enviar Pull Requests 26 | 27 | Si desea contribuir con código a **AppState**, siga estos pasos: 28 | 29 | 1. **Haga un Fork del Repositorio**: Cree un fork personal del repositorio de **AppState** en GitHub. 30 | 2. **Clone su Fork**: Clone su fork a su máquina local: 31 | ```bash 32 | git clone https://github.com/your-username/AppState.git 33 | ``` 34 | 3. **Cree una Nueva Rama**: Cree una nueva rama para su característica o corrección de errores: 35 | ```bash 36 | git checkout -b mi-rama-de-caracteristica 37 | ``` 38 | 4. **Realice Cambios**: Implemente sus cambios en la nueva rama. 39 | 5. **Pruebe sus Cambios**: Asegúrese de que sus cambios pasen todas las pruebas. Agregue nuevas pruebas si es necesario. 40 | 6. **Confirme sus Cambios**: Confirme sus cambios con un mensaje de confirmación descriptivo: 41 | ```bash 42 | git commit -m "Agregar mi nueva característica" 43 | ``` 44 | 7. **Envíe a GitHub**: Envíe su rama a su fork de GitHub: 45 | ```bash 46 | git push origin mi-rama-de-caracteristica 47 | ``` 48 | 8. **Cree un Pull Request**: Vaya al repositorio de **AppState** en GitHub y cree un pull request desde su rama. 49 | 50 | ### 4. Estilo de Código 51 | 52 | Por favor, siga las pautas de estilo de codificación utilizadas en el proyecto **AppState**. Un estilo de código consistente ayuda a que el código base sea más mantenible y fácil de revisar. 53 | 54 | ### 5. Licencia 55 | 56 | Al contribuir a **AppState**, usted acepta que sus contribuciones estarán licenciadas bajo la misma licencia que el proyecto: [LICENCIA](https://github.com/0xLeif/AppState/blob/main/LICENSE). 57 | 58 | ## ¡Gracias! 59 | 60 | Sus contribuciones son muy valoradas y apreciadas. ¡Gracias por ayudar a mejorar **AppState**! 61 | 62 | --- 63 | Esta traducción fue generada automáticamente y puede contener errores. Si eres un hablante nativo, te agradecemos que contribuyas con correcciones a través de un Pull Request. 64 | -------------------------------------------------------------------------------- /documentation/es/faq.md: -------------------------------------------------------------------------------- 1 | # Preguntas Frecuentes 2 | 3 | Esta breve sección de preguntas frecuentes aborda las preguntas comunes que los desarrolladores pueden tener al usar **AppState**. 4 | 5 | ## ¿Cómo reinicio un valor de estado? 6 | 7 | Para los estados persistentes como `StoredState`, `FileState` y `SyncState`, puede reiniciarlos a sus valores iniciales utilizando las funciones estáticas `reset` en el tipo `Application`. 8 | 9 | Por ejemplo, para reiniciar un `StoredState`: 10 | ```swift 11 | extension Application { 12 | var hasCompletedOnboarding: StoredState { storedState(initial: false, id: "onboarding_complete") } 13 | } 14 | 15 | // En algún lugar de su código 16 | Application.reset(storedState: \.hasCompletedOnboarding) 17 | ``` 18 | Esto restablecerá el valor en `UserDefaults` a `false`. Existen funciones `reset` similares para `FileState`, `SyncState` y `SecureState`. 19 | 20 | Para un `State` no persistente, puede reiniciarlo de la misma manera que los estados persistentes: 21 | ```swift 22 | extension Application { 23 | var counter: State { state(initial: 0) } 24 | } 25 | 26 | // En algún lugar de su código 27 | Application.reset(\.counter) 28 | ``` 29 | 30 | ## ¿Puedo usar AppState con tareas asíncronas? 31 | 32 | Sí. Los valores de `State` y de dependencia son seguros para hilos y funcionan sin problemas con Swift Concurrency. Puede acceder a ellos y modificarlos dentro de funciones `async` sin necesidad de bloqueos adicionales. 33 | 34 | ## ¿Dónde debo definir los estados y las dependencias? 35 | 36 | Mantenga todos sus estados y dependencias en extensiones de `Application`. Esto asegura una única fuente de verdad y facilita el descubrimiento de todos los valores disponibles. 37 | 38 | ## ¿Es AppState compatible con Combine? 39 | 40 | Puede usar AppState junto con Combine puenteando los cambios de `State` a publicadores. Observe un valor de `State` y envíe actualizaciones a través de un `PassthroughSubject` u otro publicador de Combine si es necesario. 41 | 42 | --- 43 | Esta traducción fue generada automáticamente y puede contener errores. Si eres un hablante nativo, te agradecemos que contribuyas con correcciones a través de un Pull Request. 44 | -------------------------------------------------------------------------------- /documentation/es/installation.md: -------------------------------------------------------------------------------- 1 | # Guía de Instalación 2 | 3 | Esta guía lo guiará a través del proceso de instalación de **AppState** en su proyecto de Swift utilizando Swift Package Manager. 4 | 5 | ## Swift Package Manager 6 | 7 | **AppState** se puede integrar fácilmente en su proyecto utilizando Swift Package Manager. Siga los pasos a continuación para agregar **AppState** como una dependencia. 8 | 9 | ### Paso 1: Actualice su Archivo `Package.swift` 10 | 11 | Agregue **AppState** a la sección de `dependencies` de su archivo `Package.swift`: 12 | 13 | ```swift 14 | dependencies: [ 15 | .package(url: "https://github.com/0xLeif/AppState.git", from: "2.2.0") 16 | ] 17 | ``` 18 | 19 | ### Paso 2: Agregue AppState a su Objetivo 20 | 21 | Incluya AppState en las dependencias de su objetivo: 22 | 23 | ```swift 24 | .target( 25 | name: "YourTarget", 26 | dependencies: ["AppState"] 27 | ) 28 | ``` 29 | 30 | ### Paso 3: Compile su Proyecto 31 | 32 | Una vez que haya agregado AppState a su archivo `Package.swift`, compile su proyecto para obtener la dependencia e integrarla en su base de código. 33 | 34 | ``` 35 | swift build 36 | ``` 37 | 38 | ### Paso 4: Importe AppState en su Código 39 | 40 | Ahora, puede comenzar a usar AppState en su proyecto importándolo en la parte superior de sus archivos de Swift: 41 | 42 | ```swift 43 | import AppState 44 | ``` 45 | 46 | ## Xcode 47 | 48 | Si prefiere agregar **AppState** directamente a través de Xcode, siga estos pasos: 49 | 50 | ### Paso 1: Abra su Proyecto de Xcode 51 | 52 | Abra su proyecto o espacio de trabajo de Xcode. 53 | 54 | ### Paso 2: Agregue una Dependencia de Paquete de Swift 55 | 56 | 1. Navegue hasta el navegador de proyectos y seleccione el archivo de su proyecto. 57 | 2. En el editor de proyectos, seleccione su objetivo y luego vaya a la pestaña "Swift Packages". 58 | 3. Haga clic en el botón "+" para agregar una dependencia de paquete. 59 | 60 | ### Paso 3: Ingrese la URL del Repositorio 61 | 62 | En el cuadro de diálogo "Choose Package Repository", ingrese la siguiente URL: `https://github.com/0xLeif/AppState.git` 63 | 64 | Luego haga clic en "Next". 65 | 66 | ### Paso 4: Especifique la Versión 67 | 68 | Elija la versión que desea utilizar. Se recomienda seleccionar la opción "Up to Next Major Version" y especificar `2.0.0` como el límite inferior. Luego haga clic en "Next". 69 | 70 | ### Paso 5: Agregue el Paquete 71 | 72 | Xcode obtendrá el paquete y le presentará opciones para agregar **AppState** a su objetivo. Asegúrese de seleccionar el objetivo correcto y haga clic en "Finish". 73 | 74 | ### Paso 6: Importe `AppState` en su Código 75 | 76 | Ahora puede importar **AppState** en la parte superior de sus archivos de Swift: 77 | 78 | ```swift 79 | import AppState 80 | ``` 81 | 82 | ## Próximos Pasos 83 | 84 | Con AppState instalado, puede pasar a la [Descripción General del Uso](usage-overview.md) para ver cómo implementar las características clave en su proyecto. 85 | 86 | --- 87 | Esta traducción fue generada automáticamente y puede contener errores. Si eres un hablante nativo, te agradecemos que contribuyas con correcciones a través de un Pull Request. 88 | -------------------------------------------------------------------------------- /documentation/es/starting-to-use-syncstate.md: -------------------------------------------------------------------------------- 1 | Para utilizar SyncState, primero deberá configurar las capacidades y los derechos de iCloud en su proyecto de Xcode. Aquí hay una introducción para guiarlo a través del proceso: 2 | 3 | ### Configuración de las capacidades de iCloud: 4 | 5 | 1. Abra su proyecto de Xcode y ajuste los identificadores de paquete para los destinos de macOS e iOS para que coincidan con los suyos. 6 | 2. A continuación, debe agregar la capacidad de iCloud a su proyecto. Para hacer esto, seleccione su proyecto en el Navegador de proyectos, luego seleccione su destino. En la barra de pestañas en la parte superior del área del editor, haga clic en "Signing & Capabilities". 7 | 3. En el panel Capacidades, active iCloud haciendo clic en el interruptor de la fila de iCloud. Debería ver que el interruptor se mueve a la posición de encendido. 8 | 4. Una vez que haya habilitado iCloud, debe habilitar el almacenamiento de clave-valor. Puede hacerlo marcando la casilla de verificación "Key-Value storage". 9 | 10 | ### Actualización de los derechos: 11 | 12 | 1. Ahora deberá actualizar su archivo de derechos. Abra el archivo de derechos para su destino. 13 | 2. Asegúrese de que el valor del Almacén de clave-valor de iCloud coincida con su ID de almacén de clave-valor único. Su ID único debe seguir el formato `$(TeamIdentifierPrefix)`. El valor predeterminado debería ser algo así como `$(TeamIdentifierPrefix)$(CFBundleIdentifier)`. Esto está bien para aplicaciones de una sola plataforma, pero si su aplicación está en varios sistemas operativos de Apple, es importante que las partes de la ID del almacén de clave-valor sean las mismas para ambos destinos. 14 | 15 | ### Configuración de los dispositivos: 16 | 17 | Además de configurar el proyecto en sí, también debe preparar los dispositivos que ejecutarán el proyecto. 18 | 19 | - Asegúrese de que iCloud Drive esté habilitado en los dispositivos iOS y macOS. 20 | - Inicie sesión en ambos dispositivos con la misma cuenta de iCloud. 21 | 22 | Si tiene alguna pregunta o encuentra algún problema, no dude en contactarnos o reportar un issue. 23 | 24 | --- 25 | Esta traducción fue generada automáticamente y puede contener errores. Si eres un hablante nativo, te agradecemos que contribuyas con correcciones a través de un Pull Request. 26 | -------------------------------------------------------------------------------- /documentation/es/usage-constant.md: -------------------------------------------------------------------------------- 1 | # Uso de Constantes 2 | 3 | `Constant` en la biblioteca **AppState** proporciona acceso de solo lectura a los valores dentro del estado de su aplicación. Funciona de manera similar a `Slice`, pero asegura que los valores a los que se accede sean inmutables. Esto hace que `Constant` sea ideal para acceder a valores que de otro modo podrían ser mutables pero que deben permanecer de solo lectura en ciertos contextos. 4 | 5 | ## Características Clave 6 | 7 | - **Acceso de Solo Lectura**: Las constantes proporcionan acceso al estado mutable, pero los valores no se pueden modificar. 8 | - **Ámbito de Aplicación**: Al igual que `Slice`, `Constant` se define dentro de la extensión de `Application` y tiene como ámbito el acceso a partes específicas del estado. 9 | - **Seguro para Hilos**: `Constant` garantiza un acceso seguro al estado en entornos concurrentes. 10 | 11 | ## Ejemplo de Uso 12 | 13 | ### Definir una Constante en la Aplicación 14 | 15 | Así es como se define una `Constant` en la extensión de `Application` para acceder a un valor de solo lectura: 16 | 17 | ```swift 18 | import AppState 19 | import SwiftUI 20 | 21 | struct ExampleValue { 22 | var username: String? 23 | var isLoading: Bool 24 | let value: String 25 | var mutableValue: String 26 | } 27 | 28 | extension Application { 29 | var exampleValue: State { 30 | state( 31 | initial: ExampleValue( 32 | username: "Leif", 33 | isLoading: false, 34 | value: "value", 35 | mutableValue: "" 36 | ) 37 | ) 38 | } 39 | } 40 | ``` 41 | 42 | ### Acceder a la Constante en una Vista de SwiftUI 43 | 44 | En una vista de SwiftUI, puede usar el property wrapper `@Constant` para acceder al estado constante de solo lectura: 45 | 46 | ```swift 47 | import AppState 48 | import SwiftUI 49 | 50 | struct ExampleView: View { 51 | @Constant(\.exampleValue, \.value) var constantValue: String 52 | 53 | var body: some View { 54 | Text("Valor Constante: \(constantValue)") 55 | } 56 | } 57 | ``` 58 | 59 | ### Acceso de Solo Lectura al Estado Mutable 60 | 61 | Incluso si el valor es mutable en otro lugar, cuando se accede a través de `@Constant`, el valor se vuelve inmutable: 62 | 63 | ```swift 64 | import AppState 65 | import SwiftUI 66 | 67 | struct ExampleView: View { 68 | @Constant(\.exampleValue, \.mutableValue) var constantMutableValue: String 69 | 70 | var body: some View { 71 | Text("Valor Mutable de Solo Lectura: \(constantMutableValue)") 72 | } 73 | } 74 | ``` 75 | 76 | ## Mejores Prácticas 77 | 78 | - **Usar para Acceso de Solo Lectura**: Use `Constant` para acceder a partes del estado que no deben modificarse en ciertos contextos, incluso si son mutables en otro lugar. 79 | - **Seguro para Hilos**: Al igual que otros componentes de AppState, `Constant` garantiza un acceso seguro al estado para hilos. 80 | - **Usar `OptionalConstant` para Valores Opcionales**: Si la parte del estado a la que está accediendo puede ser `nil`, use `OptionalConstant` para manejar de forma segura la ausencia de un valor. 81 | 82 | ## Conclusión 83 | 84 | `Constant` y `OptionalConstant` proporcionan una forma eficiente de acceder a partes específicas del estado de su aplicación de solo lectura. Aseguran que los valores que de otro modo podrían ser mutables se traten como inmutables cuando se accede a ellos dentro de una vista, garantizando la seguridad y la claridad en su código. 85 | 86 | --- 87 | Esta traducción fue generada automáticamente y puede contener errores. Si eres un hablante nativo, te agradecemos que contribuyas con correcciones a través de un Pull Request. 88 | -------------------------------------------------------------------------------- /documentation/es/usage-securestate.md: -------------------------------------------------------------------------------- 1 | # Uso de SecureState 2 | 3 | `SecureState` es un componente de la biblioteca **AppState** que le permite almacenar datos confidenciales de forma segura en el Llavero. Es más adecuado para almacenar pequeñas piezas de datos como tokens o contraseñas que necesitan ser encriptadas de forma segura. 4 | 5 | ## Características Clave 6 | 7 | - **Almacenamiento Seguro**: Los datos almacenados con `SecureState` se encriptan y se guardan de forma segura en el Llavero. 8 | - **Persistencia**: Los datos permanecen persistentes entre los lanzamientos de la aplicación, lo que permite la recuperación segura de valores confidenciales. 9 | 10 | ## Limitaciones del Llavero 11 | 12 | Aunque `SecureState` es muy seguro, tiene ciertas limitaciones: 13 | 14 | - **Tamaño de Almacenamiento Limitado**: El Llavero está diseñado para pequeñas piezas de datos. No es adecuado para almacenar archivos o conjuntos de datos grandes. 15 | - **Rendimiento**: Acceder al Llavero es más lento que acceder a `UserDefaults`, por lo que debe usarlo solo cuando sea necesario para almacenar de forma segura datos confidenciales. 16 | 17 | ## Ejemplo de Uso 18 | 19 | ### Almacenar un Token Seguro 20 | 21 | ```swift 22 | import AppState 23 | import SwiftUI 24 | 25 | extension Application { 26 | var userToken: SecureState { 27 | secureState(id: "userToken") 28 | } 29 | } 30 | 31 | struct SecureView: View { 32 | @SecureState(\.userToken) var userToken: String? 33 | 34 | var body: some View { 35 | VStack { 36 | if let token = userToken { 37 | Text("Token de usuario: \(token)") 38 | } else { 39 | Text("No se encontró ningún token.") 40 | } 41 | Button("Establecer Token") { 42 | userToken = "secure_token_value" 43 | } 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | ### Manejo de la Ausencia de Datos Seguros 50 | 51 | Al acceder al Llavero por primera vez, o si no hay ningún valor almacenado, `SecureState` devolverá `nil`. Asegúrese de manejar este escenario correctamente: 52 | 53 | ```swift 54 | if let token = userToken { 55 | print("Token: \(token)") 56 | } else { 57 | print("No hay ningún token disponible.") 58 | } 59 | ``` 60 | 61 | ## Mejores Prácticas 62 | 63 | - **Usar para Datos Pequeños**: El Llavero debe usarse para almacenar pequeñas piezas de información confidencial como tokens, contraseñas y claves. 64 | - **Evitar Grandes Conjuntos de Datos**: Si necesita almacenar grandes conjuntos de datos de forma segura, considere usar encriptación basada en archivos u otros métodos, ya que el Llavero no está diseñado para el almacenamiento de grandes datos. 65 | - **Manejar nil**: Siempre maneje los casos en que el Llavero devuelve `nil` cuando no hay ningún valor presente. 66 | 67 | --- 68 | Esta traducción fue generada automáticamente y puede contener errores. Si eres un hablante nativo, te agradecemos que contribuyas con correcciones a través de un Pull Request. 69 | -------------------------------------------------------------------------------- /documentation/fr/contributing.md: -------------------------------------------------------------------------------- 1 | # Contribuer à AppState 2 | 3 | Merci d'envisager de contribuer à **AppState** ! Vos contributions aident à améliorer ce projet pour tout le monde. 4 | 5 | ## Comment Contribuer 6 | 7 | ### 1. Signaler des Bugs 8 | 9 | Si vous rencontrez des bugs, veuillez ouvrir une issue sur GitHub. Lorsque vous signalez un bug, veuillez inclure : 10 | 11 | - Un titre clair et descriptif. 12 | - Une description détaillée du bug, y compris les étapes pour le reproduire. 13 | - Le comportement attendu et ce qui s'est réellement passé. 14 | - La version de **AppState** que vous utilisez. 15 | - Toutes les captures d'écran ou journaux pertinents. 16 | 17 | ### 2. Suggérer des Fonctionnalités 18 | 19 | Les nouvelles idées sont les bienvenues ! Si vous avez une fonctionnalité que vous aimeriez voir ajoutée à **AppState**, veuillez ouvrir une issue et décrire : 20 | 21 | - Le problème que la fonctionnalité résoudrait. 22 | - Comment vous pensez que la fonctionnalité devrait fonctionner. 23 | - Tout contexte ou exemple supplémentaire qui aiderait à illustrer votre idée. 24 | 25 | ### 3. Soumettre des Pull Requests 26 | 27 | Si vous souhaitez contribuer du code à **AppState**, suivez ces étapes : 28 | 29 | 1. **Forker le Dépôt**: Créez un fork personnel du dépôt **AppState** sur GitHub. 30 | 2. **Cloner Votre Fork**: Clonez votre fork sur votre machine locale : 31 | ```bash 32 | git clone https://github.com/your-username/AppState.git 33 | ``` 34 | 3. **Créer une Nouvelle Branche**: Créez une nouvelle branche pour votre fonctionnalité ou votre correction de bug : 35 | ```bash 36 | git checkout -b ma-branche-de-fonctionnalite 37 | ``` 38 | 4. **Effectuer des Modifications**: Implémentez vos modifications dans la nouvelle branche. 39 | 5. **Tester Vos Modifications**: Assurez-vous que vos modifications passent tous les tests. Ajoutez de nouveaux tests si nécessaire. 40 | 6. **Commiter Vos Modifications**: Commitez vos modifications avec un message de commit descriptif : 41 | ```bash 42 | git commit -m "Ajouter ma nouvelle fonctionnalité" 43 | ``` 44 | 7. **Pousser vers GitHub**: Poussez votre branche vers votre fork GitHub : 45 | ```bash 46 | git push origin ma-branche-de-fonctionnalite 47 | ``` 48 | 8. **Créer une Pull Request**: Allez sur le dépôt **AppState** sur GitHub et créez une pull request à partir de votre branche. 49 | 50 | ### 4. Style de Code 51 | 52 | Veuillez suivre les directives de style de codage utilisées dans le projet **AppState**. Un style de code cohérent aide à rendre la base de code plus maintenable et plus facile à examiner. 53 | 54 | ### 5. Licence 55 | 56 | En contribuant à **AppState**, vous acceptez que vos contributions soient sous licence sous la même licence que le projet : [LICENCE](https://github.com/0xLeif/AppState/blob/main/LICENSE). 57 | 58 | ## Merci ! 59 | 60 | Vos contributions sont très précieuses et appréciées. Merci d'aider à améliorer **AppState** ! 61 | 62 | --- 63 | Cette traduction a été générée automatiquement et peut contenir des erreurs. Si vous êtes un locuteur natif, nous vous serions reconnaissants de contribuer avec des corrections via une Pull Request. 64 | -------------------------------------------------------------------------------- /documentation/fr/faq.md: -------------------------------------------------------------------------------- 1 | # Foire Aux Questions 2 | 3 | Cette courte FAQ répond aux questions courantes que les développeurs peuvent se poser lors de l'utilisation de **AppState**. 4 | 5 | ## Comment réinitialiser une valeur d'état ? 6 | 7 | Pour les états persistants comme `StoredState`, `FileState` et `SyncState`, vous pouvez les réinitialiser à leurs valeurs initiales en utilisant les fonctions statiques `reset` sur le type `Application`. 8 | 9 | Par exemple, pour réinitialiser un `StoredState` : 10 | ```swift 11 | extension Application { 12 | var hasCompletedOnboarding: StoredState { storedState(initial: false, id: "onboarding_complete") } 13 | } 14 | 15 | // Quelque part dans votre code 16 | Application.reset(storedState: \.hasCompletedOnboarding) 17 | ``` 18 | Cela réinitialisera la valeur dans `UserDefaults` à `false`. Des fonctions `reset` similaires existent pour `FileState`, `SyncState` et `SecureState`. 19 | 20 | Pour un `State` non persistant, vous pouvez le réinitialiser de la même manière que les états persistants : 21 | ```swift 22 | extension Application { 23 | var counter: State { state(initial: 0) } 24 | } 25 | 26 | // Quelque part dans votre code 27 | Application.reset(\.counter) 28 | ``` 29 | 30 | ## Puis-je utiliser AppState avec des tâches asynchrones ? 31 | 32 | Oui. Les valeurs de `State` et de dépendance sont thread-safe et fonctionnent de manière transparente avec Swift Concurrency. Vous pouvez y accéder et les modifier à l'intérieur des fonctions `async` sans verrouillage supplémentaire. 33 | 34 | ## Où dois-je définir les états et les dépendances ? 35 | 36 | Conservez tous vos états et dépendances dans des extensions de `Application`. Cela garantit une source unique de vérité et facilite la découverte de toutes les valeurs disponibles. 37 | 38 | ## AppState est-il compatible avec Combine ? 39 | 40 | Vous pouvez utiliser AppState avec Combine en pontant les changements de `State` vers des publicateurs. Observez une valeur `State` et envoyez des mises à jour via un `PassthroughSubject` ou un autre publicateur Combine si nécessaire. 41 | 42 | --- 43 | Cette traduction a été générée automatiquement et peut contenir des erreurs. Si vous êtes un locuteur natif, nous vous serions reconnaissants de contribuer avec des corrections via une Pull Request. 44 | -------------------------------------------------------------------------------- /documentation/fr/installation.md: -------------------------------------------------------------------------------- 1 | # Guide d'Installation 2 | 3 | Ce guide vous expliquera comment installer **AppState** dans votre projet Swift à l'aide du Swift Package Manager. 4 | 5 | ## Swift Package Manager 6 | 7 | **AppState** peut être facilement intégré à votre projet à l'aide du Swift Package Manager. Suivez les étapes ci-dessous pour ajouter **AppState** en tant que dépendance. 8 | 9 | ### Étape 1 : Mettez à jour votre fichier `Package.swift` 10 | 11 | Ajoutez **AppState** à la section `dependencies` de votre fichier `Package.swift` : 12 | 13 | ```swift 14 | dependencies: [ 15 | .package(url: "https://github.com/0xLeif/AppState.git", from: "2.2.0") 16 | ] 17 | ``` 18 | 19 | ### Étape 2 : Ajoutez AppState à votre cible 20 | 21 | Incluez AppState dans les dépendances de votre cible : 22 | 23 | ```swift 24 | .target( 25 | name: "YourTarget", 26 | dependencies: ["AppState"] 27 | ) 28 | ``` 29 | 30 | ### Étape 3 : Compilez votre projet 31 | 32 | Une fois que vous avez ajouté AppState à votre fichier `Package.swift`, compilez votre projet pour récupérer la dépendance et l'intégrer à votre base de code. 33 | 34 | ``` 35 | swift build 36 | ``` 37 | 38 | ### Étape 4 : Importez AppState dans votre code 39 | 40 | Maintenant, vous pouvez commencer à utiliser AppState dans votre projet en l'important en haut de vos fichiers Swift : 41 | 42 | ```swift 43 | import AppState 44 | ``` 45 | 46 | ## Xcode 47 | 48 | Si vous préférez ajouter **AppState** directement via Xcode, suivez ces étapes : 49 | 50 | ### Étape 1 : Ouvrez votre projet Xcode 51 | 52 | Ouvrez votre projet ou espace de travail Xcode. 53 | 54 | ### Étape 2 : Ajoutez une dépendance de package Swift 55 | 56 | 1. Accédez au navigateur de projet et sélectionnez votre fichier de projet. 57 | 2. Dans l'éditeur de projet, sélectionnez votre cible, puis allez à l'onglet "Swift Packages". 58 | 3. Cliquez sur le bouton "+" pour ajouter une dépendance de package. 59 | 60 | ### Étape 3 : Entrez l'URL du dépôt 61 | 62 | Dans la boîte de dialogue "Choose Package Repository", entrez l'URL suivante : `https://github.com/0xLeif/AppState.git` 63 | 64 | Puis cliquez sur "Suivant". 65 | 66 | ### Étape 4 : Spécifiez la version 67 | 68 | Choisissez la version que vous souhaitez utiliser. Il est recommandé de sélectionner l'option "Up to Next Major Version" et de spécifier `2.0.0` comme limite inférieure. Puis cliquez sur "Suivant". 69 | 70 | ### Étape 5 : Ajoutez le package 71 | 72 | Xcode récupérera le package et vous présentera des options pour ajouter **AppState** à votre cible. Assurez-vous de sélectionner la bonne cible et cliquez sur "Terminer". 73 | 74 | ### Étape 6 : Importez `AppState` dans votre code 75 | 76 | Vous pouvez maintenant importer **AppState** en haut de vos fichiers Swift : 77 | 78 | ```swift 79 | import AppState 80 | ``` 81 | 82 | ## Prochaines Étapes 83 | 84 | Une fois AppState installé, vous pouvez passer à l'[Aperçu de l'utilisation](usage-overview.md) pour voir comment implémenter les fonctionnalités clés dans votre projet. 85 | 86 | --- 87 | Cette traduction a été générée automatiquement et peut contenir des erreurs. Si vous êtes un locuteur natif, nous vous serions reconnaissants de contribuer avec des corrections via une Pull Request. 88 | -------------------------------------------------------------------------------- /documentation/fr/starting-to-use-syncstate.md: -------------------------------------------------------------------------------- 1 | Pour utiliser SyncState, vous devrez d'abord configurer les fonctionnalités et les autorisations iCloud dans votre projet Xcode. Voici une introduction pour vous guider tout au long du processus : 2 | 3 | ### Configuration des fonctionnalités iCloud : 4 | 5 | 1. Ouvrez votre projet Xcode et ajustez les identifiants de lot pour les cibles macOS et iOS afin qu'ils correspondent aux vôtres. 6 | 2. Ensuite, vous devez ajouter la fonctionnalité iCloud à votre projet. Pour ce faire, sélectionnez votre projet dans le navigateur de projets, puis sélectionnez votre cible. Dans la barre d'onglets en haut de la zone de l'éditeur, cliquez sur « Signing & Capabilities ». 7 | 3. Dans le volet Capacités, activez iCloud en cliquant sur le commutateur de la ligne iCloud. Vous devriez voir le commutateur passer en position Activé. 8 | 4. Une fois que vous avez activé iCloud, vous devez activer le stockage clé-valeur. Vous pouvez le faire en cochant la case « Stockage clé-valeur ». 9 | 10 | ### Mise à jour des autorisations : 11 | 12 | 1. Vous devrez maintenant mettre à jour votre fichier d'autorisations. Ouvrez le fichier d'autorisations de votre cible. 13 | 2. Assurez-vous que la valeur du magasin clé-valeur iCloud correspond à votre ID de magasin clé-valeur unique. Votre ID unique doit respecter le format `$(TeamIdentifierPrefix)`. La valeur par défaut doit être quelque chose comme `$(TeamIdentifierPrefix)$(CFBundleIdentifier)`. C'est très bien pour les applications à plate-forme unique, mais si votre application se trouve sur plusieurs systèmes d'exploitation Apple, il est important que les parties de l'ID du magasin clé-valeur soient les mêmes pour les deux cibles. 14 | 15 | ### Configuration des appareils : 16 | 17 | En plus de configurer le projet lui-même, vous devez également préparer les appareils qui exécuteront le projet. 18 | 19 | - Assurez-vous qu'iCloud Drive est activé sur les appareils iOS et macOS. 20 | - Connectez-vous aux deux appareils avec le même compte iCloud. 21 | 22 | Si vous avez des questions ou rencontrez des problèmes, n'hésitez pas à nous contacter ou à soumettre un problème. 23 | 24 | --- 25 | Cette traduction a été générée automatiquement et peut contenir des erreurs. Si vous êtes un locuteur natif, nous vous serions reconnaissants de contribuer avec des corrections via une Pull Request. 26 | -------------------------------------------------------------------------------- /documentation/fr/usage-constant.md: -------------------------------------------------------------------------------- 1 | # Utilisation de Constant 2 | 3 | `Constant` dans la bibliothèque **AppState** fournit un accès en lecture seule aux valeurs de l'état de votre application. Il fonctionne de manière similaire à `Slice`, mais garantit que les valeurs consultées sont immuables. Cela rend `Constant` idéal pour accéder à des valeurs qui pourraient autrement être mutables mais qui doivent rester en lecture seule dans certains contextes. 4 | 5 | ## Fonctionnalités Clés 6 | 7 | - **Accès en Lecture Seule**: Les constantes permettent d'accéder à un état mutable, mais les valeurs ne peuvent pas être modifiées. 8 | - **Portée Limitée à l'Application**: Comme `Slice`, `Constant` est défini dans l'extension `Application` et sa portée est limitée à l'accès à des parties spécifiques de l'état. 9 | - **Thread-Safe**: `Constant` garantit un accès sécurisé à l'état dans les environnements concurrents. 10 | 11 | ## Exemple d'Utilisation 12 | 13 | ### Définir une Constante dans l'Application 14 | 15 | Voici comment définir une `Constant` dans l'extension `Application` pour accéder à une valeur en lecture seule : 16 | 17 | ```swift 18 | import AppState 19 | import SwiftUI 20 | 21 | struct ExampleValue { 22 | var username: String? 23 | var isLoading: Bool 24 | let value: String 25 | var mutableValue: String 26 | } 27 | 28 | extension Application { 29 | var exampleValue: State { 30 | state( 31 | initial: ExampleValue( 32 | username: "Leif", 33 | isLoading: false, 34 | value: "value", 35 | mutableValue: "" 36 | ) 37 | ) 38 | } 39 | } 40 | ``` 41 | 42 | ### Accéder à la Constante dans une Vue SwiftUI 43 | 44 | Dans une vue SwiftUI, vous pouvez utiliser le property wrapper `@Constant` pour accéder à l'état constant en lecture seule : 45 | 46 | ```swift 47 | import AppState 48 | import SwiftUI 49 | 50 | struct ExampleView: View { 51 | @Constant(\.exampleValue, \.value) var constantValue: String 52 | 53 | var body: some View { 54 | Text("Valeur Constante : \(constantValue)") 55 | } 56 | } 57 | ``` 58 | 59 | ### Accès en Lecture Seule à un État Mutable 60 | 61 | Même si la valeur est mutable ailleurs, lorsqu'elle est consultée via `@Constant`, la valeur devient immuable : 62 | 63 | ```swift 64 | import AppState 65 | import SwiftUI 66 | 67 | struct ExampleView: View { 68 | @Constant(\.exampleValue, \.mutableValue) var constantMutableValue: String 69 | 70 | var body: some View { 71 | Text("Valeur Mutable en Lecture Seule : \(constantMutableValue)") 72 | } 73 | } 74 | ``` 75 | 76 | ## Meilleures Pratiques 77 | 78 | - **Utiliser pour un Accès en Lecture Seule**: Utilisez `Constant` pour accéder aux parties de l'état qui ne doivent pas être modifiées dans certains contextes, même si elles sont mutables ailleurs. 79 | - **Thread-Safe**: Comme les autres composants d'AppState, `Constant` garantit un accès thread-safe à l'état. 80 | - **Utiliser `OptionalConstant` pour les Valeurs Optionnelles**: Si la partie de l'état que vous consultez peut être `nil`, utilisez `OptionalConstant` pour gérer en toute sécurité l'absence de valeur. 81 | 82 | ## Conclusion 83 | 84 | `Constant` et `OptionalConstant` offrent un moyen efficace d'accéder à des parties spécifiques de l'état de votre application en lecture seule. Ils garantissent que les valeurs qui pourraient autrement être mutables sont traitées comme immuables lorsqu'elles sont consultées dans une vue, assurant ainsi la sécurité et la clarté de votre code. 85 | 86 | --- 87 | Cette traduction a été générée automatiquement et peut contenir des erreurs. Si vous êtes un locuteur natif, nous vous serions reconnaissants de contribuer avec des corrections via une Pull Request. 88 | -------------------------------------------------------------------------------- /documentation/fr/usage-securestate.md: -------------------------------------------------------------------------------- 1 | # Utilisation de SecureState 2 | 3 | `SecureState` est un composant de la bibliothèque **AppState** qui vous permet de stocker des données sensibles de manière sécurisée dans le Trousseau. Il est idéal pour stocker de petites quantités de données comme des jetons ou des mots de passe qui doivent être chiffrés de manière sécurisée. 4 | 5 | ## Fonctionnalités Clés 6 | 7 | - **Stockage Sécurisé** : Les données stockées à l'aide de `SecureState` sont chiffrées et sauvegardées de manière sécurisée dans le Trousseau. 8 | - **Persistance** : Les données restent persistantes entre les lancements de l'application, ce qui permet de récupérer en toute sécurité des valeurs sensibles. 9 | 10 | ## Limitations du Trousseau 11 | 12 | Bien que `SecureState` soit très sécurisé, il présente certaines limitations : 13 | 14 | - **Taille de Stockage Limitée** : Le Trousseau est conçu pour de petites quantités de données. Il n'est pas adapté au stockage de fichiers volumineux ou de grands ensembles de données. 15 | - **Performance** : L'accès au Trousseau est plus lent que l'accès à `UserDefaults`, il ne faut donc l'utiliser que lorsque cela est nécessaire pour stocker des données sensibles en toute sécurité. 16 | 17 | ## Exemple d'Utilisation 18 | 19 | ### Stocker un Jeton Sécurisé 20 | 21 | ```swift 22 | import AppState 23 | import SwiftUI 24 | 25 | extension Application { 26 | var userToken: SecureState { 27 | secureState(id: "userToken") 28 | } 29 | } 30 | 31 | struct SecureView: View { 32 | @SecureState(\.userToken) var userToken: String? 33 | 34 | var body: some View { 35 | VStack { 36 | if let token = userToken { 37 | Text("Jeton utilisateur : \(token)") 38 | } else { 39 | Text("Aucun jeton trouvé.") 40 | } 41 | Button("Définir le jeton") { 42 | userToken = "secure_token_value" 43 | } 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | ### Gérer l'Absence de Données Sécurisées 50 | 51 | Lors du premier accès au Trousseau, ou s'il n'y a aucune valeur stockée, `SecureState` renverra `nil`. Assurez-vous de gérer correctement ce scénario : 52 | 53 | ```swift 54 | if let token = userToken { 55 | print("Jeton : \(token)") 56 | } else { 57 | print("Aucun jeton disponible.") 58 | } 59 | ``` 60 | 61 | ## Meilleures Pratiques 62 | 63 | - **Utiliser pour de Petites Données** : Le Trousseau doit être utilisé pour stocker de petites informations sensibles comme des jetons, des mots de passe et des clés. 64 | - **Éviter les Grands Ensembles de Données** : Si vous devez stocker de grands ensembles de données de manière sécurisée, envisagez d'utiliser le chiffrement basé sur les fichiers ou d'autres méthodes, car le Trousseau n'est pas conçu pour le stockage de grandes quantités de données. 65 | - **Gérer nil** : Gérez toujours les cas où le Trousseau renvoie `nil` lorsqu'aucune valeur n'est présente. 66 | 67 | --- 68 | Cette traduction a été générée automatiquement et peut contenir des erreurs. Si vous êtes un locuteur natif, nous vous serions reconnaissants de contribuer avec des corrections via une Pull Request. 69 | -------------------------------------------------------------------------------- /documentation/hi/contributing.md: -------------------------------------------------------------------------------- 1 | # AppState में योगदान 2 | 3 | **AppState** में योगदान देने पर विचार करने के लिए धन्यवाद! आपके योगदान इस परियोजना को सभी के लिए बेहतर बनाने में मदद करते हैं। 4 | 5 | ## कैसे योगदान करें 6 | 7 | ### 1. बग की रिपोर्ट करना 8 | 9 | यदि आप किसी बग का सामना करते हैं, तो कृपया GitHub पर एक समस्या खोलें। बग की रिपोर्ट करते समय, कृपया शामिल करें: 10 | 11 | - एक स्पष्ट और वर्णनात्मक शीर्षक। 12 | - बग का विस्तृत विवरण, जिसमें इसे पुन: उत्पन्न करने के चरण शामिल हैं। 13 | - अपेक्षित व्यवहार और वास्तव में क्या हुआ। 14 | - आप जिस **AppState** के संस्करण का उपयोग कर रहे हैं। 15 | - कोई भी प्रासंगिक स्क्रीनशॉट या लॉग। 16 | 17 | ### 2. सुविधाओं का सुझाव देना 18 | 19 | नए विचारों का स्वागत है! यदि आपके पास कोई ऐसी सुविधा है जिसे आप **AppState** में जोड़ा देखना चाहेंगे, तो कृपया एक समस्या खोलें और वर्णन करें: 20 | 21 | - वह समस्या जिसे सुविधा हल करेगी। 22 | - आपको क्या लगता है कि सुविधा कैसे काम करनी चाहिए। 23 | - कोई भी अतिरिक्त संदर्भ या उदाहरण जो आपके विचार को स्पष्ट करने में मदद करेगा। 24 | 25 | ### 3. पुल अनुरोध सबमिट करना 26 | 27 | यदि आप **AppState** में कोड का योगदान करना चाहते हैं, तो इन चरणों का पालन करें: 28 | 29 | 1. **रिपोजिटरी को फोर्क करें**: GitHub पर **AppState** रिपोजिटरी का एक व्यक्तिगत फोर्क बनाएं। 30 | 2. **अपने फोर्क को क्लोन करें**: अपने फोर्क को अपनी स्थानीय मशीन पर क्लोन करें: 31 | ```bash 32 | git clone https://github.com/your-username/AppState.git 33 | ``` 34 | 3. **एक नई शाखा बनाएं**: अपनी सुविधा या बगफिक्स के लिए एक नई शाखा बनाएं: 35 | ```bash 36 | git checkout -b my-feature-branch 37 | ``` 38 | 4. **परिवर्तन करें**: नई शाखा में अपने परिवर्तनों को लागू करें। 39 | 5. **अपने परिवर्तनों का परीक्षण करें**: सुनिश्चित करें कि आपके परिवर्तन सभी परीक्षण पास करते हैं। यदि आवश्यक हो तो नए परीक्षण जोड़ें। 40 | 6. **अपने परिवर्तनों को प्रतिबद्ध करें**: अपने परिवर्तनों को एक वर्णनात्मक प्रतिबद्ध संदेश के साथ प्रतिबद्ध करें: 41 | ```bash 42 | git commit -m "Add my new feature" 43 | ``` 44 | 7. **GitHub पर पुश करें**: अपनी शाखा को अपने GitHub फोर्क पर पुश करें: 45 | ```bash 46 | git push origin my-feature-branch 47 | ``` 48 | 8. **एक पुल अनुरोध बनाएं**: GitHub पर **AppState** रिपोजिटरी पर जाएं और अपनी शाखा से एक पुल अनुरोध बनाएं। 49 | 50 | ### 4. कोड शैली 51 | 52 | कृपया **AppState** परियोजना में उपयोग किए गए कोडिंग शैली दिशानिर्देशों का पालन करें। सुसंगत कोड शैली कोडबेस को अधिक रखरखाव योग्य और समीक्षा में आसान बनाने में मदद करती है। 53 | 54 | ### 5. लाइसेंस 55 | 56 | **AppState** में योगदान करके, आप सहमत हैं कि आपके योगदान को परियोजना के समान लाइसेंस के तहत लाइसेंस दिया जाएगा: [लाइसेंस](https://github.com/0xLeif/AppState/blob/main/LICENSE)। 57 | 58 | ## धन्यवाद! 59 | 60 | आपके योगदान को बहुत महत्व दिया जाता है और सराहा जाता है। **AppState** को बेहतर बनाने में मदद करने के लिए धन्यवाद! 61 | 62 | --- 63 | यह अनुवाद स्वचालित रूप से उत्पन्न किया गया था और इसमें त्रुटियाँ हो सकती हैं। यदि आप एक देशी वक्ता हैं, तो हम एक पुल अनुरोध के माध्यम से सुधारों में आपके योगदान की सराहना करेंगे। 64 | -------------------------------------------------------------------------------- /documentation/hi/faq.md: -------------------------------------------------------------------------------- 1 | # अक्सर पूछे जाने वाले प्रश्न 2 | 3 | यह संक्षिप्त सामान्य प्रश्नोत्तर उन आम सवालों का समाधान करता है जो डेवलपर्स को **AppState** का उपयोग करते समय हो सकते हैं। 4 | 5 | ## मैं किसी स्थिति मान को कैसे रीसेट करूं? 6 | 7 | `StoredState`, `FileState`, और `SyncState` जैसे स्थायी स्थितियों के लिए, आप उन्हें `Application` प्रकार पर स्थैतिक `reset` फ़ंक्शंस का उपयोग करके उनके प्रारंभिक मानों पर रीसेट कर सकते हैं। 8 | 9 | उदाहरण के लिए, `StoredState` को रीसेट करने के लिए: 10 | ```swift 11 | extension Application { 12 | var hasCompletedOnboarding: StoredState { storedState(initial: false, id: "onboarding_complete") } 13 | } 14 | 15 | // आपके कोड में कहीं 16 | Application.reset(storedState: \.hasCompletedOnboarding) 17 | ``` 18 | यह `UserDefaults` में मान को `false` पर रीसेट कर देगा। `FileState`, `SyncState`, और `SecureState` के लिए समान `reset` फ़ंक्शंस मौजूद हैं। 19 | 20 | गैर-स्थायी `State` के लिए, आप इसे स्थायी स्थितियों की तरह ही रीसेट कर सकते हैं: 21 | ```swift 22 | extension Application { 23 | var counter: State { state(initial: 0) } 24 | } 25 | 26 | // आपके कोड में कहीं 27 | Application.reset(\.counter) 28 | ``` 29 | 30 | ## क्या मैं एसिंक्रोनस कार्यों के साथ AppState का उपयोग कर सकता हूं? 31 | 32 | हां। `State` और निर्भरता मान थ्रेड-सुरक्षित हैं और स्विफ्ट कॉन्करेंसी के साथ सहजता से काम करते हैं। आप उन्हें अतिरिक्त लॉकिंग के बिना `async` फ़ंक्शंस के अंदर एक्सेस और संशोधित कर सकते हैं। 33 | 34 | ## मुझे स्थितियों और निर्भरताओं को कहां परिभाषित करना चाहिए? 35 | 36 | अपनी सभी स्थितियों और निर्भरताओं को `Application` एक्सटेंशन में रखें। यह सत्य का एक स्रोत सुनिश्चित करता है और सभी उपलब्ध मानों को खोजना आसान बनाता है। 37 | 38 | ## क्या AppState कंबाइन के साथ संगत है? 39 | 40 | आप `State` परिवर्तनों को प्रकाशकों से जोड़कर AppState को कंबाइन के साथ उपयोग कर सकते हैं। एक `State` मान का निरीक्षण करें और यदि आवश्यक हो तो `PassthroughSubject` या अन्य कंबाइन प्रकाशक के माध्यम से अपडेट भेजें। 41 | 42 | --- 43 | यह अनुवाद स्वचालित रूप से उत्पन्न किया गया था और इसमें त्रुटियाँ हो सकती हैं। यदि आप एक देशी वक्ता हैं, तो हम एक पुल अनुरोध के माध्यम से सुधारों में आपके योगदान की सराहना करेंगे। 44 | -------------------------------------------------------------------------------- /documentation/hi/installation.md: -------------------------------------------------------------------------------- 1 | # स्थापना गाइड 2 | 3 | यह गाइड आपको स्विफ्ट पैकेज मैनेजर का उपयोग करके अपने स्विफ्ट प्रोजेक्ट में **AppState** स्थापित करने की प्रक्रिया के माध्यम से मार्गदर्शन करेगी। 4 | 5 | ## स्विफ्ट पैकेज मैनेजर 6 | 7 | **AppState** को स्विफ्ट पैकेज मैनेजर का उपयोग करके आपके प्रोजेक्ट में आसानी से एकीकृत किया जा सकता है। **AppState** को एक निर्भरता के रूप में जोड़ने के लिए नीचे दिए गए चरणों का पालन करें। 8 | 9 | ### चरण 1: अपनी `Package.swift` फ़ाइल को अपडेट करें 10 | 11 | **AppState** को अपनी `Package.swift` फ़ाइल के `dependencies` अनुभाग में जोड़ें: 12 | 13 | ```swift 14 | dependencies: [ 15 | .package(url: "https://github.com/0xLeif/AppState.git", from: "2.2.0") 16 | ] 17 | ``` 18 | 19 | ### चरण 2: अपने लक्ष्य में AppState जोड़ें 20 | 21 | अपने लक्ष्य की निर्भरताओं में AppState शामिल करें: 22 | 23 | ```swift 24 | .target( 25 | name: "YourTarget", 26 | dependencies: ["AppState"] 27 | ) 28 | ``` 29 | 30 | ### चरण 3: अपना प्रोजेक्ट बनाएं 31 | 32 | एक बार जब आप AppState को अपनी `Package.swift` फ़ाइल में जोड़ लेते हैं, तो निर्भरता को लाने और इसे अपने कोडबेस में एकीकृत करने के लिए अपना प्रोजेक्ट बनाएं। 33 | 34 | ``` 35 | swift build 36 | ``` 37 | 38 | ### चरण 4: अपने कोड में AppState आयात करें 39 | 40 | अब, आप अपने स्विफ्ट फ़ाइलों के शीर्ष पर इसे आयात करके अपने प्रोजेक्ट में AppState का उपयोग शुरू कर सकते हैं: 41 | 42 | ```swift 43 | import AppState 44 | ``` 45 | 46 | ## Xcode 47 | 48 | यदि आप **AppState** को सीधे Xcode के माध्यम से जोड़ना पसंद करते हैं, तो इन चरणों का पालन करें: 49 | 50 | ### चरण 1: अपना Xcode प्रोजेक्ट खोलें 51 | 52 | अपना Xcode प्रोजेक्ट या कार्यक्षेत्र खोलें। 53 | 54 | ### चरण 2: एक स्विफ्ट पैकेज निर्भरता जोड़ें 55 | 56 | 1. प्रोजेक्ट नेविगेटर पर नेविगेट करें और अपनी प्रोजेक्ट फ़ाइल चुनें। 57 | 2. प्रोजेक्ट एडिटर में, अपना लक्ष्य चुनें, और फिर "स्विफ्ट पैकेज" टैब पर जाएं। 58 | 3. पैकेज निर्भरता जोड़ने के लिए "+" बटन पर क्लिक करें। 59 | 60 | ### चरण 3: रिपोजिटरी URL दर्ज करें 61 | 62 | "पैकेज रिपोजिटरी चुनें" संवाद में, निम्न URL दर्ज करें: `https://github.com/0xLeif/AppState.git` 63 | 64 | फिर "अगला" पर क्लिक करें। 65 | 66 | ### चरण 4: संस्करण निर्दिष्ट करें 67 | 68 | वह संस्करण चुनें जिसका आप उपयोग करना चाहते हैं। "अगले प्रमुख संस्करण तक" विकल्प का चयन करने और `2.0.0` को निचली सीमा के रूप में निर्दिष्ट करने की अनुशंसा की जाती है। फिर "अगला" पर क्लिक करें। 69 | 70 | ### चरण 5: पैकेज जोड़ें 71 | 72 | Xcode पैकेज लाएगा और आपको अपने लक्ष्य में **AppState** जोड़ने के विकल्प प्रस्तुत करेगा। सुनिश्चित करें कि आप सही लक्ष्य का चयन करते हैं और "समाप्त" पर क्लिक करें। 73 | 74 | ### चरण 6: अपने कोड में `AppState` आयात करें 75 | 76 | अब आप अपनी स्विफ्ट फ़ाइलों के शीर्ष पर **AppState** आयात कर सकते हैं: 77 | 78 | ```swift 79 | import AppState 80 | ``` 81 | 82 | ## अगले चरण 83 | 84 | AppState स्थापित होने के साथ, आप अपने प्रोजेक्ट में प्रमुख विशेषताओं को कैसे लागू करें यह देखने के लिए [उपयोग अवलोकन](usage-overview.md) पर जा सकते हैं। 85 | 86 | --- 87 | यह अनुवाद स्वचालित रूप से उत्पन्न किया गया था और इसमें त्रुटियाँ हो सकती हैं। यदि आप एक देशी वक्ता हैं, तो हम एक पुल अनुरोध के माध्यम से सुधारों में आपके योगदान की सराहना करेंगे। 88 | -------------------------------------------------------------------------------- /documentation/hi/starting-to-use-syncstate.md: -------------------------------------------------------------------------------- 1 | SyncState का उपयोग करने के लिए, आपको सबसे पहले अपने Xcode प्रोजेक्ट में iCloud क्षमताओं और अधिकारों को सेट करना होगा। प्रक्रिया में आपका मार्गदर्शन करने के लिए यहाँ एक परिचय दिया गया है: 2 | 3 | ### iCloud क्षमताओं को सेट करना: 4 | 5 | 1. अपना Xcode प्रोजेक्ट खोलें और macOS और iOS दोनों लक्ष्यों के लिए बंडल पहचानकर्ताओं को अपने से मेल खाने के लिए समायोजित करें। 6 | 2. इसके बाद, आपको अपने प्रोजेक्ट में iCloud क्षमता को जोड़ना होगा। ऐसा करने के लिए, प्रोजेक्ट नेविगेटर में अपने प्रोजेक्ट का चयन करें, फिर अपने लक्ष्य का चयन करें। संपादक क्षेत्र के शीर्ष पर टैब बार में, "Signing & Capabilities" पर क्लिक करें। 7 | 3. क्षमताएँ फलक में, iCloud पंक्ति में स्विच पर क्लिक करके iCloud चालू करें। आपको स्विच को चालू स्थिति में ले जाते हुए देखना चाहिए। 8 | 4. एक बार जब आप iCloud सक्षम कर लेते हैं, तो आपको कुंजी-मूल्य संग्रहण सक्षम करने की आवश्यकता होती है। आप "Key-Value storage" चेकबॉक्स को चेक करके ऐसा कर सकते हैं। 9 | 10 | ### अधिकारों को अद्यतन करना: 11 | 12 | 1. अब आपको अपनी अधिकार फ़ाइल को अद्यतन करने की आवश्यकता होगी। अपने लक्ष्य के लिए अधिकार फ़ाइल खोलें। 13 | 2. सुनिश्चित करें कि iCloud कुंजी-मूल्य स्टोर मान आपकी अद्वितीय कुंजी-मूल्य स्टोर ID से मेल खाता है। आपकी अद्वितीय ID को `$(TeamIdentifierPrefix)` प्रारूप का पालन करना चाहिए। डिफ़ॉल्ट मान कुछ इस तरह होना चाहिए, `$(TeamIdentifierPrefix)$(CFBundleIdentifier)`। यह एकल प्लेटफ़ॉर्म ऐप्स के लिए ठीक है, लेकिन यदि आपका ऐप कई Apple OS पर है, तो यह महत्वपूर्ण है कि कुंजी-मूल्य स्टोर ID के भाग दोनों लक्ष्यों के लिए समान हों। 14 | 15 | ### उपकरणों को कॉन्फ़िगर करना: 16 | 17 | प्रोजेक्ट को कॉन्फ़िगर करने के अलावा, आपको उन उपकरणों को भी तैयार करने की आवश्यकता है जो प्रोजेक्ट को चलाएंगे। 18 | 19 | - सुनिश्चित करें कि iCloud ड्राइव iOS और macOS दोनों उपकरणों पर सक्षम है। 20 | - एक ही iCloud खाते का उपयोग करके दोनों उपकरणों में लॉग इन करें। 21 | 22 | यदि आपके कोई प्रश्न हैं या कोई समस्या आती है, तो बेझिझक संपर्क करें या कोई समस्या सबमिट करें। 23 | 24 | --- 25 | यह अनुवाद स्वचालित रूप से उत्पन्न किया गया था और इसमें त्रुटियाँ हो सकती हैं। यदि आप एक देशी वक्ता हैं, तो हम एक पुल अनुरोध के माध्यम से सुधारों में आपके योगदान की सराहना करेंगे। 26 | -------------------------------------------------------------------------------- /documentation/hi/syncstate-implementation.md: -------------------------------------------------------------------------------- 1 | # AppState में SyncState का कार्यान्वयन 2 | 3 | यह मार्गदर्शिका बताती है कि आप अपने एप्लिकेशन में SyncState को कैसे सेट अप और कॉन्फ़िगर करें, जिसमें iCloud क्षमताओं को सेट अप करना और संभावित सीमाओं को समझना शामिल है। 4 | 5 | ## 1. iCloud क्षमताओं को सेट अप करना 6 | 7 | अपने एप्लिकेशन में SyncState का उपयोग करने के लिए, आपको पहले अपने प्रोजेक्ट में iCloud को सक्षम करना होगा और की-वैल्यू स्टोरेज को कॉन्फ़िगर करना होगा। 8 | 9 | ### iCloud और की-वैल्यू स्टोरेज को सक्षम करने के चरण: 10 | 11 | 1. अपना Xcode प्रोजेक्ट खोलें और अपनी प्रोजेक्ट सेटिंग्स पर नेविगेट करें। 12 | 2. "Signing & Capabilities" टैब के तहत, अपना लक्ष्य (iOS या macOS) चुनें। 13 | 3. "+ Capability" बटन पर क्लिक करें और सूची से "iCloud" चुनें। 14 | 4. iCloud सेटिंग्स के तहत "Key-Value storage" विकल्प को सक्षम करें। यह आपके ऐप को iCloud का उपयोग करके थोड़ी मात्रा में डेटा संग्रहीत और सिंक करने की अनुमति देता है। 15 | 16 | ### एंटाइटेलमेंट फ़ाइल कॉन्फ़िगरेशन: 17 | 18 | 1. अपने Xcode प्रोजेक्ट में, अपने ऐप के लिए **एंटाइटेलमेंट फ़ाइल** ढूंढें या बनाएं। 19 | 2. सुनिश्चित करें कि iCloud की-वैल्यू स्टोर एंटाइटेलमेंट फ़ाइल में सही iCloud कंटेनर के साथ सही ढंग से सेट है। 20 | 21 | एंटाइटेलमेंट फ़ाइल में उदाहरण: 22 | 23 | ```xml 24 | com.apple.developer.ubiquity-kvstore-identifier 25 | $(TeamIdentifierPrefix)com.yourdomain.app 26 | ``` 27 | 28 | सुनिश्चित करें कि स्ट्रिंग मान आपके प्रोजेक्ट से जुड़े iCloud कंटेनर से मेल खाता है। 29 | 30 | ## 2. अपने एप्लिकेशन में SyncState का उपयोग करना 31 | 32 | एक बार iCloud सक्षम हो जाने पर, आप उपकरणों के बीच डेटा को सिंक्रनाइज़ करने के लिए अपने एप्लिकेशन में `SyncState` का उपयोग कर सकते हैं। 33 | 34 | ### उपयोग में SyncState का उदाहरण: 35 | 36 | ```swift 37 | import AppState 38 | import SwiftUI 39 | 40 | extension Application { 41 | var syncValue: SyncState { 42 | syncState(id: "syncValue") 43 | } 44 | } 45 | 46 | struct ContentView: View { 47 | @SyncState(\.syncValue) private var syncValue: Int? 48 | 49 | var body: some View { 50 | VStack { 51 | if let syncValue = syncValue { 52 | Text("SyncValue: \(syncValue)") 53 | } else { 54 | Text("No SyncValue") 55 | } 56 | 57 | Button("Update SyncValue") { 58 | syncValue = Int.random(in: 0..<100) 59 | } 60 | } 61 | } 62 | } 63 | ``` 64 | 65 | इस उदाहरण में, सिंक स्थिति iCloud में सहेजी जाएगी और उसी iCloud खाते में लॉग इन किए गए उपकरणों के बीच सिंक्रनाइज़ की जाएगी। 66 | 67 | ## 3. सीमाएँ और सर्वोत्तम प्रथाएँ 68 | 69 | SyncState `NSUbiquitousKeyValueStore` का उपयोग करता है, जिसकी कुछ सीमाएँ हैं: 70 | 71 | - **भंडारण सीमा**: SyncState थोड़ी मात्रा में डेटा के लिए डिज़ाइन किया गया है। कुल भंडारण सीमा 1 एमबी है, और प्रत्येक की-वैल्यू जोड़ी लगभग 1 एमबी तक सीमित है। 72 | - **सिंक्रनाइज़ेशन**: SyncState में किए गए परिवर्तन उपकरणों के बीच तुरंत सिंक्रनाइज़ नहीं होते हैं। सिंक्रनाइज़ेशन में थोड़ी देरी हो सकती है, और iCloud सिंकिंग कभी-कभी नेटवर्क स्थितियों से प्रभावित हो सकती है। 73 | 74 | ### सर्वोत्तम प्रथाएं: 75 | 76 | - **छोटे डेटा के लिए SyncState का उपयोग करें**: सुनिश्चित करें कि केवल उपयोगकर्ता वरीयताओं या सेटिंग्स जैसे छोटे डेटा को SyncState का उपयोग करके सिंक्रनाइज़ किया जाए। 77 | - **SyncState विफलताओं को शालीनता से संभालें**: संभावित सिंक देरी या विफलताओं के लिए डिफ़ॉल्ट मानों या त्रुटि प्रबंधन तंत्रों का उपयोग करें। 78 | 79 | ## 4. निष्कर्ष 80 | 81 | iCloud को ठीक से कॉन्फ़िगर करके और SyncState की सीमाओं को समझकर, आप उपकरणों के बीच डेटा को सिंक करने के लिए इसकी शक्ति का लाभ उठा सकते हैं। सुनिश्चित करें कि आप iCloud भंडारण सीमाओं के साथ संभावित समस्याओं से बचने के लिए केवल छोटे, महत्वपूर्ण डेटा के टुकड़ों के लिए SyncState का उपयोग करते हैं। 82 | 83 | --- 84 | यह अनुवाद स्वचालित रूप से उत्पन्न किया गया था और इसमें त्रुटियाँ हो सकती हैं। यदि आप एक देशी वक्ता हैं, तो हम एक पुल अनुरोध के माध्यम से सुधारों में आपके योगदान की सराहना करेंगे। 85 | -------------------------------------------------------------------------------- /documentation/hi/usage-constant.md: -------------------------------------------------------------------------------- 1 | # स्थिरांक का उपयोग 2 | 3 | **AppState** लाइब्रेरी में `Constant` आपके एप्लिकेशन की स्थिति के भीतर मानों तक केवल-पढ़ने के लिए पहुँच प्रदान करता है। यह `Slice` के समान काम करता है, लेकिन यह सुनिश्चित करता है कि एक्सेस किए गए मान अपरिवर्तनीय हैं। यह `Constant` को उन मानों तक पहुँचने के लिए आदर्श बनाता है जो अन्यथा परिवर्तनीय हो सकते हैं लेकिन कुछ संदर्भों में केवल-पढ़ने के लिए बने रहना चाहिए। 4 | 5 | ## मुख्य विशेषताएँ 6 | 7 | - **केवल-पढ़ने के लिए पहुँच**: स्थिरांक परिवर्तनीय स्थिति तक पहुँच प्रदान करते हैं, लेकिन मानों को संशोधित नहीं किया जा सकता है। 8 | - **एप्लिकेशन के लिए स्कोप्ड**: `Slice` की तरह, `Constant` को `Application` एक्सटेंशन के भीतर परिभाषित किया गया है और स्थिति के विशिष्ट भागों तक पहुँचने के लिए स्कोप किया गया है। 9 | - **थ्रेड-सेफ**: `Constant` समवर्ती वातावरण में स्थिति तक सुरक्षित पहुँच सुनिश्चित करता है। 10 | 11 | ## उपयोग का उदाहरण 12 | 13 | ### एप्लिकेशन में एक स्थिरांक को परिभाषित करना 14 | 15 | यहाँ बताया गया है कि केवल-पढ़ने के लिए मान तक पहुँचने के लिए `Application` एक्सटेंशन में `Constant` को कैसे परिभाषित करें: 16 | 17 | ```swift 18 | import AppState 19 | import SwiftUI 20 | 21 | struct ExampleValue { 22 | var username: String? 23 | var isLoading: Bool 24 | let value: String 25 | var mutableValue: String 26 | } 27 | 28 | extension Application { 29 | var exampleValue: State { 30 | state( 31 | initial: ExampleValue( 32 | username: "Leif", 33 | isLoading: false, 34 | value: "value", 35 | mutableValue: "" 36 | ) 37 | ) 38 | } 39 | } 40 | ``` 41 | 42 | ### SwiftUI व्यू में स्थिरांक तक पहुँचना 43 | 44 | SwiftUI व्यू में, आप केवल-पढ़ने के तरीके से स्थिर स्थिति तक पहुँचने के लिए `@Constant` प्रॉपर्टी रैपर का उपयोग कर सकते हैं: 45 | 46 | ```swift 47 | import AppState 48 | import SwiftUI 49 | 50 | struct ExampleView: View { 51 | @Constant(\.exampleValue, \.value) var constantValue: String 52 | 53 | var body: some View { 54 | Text("स्थिर मान: \(constantValue)") 55 | } 56 | } 57 | ``` 58 | 59 | ### परिवर्तनीय स्थिति तक केवल-पढ़ने के लिए पहुँच 60 | 61 | भले ही मान कहीं और परिवर्तनीय हो, जब `@Constant` के माध्यम से पहुँचा जाता है, तो मान अपरिवर्तनीय हो जाता है: 62 | 63 | ```swift 64 | import AppState 65 | import SwiftUI 66 | 67 | struct ExampleView: View { 68 | @Constant(\.exampleValue, \.mutableValue) var constantMutableValue: String 69 | 70 | var body: some View { 71 | Text("केवल-पढ़ने के लिए परिवर्तनीय मान: \(constantMutableValue)") 72 | } 73 | } 74 | ``` 75 | 76 | ## सर्वोत्तम प्रथाएं 77 | 78 | - **केवल-पढ़ने के लिए पहुँच के लिए उपयोग करें**: स्थिति के उन हिस्सों तक पहुँचने के लिए `Constant` का उपयोग करें जिन्हें कुछ संदर्भों में संशोधित नहीं किया जाना चाहिए, भले ही वे कहीं और परिवर्तनीय हों। 79 | - **थ्रेड-सेफ**: अन्य AppState घटकों की तरह, `Constant` स्थिति तक थ्रेड-सुरक्षित पहुँच सुनिश्चित करता है। 80 | - **वैकल्पिक मानों के लिए `OptionalConstant` का उपयोग करें**: यदि आप जिस स्थिति के हिस्से तक पहुँच रहे हैं वह `nil` हो सकता है, तो मान की अनुपस्थिति को सुरक्षित रूप से संभालने के लिए `OptionalConstant` का उपयोग करें। 81 | 82 | ## निष्कर्ष 83 | 84 | `Constant` और `OptionalConstant` आपकी ऐप की स्थिति के विशिष्ट भागों तक केवल-पढ़ने के तरीके से पहुँचने का एक कुशल तरीका प्रदान करते हैं। वे यह सुनिश्चित करते हैं कि जो मान अन्यथा परिवर्तनीय हो सकते हैं, उन्हें एक दृश्य के भीतर एक्सेस किए जाने पर अपरिवर्तनीय माना जाता है, जिससे आपके कोड में सुरक्षा और स्पष्टता सुनिश्चित होती है। 85 | 86 | --- 87 | यह अनुवाद स्वचालित रूप से उत्पन्न किया गया था और इसमें त्रुटियाँ हो सकती हैं। यदि आप एक देशी वक्ता हैं, तो हम एक पुल अनुरोध के माध्यम से सुधारों में आपके योगदान की सराहना करेंगे। 88 | -------------------------------------------------------------------------------- /documentation/hi/usage-securestate.md: -------------------------------------------------------------------------------- 1 | # SecureState का उपयोग 2 | 3 | `SecureState` **AppState** लाइब्रेरी का एक घटक है जो आपको संवेदनशील डेटा को कीचेन में सुरक्षित रूप से संग्रहीत करने की अनुमति देता है। यह टोकन या पासवर्ड जैसे छोटे डेटा के टुकड़ों को संग्रहीत करने के लिए सबसे उपयुक्त है जिन्हें सुरक्षित रूप से एन्क्रिप्ट करने की आवश्यकता होती है। 4 | 5 | ## मुख्य विशेषताएँ 6 | 7 | - **सुरक्षित भंडारण**: `SecureState` का उपयोग करके संग्रहीत डेटा को एन्क्रिप्ट किया जाता है और कीचेन में सुरक्षित रूप से सहेजा जाता है। 8 | - **स्थायित्व**: डेटा ऐप लॉन्च के बीच स्थायी रहता है, जिससे संवेदनशील मानों की सुरक्षित पुनर्प्राप्ति की अनुमति मिलती है। 9 | 10 | ## कीचेन की सीमाएँ 11 | 12 | हालांकि `SecureState` बहुत सुरक्षित है, इसकी कुछ सीमाएँ हैं: 13 | 14 | - **सीमित भंडारण आकार**: कीचेन को छोटे डेटा के टुकड़ों के लिए डिज़ाइन किया गया है। यह बड़ी फ़ाइलों या डेटासेट को संग्रहीत करने के लिए उपयुक्त नहीं है। 15 | - **प्रदर्शन**: कीचेन तक पहुँचना `UserDefaults` तक पहुँचने की तुलना में धीमा है, इसलिए इसका उपयोग केवल तभी करें जब संवेदनशील डेटा को सुरक्षित रूप से संग्रहीत करना आवश्यक हो। 16 | 17 | ## उपयोग का उदाहरण 18 | 19 | ### एक सुरक्षित टोकन संग्रहीत करना 20 | 21 | ```swift 22 | import AppState 23 | import SwiftUI 24 | 25 | extension Application { 26 | var userToken: SecureState { 27 | secureState(id: "userToken") 28 | } 29 | } 30 | 31 | struct SecureView: View { 32 | @SecureState(\.userToken) var userToken: String? 33 | 34 | var body: some View { 35 | VStack { 36 | if let token = userToken { 37 | Text("उपयोगकर्ता टोकन: \(token)") 38 | } else { 39 | Text("कोई टोकन नहीं मिला।") 40 | } 41 | Button("टोकन सेट करें") { 42 | userToken = "secure_token_value" 43 | } 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | ### सुरक्षित डेटा की अनुपस्थिति को संभालना 50 | 51 | पहली बार कीचेन तक पहुँचते समय, या यदि कोई मान संग्रहीत नहीं है, तो `SecureState` `nil` लौटाएगा। सुनिश्चित करें कि आप इस परिदृश्य को ठीक से संभालते हैं: 52 | 53 | ```swift 54 | if let token = userToken { 55 | print("टोकन: \(token)") 56 | } else { 57 | print("कोई टोकन उपलब्ध नहीं है।") 58 | } 59 | ``` 60 | 61 | ## सर्वोत्तम प्रथाएं 62 | 63 | - **छोटे डेटा के लिए उपयोग करें**: कीचेन का उपयोग टोकन, पासवर्ड और कुंजी जैसी छोटी संवेदनशील जानकारी संग्रहीत करने के लिए किया जाना चाहिए। 64 | - **बड़े डेटासेट से बचें**: यदि आपको बड़े डेटासेट को सुरक्षित रूप से संग्रहीत करने की आवश्यकता है, तो फ़ाइल-आधारित एन्क्रिप्शन या अन्य तरीकों का उपयोग करने पर विचार करें, क्योंकि कीचेन बड़े डेटा भंडारण के लिए डिज़ाइन नहीं किया गया है। 65 | - **शून्य को संभालें**: हमेशा उन मामलों को संभालें जहां कोई मान मौजूद न होने पर कीचेन `nil` लौटाता है। 66 | 67 | --- 68 | यह अनुवाद स्वचालित रूप से उत्पन्न किया गया था और इसमें त्रुटियाँ हो सकती हैं। यदि आप एक देशी वक्ता हैं, तो हम एक पुल अनुरोध के माध्यम से सुधारों में आपके योगदान की सराहना करेंगे। 69 | -------------------------------------------------------------------------------- /documentation/hi/usage-storedstate.md: -------------------------------------------------------------------------------- 1 | # StoredState का उपयोग 2 | 3 | `StoredState` **AppState** लाइब्रेरी का एक घटक है जो आपको `UserDefaults` का उपयोग करके थोड़ी मात्रा में डेटा संग्रहीत और बनाए रखने की अनुमति देता है। यह हल्के, गैर-संवेदनशील डेटा को संग्रहीत करने के लिए आदर्श है जिसे ऐप लॉन्च के बीच बनाए रखा जाना चाहिए। 4 | 5 | ## अवलोकन 6 | 7 | - **StoredState** `UserDefaults` के शीर्ष पर बनाया गया है, जिसका अर्थ है कि यह थोड़ी मात्रा में डेटा (जैसे उपयोगकर्ता वरीयताएँ या ऐप सेटिंग्स) संग्रहीत करने के लिए तेज़ और कुशल है। 8 | - **StoredState** में सहेजा गया डेटा ऐप सत्रों के बीच बना रहता है, जिससे आप लॉन्च पर एप्लिकेशन स्थिति को पुनर्स्थापित कर सकते हैं। 9 | 10 | ### मुख्य विशेषताएँ 11 | 12 | - **स्थायी भंडारण**: `StoredState` में सहेजा गया डेटा ऐप लॉन्च के बीच उपलब्ध रहता है। 13 | - **छोटे डेटा का प्रबंधन**: वरीयताओं, टॉगल या छोटी कॉन्फ़िगरेशन जैसे हल्के डेटा के लिए सबसे अच्छा उपयोग किया जाता है। 14 | - **थ्रेड-सेफ**: `StoredState` यह सुनिश्चित करता है कि समवर्ती वातावरण में डेटा एक्सेस सुरक्षित रहे। 15 | 16 | ## उपयोग का उदाहरण 17 | 18 | ### एक StoredState को परिभाषित करना 19 | 20 | आप `Application` ऑब्जेक्ट का विस्तार करके और स्थिति संपत्ति की घोषणा करके एक **StoredState** को परिभाषित कर सकते हैं: 21 | 22 | ```swift 23 | import AppState 24 | 25 | extension Application { 26 | var userPreferences: StoredState { 27 | storedState(initial: "Default Preferences", id: "userPreferences") 28 | } 29 | } 30 | ``` 31 | 32 | ### एक दृश्य में StoredState तक पहुँचना और उसे संशोधित करना 33 | 34 | आप `@StoredState` प्रॉपर्टी रैपर का उपयोग करके SwiftUI दृश्यों के भीतर **StoredState** मानों तक पहुँच और उन्हें संशोधित कर सकते हैं: 35 | 36 | ```swift 37 | import AppState 38 | import SwiftUI 39 | 40 | struct PreferencesView: View { 41 | @StoredState(\.userPreferences) var userPreferences: String 42 | 43 | var body: some View { 44 | VStack { 45 | Text("वरीयताएँ: \(userPreferences)") 46 | Button("वरीयताएँ अपडेट करें") { 47 | userPreferences = "Updated Preferences" 48 | } 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | ## डेटा माइग्रेशन को संभालना 55 | 56 | जैसे-जैसे आपका ऐप विकसित होता है, आप **StoredState** के माध्यम से बनाए गए मॉडल को अपडेट कर सकते हैं। अपने डेटा मॉडल को अपडेट करते समय, पश्चगामी संगतता सुनिश्चित करें। उदाहरण के लिए, आप नए फ़ील्ड जोड़ सकते हैं या माइग्रेशन को संभालने के लिए अपने मॉडल का संस्करण बना सकते हैं। 57 | 58 | अधिक जानकारी के लिए, [माइग्रेशन संबंधी विचार मार्गदर्शिका](migration-considerations.md) देखें। 59 | 60 | ### माइग्रेशन संबंधी विचार 61 | 62 | - **नए गैर-वैकल्पिक फ़ील्ड जोड़ना**: सुनिश्चित करें कि नए फ़ील्ड या तो वैकल्पिक हैं या पश्चगामी संगतता बनाए रखने के लिए डिफ़ॉल्ट मान हैं। 63 | - **मॉडल का संस्करण बनाना**: यदि आपका डेटा मॉडल समय के साथ बदलता है, तो अपने बनाए गए डेटा के विभिन्न संस्करणों को प्रबंधित करने के लिए एक `version` फ़ील्ड शामिल करें। 64 | 65 | ## सर्वोत्तम प्रथाएं 66 | 67 | - **छोटे डेटा के लिए उपयोग करें**: हल्के, गैर-संवेदनशील डेटा को संग्रहीत करें जिसे उपयोगकर्ता वरीयताओं की तरह ऐप लॉन्च के बीच बनाए रखने की आवश्यकता है। 68 | - **बड़े डेटा के लिए विकल्पों पर विचार करें**: यदि आपको बड़ी मात्रा में डेटा संग्रहीत करने की आवश्यकता है, तो इसके बजाय **FileState** का उपयोग करने पर विचार करें। 69 | 70 | ## निष्कर्ष 71 | 72 | **StoredState** `UserDefaults` का उपयोग करके डेटा के छोटे टुकड़ों को बनाए रखने का एक सरल और कुशल तरीका है। यह वरीयताओं और अन्य छोटी सेटिंग्स को ऐप लॉन्च के बीच सहेजने के लिए आदर्श है, जबकि सुरक्षित पहुँच और SwiftUI के साथ आसान एकीकरण प्रदान करता है। अधिक जटिल दृढ़ता आवश्यकताओं के लिए, **AppState** की अन्य विशेषताओं जैसे [FileState](usage-filestate.md) या [SyncState](usage-syncstate.md) का अन्वेषण करें। 73 | 74 | --- 75 | यह अनुवाद स्वचालित रूप से उत्पन्न किया गया था और इसमें त्रुटियाँ हो सकती हैं। यदि आप एक देशी वक्ता हैं, तो हम एक पुल अनुरोध के माध्यम से सुधारों में आपके योगदान की सराहना करेंगे। 76 | -------------------------------------------------------------------------------- /documentation/pt/contributing.md: -------------------------------------------------------------------------------- 1 | # Contribuindo para o AppState 2 | 3 | Obrigado por considerar contribuir para o **AppState**! Suas contribuições ajudam a tornar este projeto melhor para todos. 4 | 5 | ## Como Contribuir 6 | 7 | ### 1. Relatando Bugs 8 | 9 | Se você encontrar algum bug, por favor, abra uma issue no GitHub. Ao relatar um bug, por favor, inclua: 10 | 11 | - Um título claro e descritivo. 12 | - Uma descrição detalhada do bug, incluindo os passos para reproduzi-lo. 13 | - O comportamento esperado e o que realmente aconteceu. 14 | - A versão do **AppState** que você está usando. 15 | - Quaisquer capturas de tela ou logs relevantes. 16 | 17 | ### 2. Sugerindo Funcionalidades 18 | 19 | Novas ideias são bem-vindas! Se você tem uma funcionalidade que gostaria de ver adicionada ao **AppState**, por favor, abra uma issue e descreva: 20 | 21 | - O problema que a funcionalidade resolveria. 22 | - Como você acha que a funcionalidade deveria funcionar. 23 | - Qualquer contexto ou exemplos adicionais que ajudem a ilustrar sua ideia. 24 | 25 | ### 3. Enviando Pull Requests 26 | 27 | Se você gostaria de contribuir com código para o **AppState**, siga estes passos: 28 | 29 | 1. **Faça um Fork do Repositório**: Crie um fork pessoal do repositório **AppState** no GitHub. 30 | 2. **Clone o Seu Fork**: Clone o seu fork para a sua máquina local: 31 | ```bash 32 | git clone https://github.com/your-username/AppState.git 33 | ``` 34 | 3. **Crie um Novo Ramo**: Crie um novo ramo para a sua funcionalidade ou correção de bug: 35 | ```bash 36 | git checkout -b meu-ramo-de-funcionalidade 37 | ``` 38 | 4. **Faça as Alterações**: Implemente as suas alterações no novo ramo. 39 | 5. **Teste as Suas Alterações**: Garanta que as suas alterações passam em todos os testes. Adicione novos testes, se necessário. 40 | 6. **Faça o Commit das Suas Alterações**: Faça o commit das suas alterações com uma mensagem de commit descritiva: 41 | ```bash 42 | git commit -m "Adicionar a minha nova funcionalidade" 43 | ``` 44 | 7. **Envie para o GitHub**: Envie o seu ramo para o seu fork no GitHub: 45 | ```bash 46 | git push origin meu-ramo-de-funcionalidade 47 | ``` 48 | 8. **Crie um Pull Request**: Vá para o repositório **AppState** no GitHub e crie um pull request a partir do seu ramo. 49 | 50 | ### 4. Estilo de Código 51 | 52 | Por favor, siga as diretrizes de estilo de codificação usadas no projeto **AppState**. Um estilo de código consistente ajuda a tornar a base de código mais fácil de manter e revisar. 53 | 54 | ### 5. Licença 55 | 56 | Ao contribuir para o **AppState**, você concorda que as suas contribuições serão licenciadas sob a mesma licença que o projeto: [LICENÇA](https://github.com/0xLeif/AppState/blob/main/LICENSE). 57 | 58 | ## Obrigado! 59 | 60 | As suas contribuições são muito valorizadas e apreciadas. Obrigado por ajudar a melhorar o **AppState**! 61 | 62 | --- 63 | Esta tradução foi gerada automaticamente e pode conter erros. Se você é um falante nativo, agradecemos suas contribuições com correções por meio de um Pull Request. 64 | -------------------------------------------------------------------------------- /documentation/pt/faq.md: -------------------------------------------------------------------------------- 1 | # Perguntas Frequentes 2 | 3 | Este breve FAQ aborda questões comuns que os desenvolvedores podem ter ao usar o **AppState**. 4 | 5 | ## Como eu redefino um valor de estado? 6 | 7 | Para estados persistentes como `StoredState`, `FileState` e `SyncState`, você pode redefini-los para seus valores iniciais usando as funções estáticas `reset` no tipo `Application`. 8 | 9 | Por exemplo, para redefinir um `StoredState`: 10 | ```swift 11 | extension Application { 12 | var hasCompletedOnboarding: StoredState { storedState(initial: false, id: "onboarding_complete") } 13 | } 14 | 15 | // Em algum lugar do seu código 16 | Application.reset(storedState: \.hasCompletedOnboarding) 17 | ``` 18 | Isso redefinirá o valor no `UserDefaults` de volta para `false`. Funções `reset` semelhantes existem para `FileState`, `SyncState` e `SecureState`. 19 | 20 | Para um `State` não persistente, você pode redefini-lo da mesma forma que os estados persistentes: 21 | ```swift 22 | extension Application { 23 | var counter: State { state(initial: 0) } 24 | } 25 | 26 | // Em algum lugar do seu código 27 | Application.reset(\.counter) 28 | ``` 29 | 30 | ## Posso usar o AppState com tarefas assíncronas? 31 | 32 | Sim. Os valores de `State` e de dependência são thread-safe e funcionam perfeitamente com o Swift Concurrency. Você pode acessá-los e modificá-los dentro de funções `async` sem bloqueio adicional. 33 | 34 | ## Onde devo definir os estados e as dependências? 35 | 36 | Mantenha todos os seus estados e dependências em extensões de `Application`. Isso garante uma única fonte de verdade e facilita a descoberta de todos os valores disponíveis. 37 | 38 | ## O AppState é compatível com o Combine? 39 | 40 | Você pode usar o AppState junto com o Combine, fazendo a ponte entre as alterações de `State` e os publishers. Observe um valor de `State` e envie atualizações através de um `PassthroughSubject` ou outro publisher do Combine, se necessário. 41 | 42 | --- 43 | Esta tradução foi gerada automaticamente e pode conter erros. Se você é um falante nativo, agradecemos suas contribuições com correções por meio de um Pull Request. 44 | -------------------------------------------------------------------------------- /documentation/pt/installation.md: -------------------------------------------------------------------------------- 1 | # Guia de Instalação 2 | 3 | Este guia irá orientá-lo através do processo de instalação do **AppState** no seu projeto Swift usando o Swift Package Manager. 4 | 5 | ## Swift Package Manager 6 | 7 | O **AppState** pode ser facilmente integrado ao seu projeto usando o Swift Package Manager. Siga os passos abaixo para adicionar o **AppState** como uma dependência. 8 | 9 | ### Passo 1: Atualize o seu Arquivo `Package.swift` 10 | 11 | Adicione o **AppState** à seção `dependencies` do seu arquivo `Package.swift`: 12 | 13 | ```swift 14 | dependencies: [ 15 | .package(url: "https://github.com/0xLeif/AppState.git", from: "2.2.0") 16 | ] 17 | ``` 18 | 19 | ### Passo 2: Adicione o AppState ao seu Alvo 20 | 21 | Inclua o AppState nas dependências do seu alvo: 22 | 23 | ```swift 24 | .target( 25 | name: "YourTarget", 26 | dependencies: ["AppState"] 27 | ) 28 | ``` 29 | 30 | ### Passo 3: Compile o seu Projeto 31 | 32 | Depois de adicionar o AppState ao seu arquivo `Package.swift`, compile o seu projeto para buscar a dependência e integrá-la ao seu código-base. 33 | 34 | ``` 35 | swift build 36 | ``` 37 | 38 | ### Passo 4: Importe o AppState no seu Código 39 | 40 | Agora, você pode começar a usar o AppState no seu projeto importando-o no topo dos seus arquivos Swift: 41 | 42 | ```swift 43 | import AppState 44 | ``` 45 | 46 | ## Xcode 47 | 48 | Se você preferir adicionar o **AppState** diretamente através do Xcode, siga estes passos: 49 | 50 | ### Passo 1: Abra o seu Projeto Xcode 51 | 52 | Abra o seu projeto ou workspace do Xcode. 53 | 54 | ### Passo 2: Adicione uma Dependência de Pacote Swift 55 | 56 | 1. Navegue até o navegador de projetos e selecione o arquivo do seu projeto. 57 | 2. No editor de projetos, selecione o seu alvo e, em seguida, vá para a guia "Swift Packages". 58 | 3. Clique no botão "+" para adicionar uma dependência de pacote. 59 | 60 | ### Passo 3: Insira a URL do Repositório 61 | 62 | Na caixa de diálogo "Choose Package Repository", insira a seguinte URL: `https://github.com/0xLeif/AppState.git` 63 | 64 | Em seguida, clique em "Próximo". 65 | 66 | ### Passo 4: Especifique a Versão 67 | 68 | Escolha a versão que deseja usar. Recomenda-se selecionar a opção "Up to Next Major Version" e especificar `2.0.0` como o limite inferior. Em seguida, clique em "Próximo". 69 | 70 | ### Passo 5: Adicione o Pacote 71 | 72 | O Xcode buscará o pacote e apresentará opções para adicionar o **AppState** ao seu alvo. Certifique-se de selecionar o alvo correto e clique em "Finalizar". 73 | 74 | ### Passo 6: Importe o `AppState` no seu Código 75 | 76 | Agora você pode importar o **AppState** no topo dos seus arquivos Swift: 77 | 78 | ```swift 79 | import AppState 80 | ``` 81 | 82 | ## Próximos Passos 83 | 84 | Com o AppState instalado, você pode avançar para a [Visão Geral do Uso](usage-overview.md) para ver como implementar os principais recursos no seu projeto. 85 | 86 | --- 87 | Esta tradução foi gerada automaticamente e pode conter erros. Se você é um falante nativo, agradecemos suas contribuições com correções por meio de um Pull Request. 88 | -------------------------------------------------------------------------------- /documentation/pt/starting-to-use-syncstate.md: -------------------------------------------------------------------------------- 1 | Para utilizar o SyncState, você primeiro precisará configurar os recursos e direitos do iCloud em seu projeto do Xcode. Aqui está uma introdução para guiá-lo através do processo: 2 | 3 | ### Configurando os recursos do iCloud: 4 | 5 | 1. Abra seu projeto do Xcode e ajuste os identificadores de pacote para os destinos macOS e iOS para corresponderem aos seus. 6 | 2. Em seguida, você precisa adicionar o recurso iCloud ao seu projeto. Para fazer isso, selecione seu projeto no Navegador de projetos e, em seguida, selecione seu destino. Na barra de guias na parte superior da área do editor, clique em "Signing & Capabilities". 7 | 3. No painel Recursos, ative o iCloud clicando no botão na linha do iCloud. Você deve ver o botão mudar para a posição Ligado. 8 | 4. Depois de habilitar o iCloud, você precisa habilitar o armazenamento de valor-chave. Você pode fazer isso marcando a caixa de seleção "Armazenamento de valor-chave". 9 | 10 | ### Atualizando os direitos: 11 | 12 | 1. Agora você precisará atualizar seu arquivo de direitos. Abra o arquivo de direitos para o seu destino. 13 | 2. Certifique-se de que o valor do Repositório de Valor-Chave do iCloud corresponda ao seu ID de repositório de valor-chave exclusivo. Seu ID exclusivo deve seguir o formato `$(TeamIdentifierPrefix)`. O valor padrão deve ser algo como, `$(TeamIdentifierPrefix)$(CFBundleIdentifier)`. Isso é bom para aplicativos de plataforma única, mas se seu aplicativo estiver em vários sistemas operacionais da Apple, é importante que as partes do ID do repositório de valor-chave sejam as mesmas para ambos os destinos. 14 | 15 | ### Configurando os dispositivos: 16 | 17 | Além de configurar o projeto em si, você também precisa preparar os dispositivos que executarão o projeto. 18 | 19 | - Certifique-se de que o iCloud Drive esteja habilitado nos dispositivos iOS e macOS. 20 | - Faça login em ambos os dispositivos usando a mesma conta do iCloud. 21 | 22 | Se você tiver alguma dúvida ou encontrar algum problema, sinta-se à vontade para entrar em contato ou enviar um problema. 23 | 24 | --- 25 | Esta tradução foi gerada automaticamente e pode conter erros. Se você é um falante nativo, agradecemos suas contribuições com correções por meio de um Pull Request. 26 | -------------------------------------------------------------------------------- /documentation/pt/usage-constant.md: -------------------------------------------------------------------------------- 1 | # Uso de Constantes 2 | 3 | `Constant` na biblioteca **AppState** fornece acesso somente leitura a valores dentro do estado da sua aplicação. Funciona de forma semelhante a `Slice`, mas garante que os valores acessados sejam imutáveis. Isso torna `Constant` ideal para acessar valores que, de outra forma, poderiam ser mutáveis, mas que devem permanecer somente leitura em certos contextos. 4 | 5 | ## Principais Características 6 | 7 | - **Acesso Somente Leitura**: As constantes fornecem acesso ao estado mutável, mas os valores não podem ser modificados. 8 | - **Escopo para a Aplicação**: Assim como `Slice`, `Constant` é definido dentro da extensão `Application` e tem como escopo o acesso a partes específicas do estado. 9 | - **Seguro para Threads**: `Constant` garante o acesso seguro ao estado em ambientes concorrentes. 10 | 11 | ## Exemplo de Uso 12 | 13 | ### Definindo uma Constante na Aplicação 14 | 15 | Veja como definir uma `Constant` na extensão `Application` para acessar um valor somente leitura: 16 | 17 | ```swift 18 | import AppState 19 | import SwiftUI 20 | 21 | struct ExampleValue { 22 | var username: String? 23 | var isLoading: Bool 24 | let value: String 25 | var mutableValue: String 26 | } 27 | 28 | extension Application { 29 | var exampleValue: State { 30 | state( 31 | initial: ExampleValue( 32 | username: "Leif", 33 | isLoading: false, 34 | value: "value", 35 | mutableValue: "" 36 | ) 37 | ) 38 | } 39 | } 40 | ``` 41 | 42 | ### Acessando a Constante em uma Visualização SwiftUI 43 | 44 | Em uma visualização SwiftUI, você pode usar o property wrapper `@Constant` para acessar o estado constante de forma somente leitura: 45 | 46 | ```swift 47 | import AppState 48 | import SwiftUI 49 | 50 | struct ExampleView: View { 51 | @Constant(\.exampleValue, \.value) var constantValue: String 52 | 53 | var body: some View { 54 | Text("Valor Constante: \(constantValue)") 55 | } 56 | } 57 | ``` 58 | 59 | ### Acesso Somente Leitura a um Estado Mutável 60 | 61 | Mesmo que o valor seja mutável em outro lugar, quando acessado através de `@Constant`, o valor se torna imutável: 62 | 63 | ```swift 64 | import AppState 65 | import SwiftUI 66 | 67 | struct ExampleView: View { 68 | @Constant(\.exampleValue, \.mutableValue) var constantMutableValue: String 69 | 70 | var body: some View { 71 | Text("Valor Mutável Somente Leitura: \(constantMutableValue)") 72 | } 73 | } 74 | ``` 75 | 76 | ## Melhores Práticas 77 | 78 | - **Use para Acesso Somente Leitura**: Use `Constant` para acessar partes do estado que não devem ser modificadas em certos contextos, mesmo que sejam mutáveis em outro lugar. 79 | - **Seguro para Threads**: Assim como outros componentes do AppState, `Constant` garante o acesso seguro ao estado por threads. 80 | - **Use `OptionalConstant` para Valores Opcionais**: Se a parte do estado que você está acessando puder ser `nil`, use `OptionalConstant` para lidar com segurança com a ausência de um valor. 81 | 82 | ## Conclusão 83 | 84 | `Constant` e `OptionalConstant` fornecem uma maneira eficiente de acessar partes específicas do estado da sua aplicação de forma somente leitura. Eles garantem que valores que, de outra forma, poderiam ser mutáveis, sejam tratados como imutáveis quando acessados dentro de uma visualização, garantindo segurança e clareza no seu código. 85 | 86 | --- 87 | Esta tradução foi gerada automaticamente e pode conter erros. Se você é um falante nativo, agradecemos suas contribuições com correções por meio de um Pull Request. 88 | -------------------------------------------------------------------------------- /documentation/pt/usage-securestate.md: -------------------------------------------------------------------------------- 1 | # Uso do SecureState 2 | 3 | `SecureState` é um componente da biblioteca **AppState** que permite armazenar dados confidenciais de forma segura no Keychain. É mais adequado para armazenar pequenas porções de dados como tokens ou senhas que precisam ser criptografados com segurança. 4 | 5 | ## Principais Características 6 | 7 | - **Armazenamento Seguro**: Os dados armazenados usando `SecureState` são criptografados e salvos com segurança no Keychain. 8 | - **Persistência**: Os dados permanecem persistentes entre os lançamentos do aplicativo, permitindo a recuperação segura de valores confidenciais. 9 | 10 | ## Limitações do Keychain 11 | 12 | Embora o `SecureState` seja muito seguro, ele possui certas limitações: 13 | 14 | - **Tamanho de Armazenamento Limitado**: O Keychain é projetado para pequenas porções de dados. Não é adequado para armazenar arquivos grandes ou conjuntos de dados. 15 | - **Desempenho**: O acesso ao Keychain é mais lento do que o acesso ao `UserDefaults`, portanto, use-o apenas quando necessário para armazenar dados confidenciais com segurança. 16 | 17 | ## Exemplo de Uso 18 | 19 | ### Armazenando um Token Seguro 20 | 21 | ```swift 22 | import AppState 23 | import SwiftUI 24 | 25 | extension Application { 26 | var userToken: SecureState { 27 | secureState(id: "userToken") 28 | } 29 | } 30 | 31 | struct SecureView: View { 32 | @SecureState(\.userToken) var userToken: String? 33 | 34 | var body: some View { 35 | VStack { 36 | if let token = userToken { 37 | Text("Token do usuário: \(token)") 38 | } else { 39 | Text("Nenhum token encontrado.") 40 | } 41 | Button("Definir Token") { 42 | userToken = "secure_token_value" 43 | } 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | ### Lidando com a Ausência de Dados Seguros 50 | 51 | Ao acessar o Keychain pela primeira vez, ou se não houver valor armazenado, `SecureState` retornará `nil`. Certifique-se de lidar com este cenário adequadamente: 52 | 53 | ```swift 54 | if let token = userToken { 55 | print("Token: \(token)") 56 | } else { 57 | print("Nenhum token disponível.") 58 | } 59 | ``` 60 | 61 | ## Melhores Práticas 62 | 63 | - **Use para Dados Pequenos**: O Keychain deve ser usado para armazenar pequenas porções de informações confidenciais como tokens, senhas e chaves. 64 | - **Evite Grandes Conjuntos de Dados**: Se você precisar armazenar grandes conjuntos de dados com segurança, considere usar criptografia baseada em arquivos ou outros métodos, pois o Keychain não é projetado para armazenamento de grandes dados. 65 | - **Lide com nulo**: Sempre lide com os casos em que o Keychain retorna `nil` quando nenhum valor está presente. 66 | 67 | --- 68 | Esta tradução foi gerada automaticamente e pode conter erros. Se você é um falante nativo, agradecemos suas contribuições com correções por meio de um Pull Request. 69 | -------------------------------------------------------------------------------- /documentation/ru/contributing.md: -------------------------------------------------------------------------------- 1 | # Вклад в AppState 2 | 3 | Спасибо за то, что рассматриваете возможность внести свой вклад в **AppState**! Ваши вклады помогают сделать этот проект лучше для всех. 4 | 5 | ## Как внести вклад 6 | 7 | ### 1. Сообщение об ошибках 8 | 9 | Если вы столкнулись с какими-либо ошибками, пожалуйста, откройте issue на GitHub. При сообщении об ошибке, пожалуйста, включите: 10 | 11 | - Четкий и описательный заголовок. 12 | - Подробное описание ошибки, включая шаги для ее воспроизведения. 13 | - Ожидаемое поведение и то, что произошло на самом деле. 14 | - Версия **AppState**, которую вы используете. 15 | - Любые соответствующие скриншоты или журналы. 16 | 17 | ### 2. Предложение функций 18 | 19 | Новые идеи приветствуются! Если у вас есть функция, которую вы хотели бы видеть добавленной в **AppState**, пожалуйста, откройте issue и опишите: 20 | 21 | - Проблему, которую решит эта функция. 22 | - Как, по вашему мнению, должна работать эта функция. 23 | - Любой дополнительный контекст или примеры, которые помогут проиллюстрировать вашу идею. 24 | 25 | ### 3. Отправка Pull-запросов 26 | 27 | Если вы хотите внести свой вклад в код **AppState**, выполните следующие действия: 28 | 29 | 1. **Сделайте форк репозитория**: Создайте личный форк репозитория **AppState** на GitHub. 30 | 2. **Клонируйте свой форк**: Клонируйте свой форк на свой локальный компьютер: 31 | ```bash 32 | git clone https://github.com/your-username/AppState.git 33 | ``` 34 | 3. **Создайте новую ветку**: Создайте новую ветку для вашей функции или исправления ошибки: 35 | ```bash 36 | git checkout -b my-feature-branch 37 | ``` 38 | 4. **Внесите изменения**: Реализуйте свои изменения в новой ветке. 39 | 5. **Протестируйте свои изменения**: Убедитесь, что ваши изменения проходят все тесты. При необходимости добавьте новые тесты. 40 | 6. **Закоммитьте свои изменения**: Закоммитьте свои изменения с описательным сообщением коммита: 41 | ```bash 42 | git commit -m "Add my new feature" 43 | ``` 44 | 7. **Отправьте на GitHub**: Отправьте свою ветку в свой форк на GitHub: 45 | ```bash 46 | git push origin my-feature-branch 47 | ``` 48 | 8. **Создайте Pull-запрос**: Перейдите в репозиторий **AppState** на GitHub и создайте pull-запрос из вашей ветки. 49 | 50 | ### 4. Стиль кода 51 | 52 | Пожалуйста, следуйте рекомендациям по стилю кодирования, используемым в проекте **AppState**. Последовательный стиль кода помогает сделать кодовую базу более удобной для сопровождения и просмотра. 53 | 54 | ### 5. Лицензия 55 | 56 | Внося вклад в **AppState**, вы соглашаетесь с тем, что ваши вклады будут лицензироваться по той же лицензии, что и проект: [ЛИЦЕНЗИЯ](https://github.com/0xLeif/AppState/blob/main/LICENSE). 57 | 58 | ## Спасибо! 59 | 60 | Ваши вклады очень ценны и высоко ценятся. Спасибо за помощь в улучшении **AppState**! 61 | 62 | --- 63 | Этот перевод был сгенерирован автоматически и может содержать ошибки. Если вы носитель языка, мы будем признательны за ваши исправления через Pull Request. 64 | -------------------------------------------------------------------------------- /documentation/ru/faq.md: -------------------------------------------------------------------------------- 1 | # Часто задаваемые вопросы 2 | 3 | Этот краткий FAQ отвечает на распространенные вопросы, которые могут возникнуть у разработчиков при использовании **AppState**. 4 | 5 | ## Как сбросить значение состояния? 6 | 7 | Для постоянных состояний, таких как `StoredState`, `FileState` и `SyncState`, вы можете сбросить их до начальных значений, используя статические функции `reset` для типа `Application`. 8 | 9 | Например, чтобы сбросить `StoredState`: 10 | ```swift 11 | extension Application { 12 | var hasCompletedOnboarding: StoredState { storedState(initial: false, id: "onboarding_complete") } 13 | } 14 | 15 | // Где-то в вашем коде 16 | Application.reset(storedState: \.hasCompletedOnboarding) 17 | ``` 18 | Это сбросит значение в `UserDefaults` обратно на `false`. Аналогичные функции `reset` существуют для `FileState`, `SyncState` и `SecureState`. 19 | 20 | Для непостоянного `State` вы можете сбросить его так же, как и постоянные состояния: 21 | ```swift 22 | extension Application { 23 | var counter: State { state(initial: 0) } 24 | } 25 | 26 | // Где-то в вашем коде 27 | Application.reset(\.counter) 28 | ``` 29 | 30 | ## Могу ли я использовать AppState с асинхронными задачами? 31 | 32 | Да. Значения `State` и зависимостей являются поточно-безопасными и без проблем работают с Swift Concurrency. Вы можете получать к ним доступ и изменять их внутри функций `async` без дополнительной блокировки. 33 | 34 | ## Где мне следует определять состояния и зависимости? 35 | 36 | Храните все свои состояния и зависимости в расширениях `Application`. Это обеспечивает единый источник истины и упрощает обнаружение всех доступных значений. 37 | 38 | ## Совместим ли AppState с Combine? 39 | 40 | Вы можете использовать AppState вместе с Combine, связывая изменения `State` с издателями. Наблюдайте за значением `State` и при необходимости отправляйте обновления через `PassthroughSubject` или другой издатель Combine. 41 | 42 | --- 43 | Этот перевод был сгенерирован автоматически и может содержать ошибки. Если вы носитель языка, мы будем признательны за ваши исправления через Pull Request. 44 | -------------------------------------------------------------------------------- /documentation/ru/installation.md: -------------------------------------------------------------------------------- 1 | # Руководство по установке 2 | 3 | Это руководство проведет вас через процесс установки **AppState** в ваш проект Swift с помощью Swift Package Manager. 4 | 5 | ## Swift Package Manager 6 | 7 | **AppState** можно легко интегрировать в ваш проект с помощью Swift Package Manager. Следуйте приведенным ниже шагам, чтобы добавить **AppState** в качестве зависимости. 8 | 9 | ### Шаг 1: Обновите ваш файл `Package.swift` 10 | 11 | Добавьте **AppState** в раздел `dependencies` вашего файла `Package.swift`: 12 | 13 | ```swift 14 | dependencies: [ 15 | .package(url: "https://github.com/0xLeif/AppState.git", from: "2.2.0") 16 | ] 17 | ``` 18 | 19 | ### Шаг 2: Добавьте AppState в вашу цель 20 | 21 | Включите AppState в зависимости вашей цели: 22 | 23 | ```swift 24 | .target( 25 | name: "YourTarget", 26 | dependencies: ["AppState"] 27 | ) 28 | ``` 29 | 30 | ### Шаг 3: Соберите ваш проект 31 | 32 | После того, как вы добавили AppState в ваш файл `Package.swift`, соберите ваш проект, чтобы получить зависимость и интегрировать ее в вашу кодовую базу. 33 | 34 | ``` 35 | swift build 36 | ``` 37 | 38 | ### Шаг 4: Импортируйте AppState в ваш код 39 | 40 | Теперь вы можете начать использовать AppState в вашем проекте, импортировав его в верхней части ваших файлов Swift: 41 | 42 | ```swift 43 | import AppState 44 | ``` 45 | 46 | ## Xcode 47 | 48 | Если вы предпочитаете добавлять **AppState** напрямую через Xcode, выполните следующие действия: 49 | 50 | ### Шаг 1: Откройте ваш проект Xcode 51 | 52 | Откройте ваш проект или рабочее пространство Xcode. 53 | 54 | ### Шаг 2: Добавьте зависимость Swift Package 55 | 56 | 1. Перейдите в навигатор проекта и выберите файл вашего проекта. 57 | 2. В редакторе проекта выберите вашу цель, а затем перейдите на вкладку «Swift Packages». 58 | 3. Нажмите кнопку «+», чтобы добавить зависимость пакета. 59 | 60 | ### Шаг 3: Введите URL-адрес репозитория 61 | 62 | В диалоговом окне «Выбрать репозиторий пакетов» введите следующий URL-адрес: `https://github.com/0xLeif/AppState.git` 63 | 64 | Затем нажмите «Далее». 65 | 66 | ### Шаг 4: Укажите версию 67 | 68 | Выберите версию, которую вы хотите использовать. Рекомендуется выбрать опцию «До следующей основной версии» и указать `2.0.0` в качестве нижней границы. Затем нажмите «Далее». 69 | 70 | ### Шаг 5: Добавьте пакет 71 | 72 | Xcode получит пакет и предоставит вам варианты добавления **AppState** в вашу цель. Убедитесь, что вы выбрали правильную цель, и нажмите «Готово». 73 | 74 | ### Шаг 6: Импортируйте `AppState` в ваш код 75 | 76 | Теперь вы можете импортировать **AppState** в верхней части ваших файлов Swift: 77 | 78 | ```swift 79 | import AppState 80 | ``` 81 | 82 | ## Следующие шаги 83 | 84 | После установки AppState вы можете перейти к [Обзору использования](usage-overview.md), чтобы увидеть, как реализовать ключевые функции в вашем проекте. 85 | 86 | --- 87 | Этот перевод был сгенерирован автоматически и может содержать ошибки. Если вы носитель языка, мы будем признательны за ваши исправления через Pull Request. 88 | -------------------------------------------------------------------------------- /documentation/ru/starting-to-use-syncstate.md: -------------------------------------------------------------------------------- 1 | # Начало работы с SyncState 2 | 3 | Чтобы использовать SyncState, вам сначала необходимо настроить возможности и права iCloud в вашем проекте Xcode. Вот введение, которое проведет вас через этот процесс: 4 | 5 | ### Настройка возможностей iCloud: 6 | 7 | 1. Откройте свой проект Xcode и измените идентификаторы пакетов для целей macOS и iOS, чтобы они соответствовали вашим собственным. 8 | 2. Затем вам нужно добавить возможность iCloud в свой проект. Для этого выберите свой проект в навигаторе проектов, затем выберите свою цель. На панели вкладок в верхней части области редактора нажмите «Signing & Capabilities». 9 | 3. На панели «Возможности» включите iCloud, щелкнув переключатель в строке iCloud. Вы должны увидеть, как переключатель переместится в положение «Вкл.». 10 | 4. После включения iCloud вам необходимо включить хранилище «ключ-значение». Вы можете сделать это, установив флажок «Хранилище ключ-значение». 11 | 12 | ### Обновление прав: 13 | 14 | 1. Теперь вам нужно будет обновить файл прав. Откройте файл прав для вашей цели. 15 | 2. Убедитесь, что значение хранилища ключ-значение iCloud совпадает с вашим уникальным идентификатором хранилища ключ-значение. Ваш уникальный идентификатор должен соответствовать формату `$(TeamIdentifierPrefix)`. Значение по умолчанию должно быть примерно таким: `$(TeamIdentifierPrefix)$(CFBundleIdentifier)`. Это подходит для приложений для одной платформы, но если ваше приложение работает на нескольких ОС Apple, важно, чтобы части идентификатора хранилища ключ-значение были одинаковыми для обеих целей. 16 | 17 | ### Настройка устройств: 18 | 19 | Помимо настройки самого проекта, вам также необходимо подготовить устройства, на которых будет работать проект. 20 | 21 | - Убедитесь, что iCloud Drive включен как на устройствах iOS, так и на macOS. 22 | - Войдите на оба устройства, используя одну и ту же учетную запись iCloud. 23 | 24 | Если у вас есть какие-либо вопросы или вы столкнулись с какими-либо проблемами, не стесняйтесь обращаться к нам или отправлять отчет о проблеме. 25 | 26 | --- 27 | Этот перевод был сгенерирован автоматически и может содержать ошибки. Если вы носитель языка, мы будем признательны за ваши исправления через Pull Request. 28 | -------------------------------------------------------------------------------- /documentation/ru/usage-constant.md: -------------------------------------------------------------------------------- 1 | # Использование констант 2 | 3 | `Constant` в библиотеке **AppState** предоставляет доступ только для чтения к значениям в состоянии вашего приложения. Он работает аналогично `Slice`, но гарантирует, что доступные значения являются неизменяемыми. Это делает `Constant` идеальным для доступа к значениям, которые в противном случае могли бы быть изменяемыми, но должны оставаться только для чтения в определенных контекстах. 4 | 5 | ## Ключевые особенности 6 | 7 | - **Доступ только для чтения**: константы предоставляют доступ к изменяемому состоянию, но значения не могут быть изменены. 8 | - **Ограничено приложением**: как и `Slice`, `Constant` определяется в расширении `Application` и ограничен доступом к определенным частям состояния. 9 | - **Потокобезопасность**: `Constant` обеспечивает безопасный доступ к состоянию в многопоточных средах. 10 | 11 | ## Пример использования 12 | 13 | ### Определение константы в приложении 14 | 15 | Вот как вы определяете `Constant` в расширении `Application` для доступа к значению только для чтения: 16 | 17 | ```swift 18 | import AppState 19 | import SwiftUI 20 | 21 | struct ExampleValue { 22 | var username: String? 23 | var isLoading: Bool 24 | let value: String 25 | var mutableValue: String 26 | } 27 | 28 | extension Application { 29 | var exampleValue: State { 30 | state( 31 | initial: ExampleValue( 32 | username: "Leif", 33 | isLoading: false, 34 | value: "value", 35 | mutableValue: "" 36 | ) 37 | ) 38 | } 39 | } 40 | ``` 41 | 42 | ### Доступ к константе в представлении SwiftUI 43 | 44 | В представлении SwiftUI вы можете использовать обертку свойства `@Constant` для доступа к постоянному состоянию только для чтения: 45 | 46 | ```swift 47 | import AppState 48 | import SwiftUI 49 | 50 | struct ExampleView: View { 51 | @Constant(\.exampleValue, \.value) var constantValue: String 52 | 53 | var body: some View { 54 | Text("Постоянное значение: \(constantValue)") 55 | } 56 | } 57 | ``` 58 | 59 | ### Доступ только для чтения к изменяемому состоянию 60 | 61 | Даже если значение изменяемо в другом месте, при доступе через `@Constant` значение становится неизменяемым: 62 | 63 | ```swift 64 | import AppState 65 | import SwiftUI 66 | 67 | struct ExampleView: View { 68 | @Constant(\.exampleValue, \.mutableValue) var constantMutableValue: String 69 | 70 | var body: some View { 71 | Text("Изменяемое значение только для чтения: \(constantMutableValue)") 72 | } 73 | } 74 | ``` 75 | 76 | ## Лучшие практики 77 | 78 | - **Используйте для доступа только для чтения**: используйте `Constant` для доступа к частям состояния, которые не должны изменяться в определенных контекстах, даже если они изменяемы в другом месте. 79 | - **Потокобезопасность**: как и другие компоненты AppState, `Constant` обеспечивает потокобезопасный доступ к состоянию. 80 | - **Используйте `OptionalConstant` для необязательных значений**: если часть состояния, к которой вы обращаетесь, может быть `nil`, используйте `OptionalConstant` для безопасной обработки отсутствия значения. 81 | 82 | ## Заключение 83 | 84 | `Constant` и `OptionalConstant` предоставляют эффективный способ доступа к определенным частям состояния вашего приложения только для чтения. Они гарантируют, что значения, которые в противном случае могли бы быть изменяемыми, рассматриваются как неизменяемые при доступе к ним в представлении, обеспечивая безопасность и ясность в вашем коде. 85 | 86 | --- 87 | Этот перевод был сгенерирован автоматически и может содержать ошибки. Если вы носитель языка, мы будем признательны за ваши исправления через Pull Request. 88 | -------------------------------------------------------------------------------- /documentation/ru/usage-securestate.md: -------------------------------------------------------------------------------- 1 | # Использование SecureState 2 | 3 | `SecureState` — это компонент библиотеки **AppState**, который позволяет безопасно хранить конфиденциальные данные в связке ключей. Он лучше всего подходит для хранения небольших фрагментов данных, таких как токены или пароли, которые необходимо надежно зашифровать. 4 | 5 | ## Ключевые особенности 6 | 7 | - **Безопасное хранилище**: данные, хранящиеся с помощью `SecureState`, шифруются и безопасно сохраняются в связке ключей. 8 | - **Постоянство**: данные остаются постоянными между запусками приложения, что позволяет безопасно извлекать конфиденциальные значения. 9 | 10 | ## Ограничения связки ключей 11 | 12 | Хотя `SecureState` очень безопасен, у него есть определенные ограничения: 13 | 14 | - **Ограниченный размер хранилища**: связка ключей предназначена для небольших фрагментов данных. Она не подходит для хранения больших файлов или наборов данных. 15 | - **Производительность**: доступ к связке ключей медленнее, чем доступ к `UserDefaults`, поэтому используйте его только при необходимости безопасного хранения конфиденциальных данных. 16 | 17 | ## Пример использования 18 | 19 | ### Хранение безопасного токена 20 | 21 | ```swift 22 | import AppState 23 | import SwiftUI 24 | 25 | extension Application { 26 | var userToken: SecureState { 27 | secureState(id: "userToken") 28 | } 29 | } 30 | 31 | struct SecureView: View { 32 | @SecureState(\.userToken) var userToken: String? 33 | 34 | var body: some View { 35 | VStack { 36 | if let token = userToken { 37 | Text("Токен пользователя: \(token)") 38 | } else { 39 | Text("Токен не найден.") 40 | } 41 | Button("Установить токен") { 42 | userToken = "secure_token_value" 43 | } 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | ### Обработка отсутствия безопасных данных 50 | 51 | При первом доступе к связке ключей или если значение не сохранено, `SecureState` вернет `nil`. Убедитесь, что вы правильно обрабатываете этот сценарий: 52 | 53 | ```swift 54 | if let token = userToken { 55 | print("Токен: \(token)") 56 | } else { 57 | print("Токен недоступен.") 58 | } 59 | ``` 60 | 61 | ## Лучшие практики 62 | 63 | - **Используйте для небольших данных**: связку ключей следует использовать для хранения небольших фрагментов конфиденциальной информации, такой как токены, пароли и ключи. 64 | - **Избегайте больших наборов данных**: если вам необходимо безопасно хранить большие наборы данных, рассмотрите возможность использования шифрования на основе файлов или других методов, поскольку связка ключей не предназначена для хранения больших данных. 65 | - **Обрабатывайте nil**: всегда обрабатывайте случаи, когда связка ключей возвращает `nil` при отсутствии значения. 66 | 67 | --- 68 | Этот перевод был сгенерирован автоматически и может содержать ошибки. Если вы носитель языка, мы будем признательны за ваши исправления через Pull Request. 69 | -------------------------------------------------------------------------------- /documentation/zh-CN/best-practices.md: -------------------------------------------------------------------------------- 1 | # 使用 AppState 的最佳实践 2 | 3 | 本指南提供了帮助您在 Swift 应用程序中高效使用 AppState 的最佳实践。 4 | 5 | ## 1. 谨慎使用 AppState 6 | 7 | AppState 功能多样,适用于共享和本地化状态管理。它非常适合需要在多个组件之间共享、跨视图或用户会话持久化或在组件级别管理的数据。然而,过度使用可能导致不必要的复杂性。 8 | 9 | ### 建议: 10 | - 将 AppState 用于真正需要在整个应用程序范围内共享、在远距离组件之间共享或需要 AppState 特定持久化/同步功能的数据。 11 | - 对于单个 SwiftUI 视图或紧密视图层次结构中的本地状态,首选 SwiftUI 的内置工具,如 `@State`、`@StateObject`、`@ObservedObject` 或 `@EnvironmentObject`。 12 | 13 | ## 2. 保持 AppState 的整洁 14 | 15 | 随着应用程序的扩展,您的 AppState 可能会变得越来越复杂。定期审查和重构您的 AppState,以删除未使用的状态和依赖项。保持 AppState 的整洁可以使其更易于理解、维护和测试。 16 | 17 | ### 建议: 18 | - 定期审计您的 AppState,查找未使用或冗余的状态和依赖项。 19 | - 重构大型 AppState 结构,以保持其整洁和可管理性。 20 | 21 | ## 3. 测试您的 AppState 22 | 23 | 与应用程序的其他方面一样,确保您的 AppState 经过了彻底的测试。在测试期间,使用模拟依赖项将您的 AppState 与外部依赖项隔离开来,并确认应用程序的每个部分都按预期运行。 24 | 25 | ### 建议: 26 | - 使用 XCTest 或类似框架来测试 AppState 的行为和交互。 27 | - 模拟或存根依赖项,以确保 AppState 测试是隔离且可靠的。 28 | 29 | ## 4. 明智地使用 Slice 功能 30 | 31 | `Slice` 功能允许您访问 AppState 状态的特定部分,这对于处理大型复杂的状态结构非常有用。但是,要明智地使用此功能,以保持 AppState 的整洁和组织良好,避免不必要的切片,从而导致状态处理的碎片化。 32 | 33 | ### 建议: 34 | - 仅在需要访问单个组件的大型或嵌套状态时使用 `Slice`。 35 | - 避免过度切片状态,这可能导致混乱和碎片化的状态管理。 36 | 37 | ## 5. 使用常量来表示静态值 38 | 39 | `@Constant` 功能允许您定义可在整个应用程序中共享的只读常量。它对于在应用程序生命周期内保持不变的值(如配置设置或预定义数据)非常有用。常量可确保这些值不会被意外修改。 40 | 41 | ### 建议: 42 | - 对于保持不变的值,例如应用程序配置、环境变量或静态引用,请使用 `@Constant`。 43 | 44 | ## 6. 模块化您的 AppState 45 | 46 | 对于大型应用程序,可以考虑将您的 AppState 分解为更小、更易于管理的模块。每个模块可以有自己的状态和依赖项,然后将它们组合成整个 AppState。这可以使您的 AppState 更易于理解、测试和维护。 47 | 48 | ### 建议: 49 | - 将您的 `Application` 扩展组织到单独的 Swift 文件甚至单独的 Swift 模块中,按功能或领域分组。这自然地模块化了定义。 50 | - 在使用 `state(initial:feature:id:)` 等工厂方法定义状态或依赖项时,利用 `feature` 参数提供命名空间,例如 `state(initial: 0, feature: "UserProfile", id: "score")`。这有助于组织和防止在使用手动 ID 时的 ID 冲突。 51 | - 避免创建 `Application` 的多个实例。坚持扩展和使用共享的单例(`Application.shared`)。 52 | 53 | ## 7. 利用即时创建 54 | 55 | AppState 值是即时创建的,这意味着它们仅在被访问时才被实例化。这优化了内存使用,并确保 AppState 值仅在必要时才被创建。 56 | 57 | ### 建议: 58 | - 允许 AppState 值即时创建,而不是不必要地预加载所有状态和依赖项。 59 | 60 | ## 结论 61 | 62 | 每个应用程序都是独一无二的,因此这些最佳实践可能不适用于所有情况。在决定如何使用 AppState 时,请始终考虑您的应用程序的具体要求,并努力保持您的状态管理整洁、高效和经过良好测试。 63 | 64 | --- 65 | 该译文由机器自动生成,可能存在错误。如果您是母语使用者,我们期待您通过 Pull Request 提出修改建议。 66 | -------------------------------------------------------------------------------- /documentation/zh-CN/contributing.md: -------------------------------------------------------------------------------- 1 | # 为 AppState 做贡献 2 | 3 | 感谢您考虑为 **AppState** 做出贡献!您的贡献有助于让这个项目对每个人都更好。 4 | 5 | ## 如何贡献 6 | 7 | ### 1. 报告错误 8 | 9 | 如果您遇到任何错误,请在 GitHub 上提出一个问题。在报告错误时,请包括: 10 | 11 | - 一个清晰且描述性的标题。 12 | - 错误的详细描述,包括重现错误的步骤。 13 | - 预期的行为以及实际发生的情况。 14 | - 您正在使用的 **AppState** 的版本。 15 | - 任何相关的截图或日志。 16 | 17 | ### 2. 建议功能 18 | 19 | 欢迎提出新想法!如果您希望看到某个功能被添加到 **AppState** 中,请提出一个问题并描述: 20 | 21 | - 该功能将解决的问题。 22 | - 您认为该功能应该如何工作。 23 | - 任何有助于说明您的想法的额外上下文或示例。 24 | 25 | ### 3. 提交拉取请求 26 | 27 | 如果您想为 **AppState** 贡献代码,请按照以下步骤操作: 28 | 29 | 1. **Fork 仓库**:在 GitHub 上创建 **AppState** 仓库的个人分支。 30 | 2. **克隆您的分支**:将您的分支克隆到本地计算机: 31 | ```bash 32 | git clone https://github.com/your-username/AppState.git 33 | ``` 34 | 3. **创建一个新分支**:为您的功能或错误修复创建一个新分支: 35 | ```bash 36 | git checkout -b my-feature-branch 37 | ``` 38 | 4. **进行更改**:在新分支中实现您的更改。 39 | 5. **测试您的更改**:确保您的更改通过所有测试。如有必要,请添加新测试。 40 | 6. **提交您的更改**:使用描述性的提交消息提交您的更改: 41 | ```bash 42 | git commit -m "Add my new feature" 43 | ``` 44 | 7. **推送到 GitHub**:将您的分支推送到您的 GitHub 分支: 45 | ```bash 46 | git push origin my-feature-branch 47 | ``` 48 | 8. **创建拉取请求**:转到 GitHub 上的 **AppState** 仓库,并从您的分支创建一个拉取请求。 49 | 50 | ### 4. 代码风格 51 | 52 | 请遵循 **AppState** 项目中使用的编码风格指南。一致的代码风格有助于使代码库更易于维护和审查。 53 | 54 | ### 5. 许可证 55 | 56 | 通过为 **AppState** 做出贡献,您同意您的贡献将根据与项目相同的许可证进行许可:[许可证](https://github.com/0xLeif/AppState/blob/main/LICENSE)。 57 | 58 | ## 谢谢! 59 | 60 | 您的贡献非常有价值和赞赏。感谢您帮助改进 **AppState**! 61 | 62 | --- 63 | 该译文由机器自动生成,可能存在错误。如果您是母语使用者,我们期待您通过 Pull Request 提出修改建议。 64 | -------------------------------------------------------------------------------- /documentation/zh-CN/faq.md: -------------------------------------------------------------------------------- 1 | # 常见问题 2 | 3 | 这个简短的常见问题解答解决了开发人员在使用 **AppState** 时可能遇到的常见问题。 4 | 5 | ## 如何重置状态值? 6 | 7 | 对于像 `StoredState`、`FileState` 和 `SyncState` 这样的持久化状态,您可以使用 `Application` 类型上的静态 `reset` 函数将它们重置为初始值。 8 | 9 | 例如,要重置一个 `StoredState`: 10 | ```swift 11 | extension Application { 12 | var hasCompletedOnboarding: StoredState { storedState(initial: false, id: "onboarding_complete") } 13 | } 14 | 15 | // 在您的代码中的某个地方 16 | Application.reset(storedState: \.hasCompletedOnboarding) 17 | ``` 18 | 这将把 `UserDefaults` 中的值重置为 `false`。`FileState`、`SyncState` 和 `SecureState` 也存在类似的 `reset` 函数。 19 | 20 | 对于非持久化的 `State`,您可以像持久化状态一样重置它: 21 | ```swift 22 | extension Application { 23 | var counter: State { state(initial: 0) } 24 | } 25 | 26 | // 在您的代码中的某个地方 27 | Application.reset(\.counter) 28 | ``` 29 | 30 | ## 我可以在异步任务中使用 AppState 吗? 31 | 32 | 可以。`State` 和依赖项值是线程安全的,并且可以与 Swift Concurrency 无缝协作。您可以在 `async` 函数中访问和修改它们,而无需额外的锁定。 33 | 34 | ## 我应该在哪里定义状态和依赖项? 35 | 36 | 将您所有的状态和依赖项都放在 `Application` 扩展中。这确保了单一的真实来源,并使发现所有可用的值变得更加容易。 37 | 38 | ## AppState 与 Combine 兼容吗? 39 | 40 | 您可以通过将 `State` 的更改桥接到发布者来将 AppState 与 Combine 一起使用。观察一个 `State` 值,并在需要时通过 `PassthroughSubject` 或其他 Combine 发布者发送更新。 41 | 42 | --- 43 | 该译文由机器自动生成,可能存在错误。如果您是母语使用者,我们期待您通过 Pull Request 提出修改建议。 44 | -------------------------------------------------------------------------------- /documentation/zh-CN/installation.md: -------------------------------------------------------------------------------- 1 | # 安装指南 2 | 3 | 本指南将引导您完成使用 Swift Package Manager 将 **AppState** 安装到您的 Swift 项目中的过程。 4 | 5 | ## Swift Package Manager 6 | 7 | **AppState** 可以使用 Swift Package Manager 轻松集成到您的项目中。请按照以下步骤添加 **AppState** 作为依赖项。 8 | 9 | ### 步骤 1:更新您的 `Package.swift` 文件 10 | 11 | 将 **AppState** 添加到您的 `Package.swift` 文件的 `dependencies` 部分: 12 | 13 | ```swift 14 | dependencies: [ 15 | .package(url: "https://github.com/0xLeif/AppState.git", from: "2.2.0") 16 | ] 17 | ``` 18 | 19 | ### 步骤 2:将 AppState 添加到您的目标 20 | 21 | 在您的目标依赖项中包含 AppState: 22 | 23 | ```swift 24 | .target( 25 | name: "YourTarget", 26 | dependencies: ["AppState"] 27 | ) 28 | ``` 29 | 30 | ### 步骤 3:构建您的项目 31 | 32 | 将 AppState 添加到您的 `Package.swift` 文件后,构建您的项目以获取依赖项并将其集成到您的代码库中。 33 | 34 | ``` 35 | swift build 36 | ``` 37 | 38 | ### 步骤 4:在您的代码中导入 AppState 39 | 40 | 现在,您可以通过在 Swift 文件的顶部导入 AppState 来开始在您的项目中使用它: 41 | 42 | ```swift 43 | import AppState 44 | ``` 45 | 46 | ## Xcode 47 | 48 | 如果您希望直接通过 Xcode 添加 **AppState**,请按照以下步骤操作: 49 | 50 | ### 步骤 1:打开您的 Xcode 项目 51 | 52 | 打开您的 Xcode 项目或工作区。 53 | 54 | ### 步骤 2:添加 Swift 包依赖项 55 | 56 | 1. 导航到项目导航器并选择您的项目文件。 57 | 2. 在项目编辑器中,选择您的目标,然后转到“Swift Packages”选项卡。 58 | 3. 单击“+”按钮以添加包依赖项。 59 | 60 | ### 步骤 3:输入仓库 URL 61 | 62 | 在“选择包仓库”对话框中,输入以下 URL:`https://github.com/0xLeif/AppState.git` 63 | 64 | 然后单击“下一步”。 65 | 66 | ### 步骤 4:指定版本 67 | 68 | 选择您要使用的版本。建议选择“直到下一个主要版本”选项,并将 `2.0.0` 指定为下限。然后单击“下一步”。 69 | 70 | ### 步骤 5:添加包 71 | 72 | Xcode 将获取该包并为您提供将 **AppState** 添加到您的目标的选项。请确保选择正确的目标,然后单击“完成”。 73 | 74 | ### 步骤 6:在您的代码中导入 `AppState` 75 | 76 | 现在,您可以在 Swift 文件的顶部导入 **AppState**: 77 | 78 | ```swift 79 | import AppState 80 | ``` 81 | 82 | ## 后续步骤 83 | 84 | 安装 AppState 后,您可以转到[用法概述](usage-overview.md)以了解如何在您的项目中实现关键功能。 85 | 86 | --- 87 | 该译文由机器自动生成,可能存在错误。如果您是母语使用者,我们期待您通过 Pull Request 提出修改建议。 88 | -------------------------------------------------------------------------------- /documentation/zh-CN/migration-considerations.md: -------------------------------------------------------------------------------- 1 | # 迁移注意事项 2 | 3 | 在更新您的数据模型时,特别是对于持久化或同步的数据,您需要处理向后兼容性,以避免在加载旧数据时出现潜在问题。以下是一些需要牢记的重要事项: 4 | 5 | ## 1. 添加非可选字段 6 | 如果您向模型中添加新的非可选字段,解码旧数据(其中不包含这些字段)可能会失败。为避免这种情况: 7 | - 考虑为新字段提供默认值。 8 | - 将新字段设为可选,以确保与旧版应用程序的兼容性。 9 | 10 | ### 示例: 11 | ```swift 12 | struct Settings: Codable { 13 | var text: String 14 | var isDarkMode: Bool 15 | var newField: String? // 新字段是可选的 16 | } 17 | ``` 18 | 19 | ## 2. 数据格式更改 20 | 如果您修改了模型的结构(例如,将类型从 `Int` 更改为 `String`),在读取旧数据时解码过程可能会失败。通过以下方式规划平滑迁移: 21 | - 创建迁移逻辑以将旧数据格式转换为新结构。 22 | - 使用 `Decodable` 的自定义初始化程序来处理旧数据并将其映射到您的新模型。 23 | 24 | ### 示例: 25 | ```swift 26 | struct Settings: Codable { 27 | var text: String 28 | var isDarkMode: Bool 29 | var version: Int 30 | 31 | // 针对旧版本的自定义解码逻辑 32 | init(from decoder: Decoder) throws { 33 | let container = try decoder.container(keyedBy: CodingKeys.self) 34 | self.text = try container.decode(String.self, forKey: .text) 35 | self.isDarkMode = try container.decode(Bool.self, forKey: .isDarkMode) 36 | self.version = (try? container.decode(Int.self, forKey: .version)) ?? 1 // 旧数据的默认值 37 | } 38 | } 39 | ``` 40 | 41 | ## 3. 处理已删除或已弃用的字段 42 | 如果您从模型中删除一个字段,请确保旧版本的应用程序仍然可以在不崩溃的情况下解码新数据。您可以: 43 | - 在解码时忽略多余的字段。 44 | - 使用自定义解码器来处理旧数据并正确管理已弃用的字段。 45 | 46 | ## 4. 版本化您的模型 47 | 48 | 版本化您的模型允许您随时间处理数据结构中的更改。通过在模型中保留版本号,您可以轻松地实现迁移逻辑,将旧数据格式转换为新数据格式。这种方法可确保您的应用程序可以处理旧数据结构,同时平滑地过渡到新版本。 49 | 50 | - **为什么版本化很重要**:当用户更新其应用程序时,他们的设备上可能仍保留有旧数据。版本化可帮助您的应用程序识别数据格式并应用正确的迁移逻辑。 51 | - **如何使用**:向您的模型添加一个 `version` 字段,并在解码过程中检查它,以确定是否需要迁移。 52 | 53 | ### 示例: 54 | ```swift 55 | struct Settings: Codable { 56 | var version: Int 57 | var text: String 58 | var isDarkMode: Bool 59 | 60 | // 处理特定于版本的解码逻辑 61 | init(from decoder: Decoder) throws { 62 | let container = try decoder.container(keyedBy: CodingKeys.self) 63 | self.version = try container.decode(Int.self, forKey: .version) 64 | self.text = try container.decode(String.self, forKey: .text) 65 | self.isDarkMode = try container.decode(Bool.self, forKey: .isDarkMode) 66 | 67 | // 如果从旧版本迁移,请在此处应用必要的转换 68 | if version < 2 { 69 | // 将旧数据迁移到新格式 70 | } 71 | } 72 | } 73 | ``` 74 | 75 | - **最佳实践**:从一开始就使用 `version` 字段。每次更新模型结构时,增加版本并处理必要的迁移逻辑。 76 | 77 | ## 5. 测试迁移 78 | 始终通过使用新版本的模型模拟加载旧数据来彻底测试您的迁移,以确保您的应用程序按预期运行。 79 | 80 | --- 81 | 该译文由机器自动生成,可能存在错误。如果您是母语使用者,我们期待您通过 Pull Request 提出修改建议。 82 | -------------------------------------------------------------------------------- /documentation/zh-CN/starting-to-use-syncstate.md: -------------------------------------------------------------------------------- 1 | # 开始使用 SyncState 2 | 3 | 要使用 SyncState,您首先需要在您的 Xcode 项目中设置 iCloud 功能和权利。以下是指导您完成该过程的简介: 4 | 5 | ### 设置 iCloud 功能: 6 | 7 | 1. 打开您的 Xcode 项目并调整 macOS 和 iOS 目标的捆绑包标识符以匹配您自己的。 8 | 2. 接下来,您需要将 iCloud 功能添加到您的项目中。为此,请在项目导航器中选择您的项目,然后选择您的目标。在编辑器区域顶部的选项卡栏中,单击“Signing & Capabilities”。 9 | 3. 在“功能”窗格中,通过单击 iCloud 行中的开关打开 iCloud。您应该会看到开关移动到“打开”位置。 10 | 4. 启用 iCloud 后,您需要启用键值存储。您可以通过选中“键值存储”复选框来执行此操作。 11 | 12 | ### 更新权利: 13 | 14 | 1. 您现在需要更新您的权利文件。打开您目标的功能文件。 15 | 2. 确保 iCloud 键值存储值与您的唯一键值存储 ID 匹配。您的唯一 ID 应遵循 `$(TeamIdentifierPrefix)` 的格式。默认值应类似于 `$(TeamIdentifierPrefix)$(CFBundleIdentifier)`。这对于单平台应用程序来说很好,但如果您的应用程序在多个 Apple 操作系统上,则键值存储 ID 部分对于两个目标都相同非常重要。 16 | 17 | ### 配置设备: 18 | 19 | 除了配置项目本身之外,您还需要准备将运行该项目的设备。 20 | 21 | - 确保在 iOS 和 macOS 设备上都启用了 iCloud 云盘。 22 | - 使用相同的 iCloud 帐户登录两台设备。 23 | 24 | 如果您有任何疑问或遇到任何问题,请随时与我们联系或提交问题。 25 | 26 | --- 27 | 该译文由机器自动生成,可能存在错误。如果您是母语使用者,我们期待您通过 Pull Request 提出修改建议。 28 | -------------------------------------------------------------------------------- /documentation/zh-CN/syncstate-implementation.md: -------------------------------------------------------------------------------- 1 | # AppState 中的 SyncState 实现 2 | 3 | 本指南介绍了如何在您的应用程序中设置和配置 SyncState,包括设置 iCloud 功能和了解潜在的限制。 4 | 5 | ## 1. 设置 iCloud 功能 6 | 7 | 要在您的应用程序中使用 SyncState,您首先需要在您的项目中启用 iCloud 并配置键值存储。 8 | 9 | ### 启用 iCloud 和键值存储的步骤: 10 | 11 | 1. 打开您的 Xcode 项目并导航到您的项目设置。 12 | 2. 在“Signing & Capabilities”选项卡下,选择您的目标(iOS 或 macOS)。 13 | 3. 单击“+ Capability”按钮,然后从列表中选择“iCloud”。 14 | 4. 在 iCloud 设置下启用“Key-Value storage”选项。这允许您的应用程序使用 iCloud 存储和同步少量数据。 15 | 16 | ### Entitlements 文件配置: 17 | 18 | 1. 在您的 Xcode 项目中,找到或创建您应用程序的 **entitlements 文件**。 19 | 2. 确保在 entitlements 文件中正确设置了 iCloud 键值存储,并使用了正确的 iCloud 容器。 20 | 21 | entitlements 文件中的示例: 22 | 23 | ```xml 24 | com.apple.developer.ubiquity-kvstore-identifier 25 | $(TeamIdentifierPrefix)com.yourdomain.app 26 | ``` 27 | 28 | 确保字符串值与您的项目关联的 iCloud 容器匹配。 29 | 30 | ## 2. 在您的应用程序中使用 SyncState 31 | 32 | 启用 iCloud 后,您可以在您的应用程序中使用 `SyncState` 来跨设备同步数据。 33 | 34 | ### SyncState 使用示例: 35 | 36 | ```swift 37 | import AppState 38 | import SwiftUI 39 | 40 | extension Application { 41 | var syncValue: SyncState { 42 | syncState(id: "syncValue") 43 | } 44 | } 45 | 46 | struct ContentView: View { 47 | @SyncState(\.syncValue) private var syncValue: Int? 48 | 49 | var body: some View { 50 | VStack { 51 | if let syncValue = syncValue { 52 | Text("SyncValue: \(syncValue)") 53 | } else { 54 | Text("No SyncValue") 55 | } 56 | 57 | Button("Update SyncValue") { 58 | syncValue = Int.random(in: 0..<100) 59 | } 60 | } 61 | } 62 | } 63 | ``` 64 | 65 | 在此示例中,同步状态将保存到 iCloud 并在登录到同一 iCloud 帐户的设备之间同步。 66 | 67 | ## 3. 限制和最佳实践 68 | 69 | SyncState 使用 `NSUbiquitousKeyValueStore`,它有一些限制: 70 | 71 | - **存储限制**:SyncState 专为少量数据而设计。总存储限制为 1 MB,每个键值对限制在 1 MB 左右。 72 | - **同步**:对 SyncState 所做的更改不会立即在设备之间同步。同步可能会有轻微的延迟,并且 iCloud 同步有时可能会受到网络条件的影响。 73 | 74 | ### 最佳实践: 75 | 76 | - **将 SyncState 用于小数据**:确保仅使用 SyncState 同步用户偏好或设置等小数据。 77 | - **优雅地处理 SyncState 失败**:使用默认值或错误处理机制来解决潜在的同步延迟或失败。 78 | 79 | ## 4. 结论 80 | 81 | 通过正确配置 iCloud 并了解 SyncState 的限制,您可以利用其功能来跨设备同步数据。确保您仅将 SyncState 用于小的、关键的数据片段,以避免 iCloud 存储限制的潜在问题。 82 | 83 | --- 84 | 该译文由机器自动生成,可能存在错误。如果您是母语使用者,我们期待您通过 Pull Request 提出修改建议。 85 | -------------------------------------------------------------------------------- /documentation/zh-CN/usage-constant.md: -------------------------------------------------------------------------------- 1 | # 常量用法 2 | 3 | **AppState** 库中的 `Constant` 提供了对应用程序状态中值的只读访问。它的工作方式类似于 `Slice`,但确保所访问的值是不可变的。这使得 `Constant` 非常适合访问在某些上下文中应保持只读但在其他地方可能是可变的值。 4 | 5 | ## 主要功能 6 | 7 | - **只读访问**:常量提供对可变状态的访问,但值不能被修改。 8 | - **作用域限定于应用程序**:与 `Slice` 一样,`Constant` 在 `Application` 扩展中定义,并作用域限定于访问状态的特定部分。 9 | - **线程安全**:`Constant` 确保在并发环境中安全地访问状态。 10 | 11 | ## 用法示例 12 | 13 | ### 在应用程序中定义常量 14 | 15 | 以下是如何在 `Application` 扩展中定义 `Constant` 以访问只读值: 16 | 17 | ```swift 18 | import AppState 19 | import SwiftUI 20 | 21 | struct ExampleValue { 22 | var username: String? 23 | var isLoading: Bool 24 | let value: String 25 | var mutableValue: String 26 | } 27 | 28 | extension Application { 29 | var exampleValue: State { 30 | state( 31 | initial: ExampleValue( 32 | username: "Leif", 33 | isLoading: false, 34 | value: "value", 35 | mutableValue: "" 36 | ) 37 | ) 38 | } 39 | } 40 | ``` 41 | 42 | ### 在 SwiftUI 视图中访问常量 43 | 44 | 在 SwiftUI 视图中,您可以使用 `@Constant` 属性包装器以只读方式访问常量状态: 45 | 46 | ```swift 47 | import AppState 48 | import SwiftUI 49 | 50 | struct ExampleView: View { 51 | @Constant(\.exampleValue, \.value) var constantValue: String 52 | 53 | var body: some View { 54 | Text("常量值: \(constantValue)") 55 | } 56 | } 57 | ``` 58 | 59 | ### 对可变状态的只读访问 60 | 61 | 即使该值在其他地方是可变的,当通过 `@Constant` 访问时,该值也变为不可变的: 62 | 63 | ```swift 64 | import AppState 65 | import SwiftUI 66 | 67 | struct ExampleView: View { 68 | @Constant(\.exampleValue, \.mutableValue) var constantMutableValue: String 69 | 70 | var body: some View { 71 | Text("只读可变值: \(constantMutableValue)") 72 | } 73 | } 74 | ``` 75 | 76 | ## 最佳实践 77 | 78 | - **用于只读访问**:使用 `Constant` 访问在某些上下文中不应修改的状态部分,即使它们在其他地方是可变的。 79 | - **线程安全**:与其他 AppState 组件一样,`Constant` 确保对状态的线程安全访问。 80 | - **对可选值使用 `OptionalConstant`**:如果您正在访问的状态部分可能为 `nil`,请使用 `OptionalConstant` 来安全地处理值的缺失。 81 | 82 | ## 结论 83 | 84 | `Constant` 和 `OptionalConstant` 提供了一种以只读方式访问应用程序状态特定部分的有效方法。它们确保在视图中访问时,可能在其他地方是可变的值被视为不可变的,从而确保代码的安全性和清晰性。 85 | 86 | --- 87 | 该译文由机器自动生成,可能存在错误。如果您是母语使用者,我们期待您通过 Pull Request 提出修改建议。 88 | -------------------------------------------------------------------------------- /documentation/zh-CN/usage-filestate.md: -------------------------------------------------------------------------------- 1 | # FileState 用法 2 | 3 | `FileState` 是 **AppState** 库的一个组件,允许您使用文件系统存储和检索持久性数据。它对于存储需要在应用程序启动之间保存并在需要时恢复的大型数据或复杂对象非常有用。 4 | 5 | ## 主要功能 6 | 7 | - **持久性存储**:使用 `FileState` 存储的数据在应用程序启动之间保持持久。 8 | - **大型数据处理**:与 `StoredState` 不同,`FileState` 非常适合处理较大或更复杂的数据。 9 | - **线程安全**:与其他 AppState 组件一样,`FileState` 确保在并发环境中安全地访问数据。 10 | 11 | ## 用法示例 12 | 13 | ### 使用 FileState 存储和检索数据 14 | 15 | 以下是如何在 `Application` 扩展中定义 `FileState` 以存储和检索大型对象: 16 | 17 | ```swift 18 | import AppState 19 | import SwiftUI 20 | 21 | struct UserProfile: Codable { 22 | var name: String 23 | var age: Int 24 | } 25 | 26 | extension Application { 27 | @MainActor 28 | var userProfile: FileState { 29 | fileState(initial: UserProfile(name: "Guest", age: 25), filename: "userProfile") 30 | } 31 | } 32 | 33 | struct FileStateExampleView: View { 34 | @FileState(\.userProfile) var userProfile: UserProfile 35 | 36 | var body: some View { 37 | VStack { 38 | Text("姓名: \(userProfile.name), 年龄: \(userProfile.age)") 39 | Button("更新个人资料") { 40 | userProfile = UserProfile(name: "UpdatedName", age: 30) 41 | } 42 | } 43 | } 44 | } 45 | ``` 46 | 47 | ### 使用 FileState 处理大型数据 48 | 49 | 当您需要处理较大数据集或对象时,`FileState` 可确保数据高效地存储在应用程序的文件系统中。这对于缓存或离线存储等场景非常有用。 50 | 51 | ```swift 52 | import AppState 53 | import SwiftUI 54 | 55 | extension Application { 56 | @MainActor 57 | var largeDataset: FileState<[String]> { 58 | fileState(initial: [], filename: "largeDataset") 59 | } 60 | } 61 | 62 | struct LargeDataView: View { 63 | @FileState(\.largeDataset) var largeDataset: [String] 64 | 65 | var body: some View { 66 | List(largeDataset, id: \.self) { item in 67 | Text(item) 68 | } 69 | } 70 | } 71 | ``` 72 | 73 | ### 迁移注意事项 74 | 75 | 在更新数据模型时,考虑潜在的迁移挑战非常重要,尤其是在使用 **StoredState**、**FileState** 或 **SyncState** 处理持久化数据时。如果没有适当的迁移处理,添加新字段或修改数据格式等更改可能会在加载旧数据时导致问题。 76 | 77 | 以下是一些需要牢记的关键点: 78 | - **添加新的非可选字段**:确保新字段是可选的或具有默认值,以保持向后兼容性。 79 | - **处理数据格式更改**:如果模型的结构发生变化,请实现自定义解码逻辑以支持旧格式。 80 | - **版本化您的模型**:在您的模型中使用 `version` 字段以帮助进行迁移,并根据数据版本应用逻辑。 81 | 82 | 要了解有关如何管理迁移和避免潜在问题的更多信息,请参阅[迁移注意事项指南](migration-considerations.md)。 83 | 84 | 85 | ## 最佳实践 86 | 87 | - **用于大型或复杂数据**:如果要存储大型数据或复杂对象,`FileState` 优于 `StoredState`。 88 | - **线程安全访问**:与 **AppState** 的其他组件一样,`FileState` 确保即使在多个任务与存储的数据交互时也能安全地访问数据。 89 | - **与 Codable 结合使用**:在使用自定义数据类型时,请确保它们符合 `Codable`,以简化与文件系统的编码和解码。 90 | 91 | ## 结论 92 | 93 | `FileState` 是在您的应用程序中处理持久性数据的强大工具,允许您以线程安全和持久的方式存储和检索较大或更复杂的对象。它与 Swift 的 `Codable` 协议无缝协作,确保您的数据可以轻松地序列化和反序列化以进行长期存储。 94 | 95 | --- 96 | 该译文由机器自动生成,可能存在错误。如果您是母语使用者,我们期待您通过 Pull Request 提出修改建议。 97 | -------------------------------------------------------------------------------- /documentation/zh-CN/usage-observeddependency.md: -------------------------------------------------------------------------------- 1 | # ObservedDependency 用法 2 | 3 | `ObservedDependency` 是 **AppState** 库的一个组件,允许您使用符合 `ObservableObject` 的依赖项。当您希望依赖项通知您的 SwiftUI 视图有关更改时,这非常有用,使您的视图具有反应性和动态性。 4 | 5 | ## 主要功能 6 | 7 | - **可观察的依赖项**:使用符合 `ObservableObject` 的依赖项,允许依赖项在其状态更改时自动更新您的视图。 8 | - **反应式 UI 更新**:当观察到的依赖项发布更改时,SwiftUI 视图会自动更新。 9 | - **线程安全**:与其他 AppState 组件一样,`ObservedDependency` 确保对观察到的依赖项进行线程安全的访问。 10 | 11 | ## 用法示例 12 | 13 | ### 定义可观察的依赖项 14 | 15 | 以下是如何在 `Application` 扩展中将可观察的服务定义为依赖项: 16 | 17 | ```swift 18 | import AppState 19 | import SwiftUI 20 | 21 | @MainActor 22 | class ObservableService: ObservableObject { 23 | @Published var count: Int = 0 24 | } 25 | 26 | extension Application { 27 | @MainActor 28 | var observableService: Dependency { 29 | dependency(ObservableService()) 30 | } 31 | } 32 | ``` 33 | 34 | ### 在 SwiftUI 视图中使用观察到的依赖项 35 | 36 | 在您的 SwiftUI 视图中,您可以使用 `@ObservedDependency` 属性包装器访问可观察的依赖项。观察到的对象在其状态更改时会自动更新视图。 37 | 38 | ```swift 39 | import AppState 40 | import SwiftUI 41 | 42 | struct ObservedDependencyExampleView: View { 43 | @ObservedDependency(\.observableService) var service: ObservableService 44 | 45 | var body: some View { 46 | VStack { 47 | Text("计数: \(service.count)") 48 | Button("增加计数") { 49 | service.count += 1 50 | } 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | ### 测试用例 57 | 58 | 以下测试用例演示了与 `ObservedDependency` 的交互: 59 | 60 | ```swift 61 | import XCTest 62 | @testable import AppState 63 | 64 | @MainActor 65 | fileprivate class ObservableService: ObservableObject { 66 | @Published var count: Int 67 | 68 | init() { 69 | count = 0 70 | } 71 | } 72 | 73 | fileprivate extension Application { 74 | @MainActor 75 | var observableService: Dependency { 76 | dependency(ObservableService()) 77 | } 78 | } 79 | 80 | @MainActor 81 | fileprivate struct ExampleDependencyWrapper { 82 | @ObservedDependency(\.observableService) var service 83 | 84 | func test() { 85 | service.count += 1 86 | } 87 | } 88 | 89 | final class ObservedDependencyTests: XCTestCase { 90 | @MainActor 91 | func testDependency() async { 92 | let example = ExampleDependencyWrapper() 93 | 94 | XCTAssertEqual(example.service.count, 0) 95 | 96 | example.test() 97 | 98 | XCTAssertEqual(example.service.count, 1) 99 | } 100 | } 101 | ``` 102 | 103 | ### 反应式 UI 更新 104 | 105 | 由于依赖项符合 `ObservableObject`,因此对其状态的任何更改都将触发 SwiftUI 视图中的 UI 更新。您可以将状态直接绑定到 UI 元素,例如 `Picker`: 106 | 107 | ```swift 108 | import AppState 109 | import SwiftUI 110 | 111 | struct ReactiveView: View { 112 | @ObservedDependency(\.observableService) var service: ObservableService 113 | 114 | var body: some View { 115 | Picker("选择计数", selection: $service.count) { 116 | ForEach(0..<10) { count in 117 | Text("\(count)").tag(count) 118 | } 119 | } 120 | } 121 | } 122 | ``` 123 | 124 | ## 最佳实践 125 | 126 | - **用于可观察的服务**:当您的依赖项需要通知视图有关更改时,`ObservedDependency` 是理想的选择,特别是对于提供数据或状态更新的服务。 127 | - **利用已发布的属性**:确保您的依赖项使用 `@Published` 属性来触发 SwiftUI 视图中的更新。 128 | - **线程安全**:与其他 AppState 组件一样,`ObservedDependency` 确保对可观察服务的线程安全访问和修改。 129 | 130 | ## 结论 131 | 132 | `ObservedDependency` 是在您的应用程序中管理可观察依赖项的强大工具。通过利用 Swift 的 `ObservableObject` 协议,它确保您的 SwiftUI 视图保持反应性并与服务或资源中的更改保持同步。 133 | 134 | --- 135 | 该译文由机器自动生成,可能存在错误。如果您是母语使用者,我们期待您通过 Pull Request 提出修改建议。 136 | -------------------------------------------------------------------------------- /documentation/zh-CN/usage-securestate.md: -------------------------------------------------------------------------------- 1 | # SecureState 用法 2 | 3 | `SecureState` 是 **AppState** 库的一个组件,允许您将敏感数据安全地存储在钥匙串中。它最适合存储需要安全加密的小块数据,例如令牌或密码。 4 | 5 | ## 主要功能 6 | 7 | - **安全存储**:使用 `SecureState` 存储的数据经过加密并安全地保存在钥匙串中。 8 | - **持久性**:数据在应用程序启动之间保持持久,允许安全地检索敏感值。 9 | 10 | ## 钥匙串限制 11 | 12 | 虽然 `SecureState` 非常安全,但它有一定的限制: 13 | 14 | - **有限的存储大小**:钥匙串专为小块数据而设计。它不适合存储大文件或数据集。 15 | - **性能**:访问钥匙串比访问 `UserDefaults` 慢,因此仅在需要安全存储敏感数据时才使用它。 16 | 17 | ## 用法示例 18 | 19 | ### 存储安全令牌 20 | 21 | ```swift 22 | import AppState 23 | import SwiftUI 24 | 25 | extension Application { 26 | var userToken: SecureState { 27 | secureState(id: "userToken") 28 | } 29 | } 30 | 31 | struct SecureView: View { 32 | @SecureState(\.userToken) var userToken: String? 33 | 34 | var body: some View { 35 | VStack { 36 | if let token = userToken { 37 | Text("用户令牌: \(token)") 38 | } else { 39 | Text("未找到令牌。") 40 | } 41 | Button("设置令牌") { 42 | userToken = "secure_token_value" 43 | } 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | ### 处理安全数据缺失的情况 50 | 51 | 首次访问钥匙串时,或者如果没有存储任何值,`SecureState` 将返回 `nil`。确保您正确处理这种情况: 52 | 53 | ```swift 54 | if let token = userToken { 55 | print("令牌: \(token)") 56 | } else { 57 | print("没有可用的令牌。") 58 | } 59 | ``` 60 | 61 | ## 最佳实践 62 | 63 | - **用于小数据**:钥匙串应用于存储小块敏感信息,如令牌、密码和密钥。 64 | - **避免大数据集**:如果您需要安全地存储大数据集,请考虑使用基于文件的加密或其他方法,因为钥匙串不是为大数据存储而设计的。 65 | - **处理 nil**:始终处理钥匙串在没有值时返回 `nil` 的情况。 66 | 67 | --- 68 | 该译文由机器自动生成,可能存在错误。如果您是母语使用者,我们期待您通过 Pull Request 提出修改建议。 69 | -------------------------------------------------------------------------------- /documentation/zh-CN/usage-slice.md: -------------------------------------------------------------------------------- 1 | # Slice 和 OptionalSlice 用法 2 | 3 | `Slice` 和 `OptionalSlice` 是 **AppState** 库的组件,允许您访问应用程序状态的特定部分。当您需要操作或观察更复杂状态结构的一部分时,它们非常有用。 4 | 5 | ## 概述 6 | 7 | - **Slice**:允许您访问和修改现有 `State` 对象的特定部分。 8 | - **OptionalSlice**:与 `Slice` 类似,但旨在处理可选值,例如当您的状态的一部分可能为 `nil` 或不为 `nil` 时。 9 | 10 | ### 主要功能 11 | 12 | - **选择性状态访问**:仅访问您需要的状态部分。 13 | - **线程安全**:与 **AppState** 中的其他状态管理类型一样,`Slice` 和 `OptionalSlice` 是线程安全的。 14 | - **反应性**:当状态的切片发生变化时,SwiftUI 视图会更新,确保您的 UI 保持反应性。 15 | 16 | ## 用法示例 17 | 18 | ### 使用 Slice 19 | 20 | 在此示例中,我们使用 `Slice` 访问和更新状态的特定部分——在本例中,是从存储在应用程序状态中的更复杂的 `User` 对象中获取 `username`。 21 | 22 | ```swift 23 | import AppState 24 | import SwiftUI 25 | 26 | struct User { 27 | var username: String 28 | var email: String 29 | } 30 | 31 | extension Application { 32 | var user: State { 33 | state(initial: User(username: "Guest", email: "guest@example.com")) 34 | } 35 | } 36 | 37 | struct SlicingView: View { 38 | @Slice(\.user, \.username) var username: String 39 | 40 | var body: some View { 41 | VStack { 42 | Text("用户名: \(username)") 43 | Button("更新用户名") { 44 | username = "NewUsername" 45 | } 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | ### 使用 OptionalSlice 52 | 53 | 当您的状态的一部分可能为 `nil` 时,`OptionalSlice` 非常有用。在此示例中,`User` 对象本身可能为 `nil`,因此我们使用 `OptionalSlice` 来安全地处理这种情况。 54 | 55 | ```swift 56 | import AppState 57 | import SwiftUI 58 | 59 | extension Application { 60 | var user: State { 61 | state(initial: nil) 62 | } 63 | } 64 | 65 | struct OptionalSlicingView: View { 66 | @OptionalSlice(\.user, \.username) var username: String? 67 | 68 | var body: some View { 69 | VStack { 70 | if let username = username { 71 | Text("用户名: \(username)") 72 | } else { 73 | Text("没有可用的用户名") 74 | } 75 | Button("设置用户名") { 76 | username = "UpdatedUsername" 77 | } 78 | } 79 | } 80 | } 81 | ``` 82 | 83 | ## 最佳实践 84 | 85 | - **对非可选状态使用 `Slice`**:如果您的状态保证为非可选,请使用 `Slice` 访问和更新它。 86 | - **对可选状态使用 `OptionalSlice`**:如果您的状态或状态的一部分是可选的,请使用 `OptionalSlice` 处理值可能为 `nil` 的情况。 87 | - **线程安全**:与 `State` 一样,`Slice` 和 `OptionalSlice` 是线程安全的,并且设计用于与 Swift 的并发模型一起工作。 88 | 89 | ## 结论 90 | 91 | `Slice` 和 `OptionalSlice` 提供了以线程安全的方式访问和修改状态特定部分的强大方法。通过利用这些组件,您可以在更复杂的应用程序中简化状态管理,确保您的 UI 保持反应性和最新。 92 | 93 | --- 94 | 该译文由机器自动生成,可能存在错误。如果您是母语使用者,我们期待您通过 Pull Request 提出修改建议。 95 | -------------------------------------------------------------------------------- /documentation/zh-CN/usage-state-dependency.md: -------------------------------------------------------------------------------- 1 | # 状态和依赖用法 2 | 3 | **AppState** 提供了强大的工具,用于管理应用程序范围的状态并将依赖项注入到 SwiftUI 视图中。通过集中管理您的状态和依赖项,您可以确保您的应用程序保持一致和可维护。 4 | 5 | ## 概述 6 | 7 | - **状态**:表示可在整个应用程序中共享的值。可以在您的 SwiftUI 视图中修改和观察状态值。 8 | - **依赖项**:表示可在 SwiftUI 视图中注入和访问的共享资源或服务。 9 | 10 | ### 主要功能 11 | 12 | - **集中式状态**:在一个地方定义和管理应用程序范围的状态。 13 | - **依赖注入**:在应用程序的不同组件之间注入和访问共享服务和资源。 14 | 15 | ## 用法示例 16 | 17 | ### 定义应用程序状态 18 | 19 | 要定义应用程序范围的状态,请扩展 `Application` 对象并声明状态属性。 20 | 21 | ```swift 22 | import AppState 23 | 24 | struct User { 25 | var name: String 26 | var isLoggedIn: Bool 27 | } 28 | 29 | extension Application { 30 | var user: State { 31 | state(initial: User(name: "Guest", isLoggedIn: false)) 32 | } 33 | } 34 | ``` 35 | 36 | ### 在视图中访问和修改状态 37 | 38 | 您可以使用 `@AppState` 属性包装器直接在 SwiftUI 视图中访问和修改状态值。 39 | 40 | ```swift 41 | import AppState 42 | import SwiftUI 43 | 44 | struct ContentView: View { 45 | @AppState(\.user) var user: User 46 | 47 | var body: some View { 48 | VStack { 49 | Text("你好, \(user.name)!") 50 | Button("登录") { 51 | user.name = "John Doe" 52 | user.isLoggedIn = true 53 | } 54 | } 55 | } 56 | } 57 | ``` 58 | 59 | ### 定义依赖项 60 | 61 | 您可以将共享资源(例如网络服务)定义为 `Application` 对象中的依赖项。这些依赖项可以注入到 SwiftUI 视图中。 62 | 63 | ```swift 64 | import AppState 65 | 66 | protocol NetworkServiceType { 67 | func fetchData() -> String 68 | } 69 | 70 | class NetworkService: NetworkServiceType { 71 | func fetchData() -> String { 72 | return "Data from network" 73 | } 74 | } 75 | 76 | extension Application { 77 | var networkService: Dependency { 78 | dependency(NetworkService()) 79 | } 80 | } 81 | ``` 82 | 83 | ### 在视图中访问依赖项 84 | 85 | 使用 `@AppDependency` 属性包装器在 SwiftUI 视图中访问依赖项。这允许您将网络服务等服务注入到您的视图中。 86 | 87 | ```swift 88 | import AppState 89 | import SwiftUI 90 | 91 | struct NetworkView: View { 92 | @AppDependency(\.networkService) var networkService: NetworkServiceType 93 | 94 | var body: some View { 95 | VStack { 96 | Text("数据: \(networkService.fetchData())") 97 | } 98 | } 99 | } 100 | ``` 101 | 102 | ### 在视图中结合状态和依赖项 103 | 104 | 状态和依赖项可以协同工作,构建更复杂的应用程序逻辑。例如,您可以从服务中获取数据并更新状态: 105 | 106 | ```swift 107 | import AppState 108 | import SwiftUI 109 | 110 | struct CombinedView: View { 111 | @AppState(\.user) var user: User 112 | @AppDependency(\.networkService) var networkService: NetworkServiceType 113 | 114 | var body: some View { 115 | VStack { 116 | Text("用户: \(user.name)") 117 | Button("获取数据") { 118 | user.name = networkService.fetchData() 119 | user.isLoggedIn = true 120 | } 121 | } 122 | } 123 | } 124 | ``` 125 | 126 | ### 最佳实践 127 | 128 | - **集中管理状态**:将您的应用程序范围的状态放在一个地方,以避免重复并确保一致性。 129 | - **对共享服务使用依赖项**:注入网络服务、数据库或其他共享资源等依赖项,以避免组件之间的紧密耦合。 130 | 131 | ## 结论 132 | 133 | 使用 **AppState**,您可以管理应用程序范围的状态,并将共享依赖项直接注入到您的 SwiftUI 视图中。这种模式有助于保持您的应用程序模块化和可维护性。探索 **AppState** 库的其他功能,例如 [SecureState](usage-securestate.md) 和 [SyncState](usage-syncstate.md),以进一步增强您的应用程序的状态管理。 134 | 135 | --- 136 | 该译文由机器自动生成,可能存在错误。如果您是母语使用者,我们期待您通过 Pull Request 提出修改建议。 137 | -------------------------------------------------------------------------------- /documentation/zh-CN/usage-storedstate.md: -------------------------------------------------------------------------------- 1 | # StoredState 用法 2 | 3 | `StoredState` 是 **AppState** 库的一个组件,允许您使用 `UserDefaults` 存储和持久化少量数据。它非常适合存储需要在应用程序启动之间持久化的轻量级、非敏感数据。 4 | 5 | ## 概述 6 | 7 | - **StoredState** 构建在 `UserDefaults` 之上,这意味着它对于存储少量数据(例如用户偏好或应用程序设置)来说是快速高效的。 8 | - 保存在 **StoredState** 中的数据在应用程序会话之间保持持久,允许您在启动时恢复应用程序状态。 9 | 10 | ### 主要功能 11 | 12 | - **持久性存储**:保存在 `StoredState` 中的数据在应用程序启动之间保持可用。 13 | - **小数据处理**:最适合用于轻量级数据,如偏好、切换开关或小型配置。 14 | - **线程安全**:`StoredState` 确保在并发环境中数据访问保持安全。 15 | 16 | ## 用法示例 17 | 18 | ### 定义 StoredState 19 | 20 | 您可以通过扩展 `Application` 对象并声明状态属性来定义 **StoredState**: 21 | 22 | ```swift 23 | import AppState 24 | 25 | extension Application { 26 | var userPreferences: StoredState { 27 | storedState(initial: "Default Preferences", id: "userPreferences") 28 | } 29 | } 30 | ``` 31 | 32 | ### 在视图中访问和修改 StoredState 33 | 34 | 您可以使用 `@StoredState` 属性包装器在 SwiftUI 视图中访问和修改 **StoredState** 值: 35 | 36 | ```swift 37 | import AppState 38 | import SwiftUI 39 | 40 | struct PreferencesView: View { 41 | @StoredState(\.userPreferences) var userPreferences: String 42 | 43 | var body: some View { 44 | VStack { 45 | Text("偏好设置: \(userPreferences)") 46 | Button("更新偏好设置") { 47 | userPreferences = "Updated Preferences" 48 | } 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | ## 处理数据迁移 55 | 56 | 随着您的应用程序的发展,您可能会更新通过 **StoredState** 持久化的模型。在更新数据模型时,请确保向后兼容性。例如,您可能需要添加新字段或对模型进行版本控制以处理迁移。 57 | 58 | 有关更多信息,请参阅[迁移注意事项指南](migration-considerations.md)。 59 | 60 | ### 迁移注意事项 61 | 62 | - **添加新的非可选字段**:确保新字段是可选的或具有默认值,以保持向后兼容性。 63 | - **版本化模型**:如果您的数据模型随时间变化,请包含一个 `version` 字段来管理持久化数据的不同版本。 64 | 65 | ## 最佳实践 66 | 67 | - **用于小数据**:存储需要在应用程序启动之间持久化的轻量级、非敏感数据,例如用户偏好。 68 | - **考虑大数据的替代方案**:如果您需要存储大量数据,请考虑改用 **FileState**。 69 | 70 | ## 结论 71 | 72 | **StoredState** 是使用 `UserDefaults` 持久化小块数据的简单有效的方法。它非常适合在应用程序启动之间保存偏好和其他小型设置,同时提供安全的访问和与 SwiftUI 的轻松集成。对于更复杂的持久化需求,请探索 **AppState** 的其他功能,例如 [FileState](usage-filestate.md) 或 [SyncState](usage-syncstate.md)。 73 | 74 | --- 75 | 这是使用 [Jules](https://jules.google) 生成的,可能会出现错误。如果您是母语人士,请提出包含任何应有修复的拉取请求。 76 | --------------------------------------------------------------------------------