Giter VIP home page Giter VIP logo

defaults's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

defaults's Issues

SwiftUI support

Would be nice to have something like the @Published property wrapper, but that also handles the synchronization of UserDefaults.

struct FooView: View {
	@Defaults.Published(.timeZonesInMenu) var arrayOfThings
}

Reproducible crash of Defaults.publisher()

The following example will cause a crash:

let c = Defaults.publisher(.someSetting).first().sink { print($0) }

The crash says:

'NSRangeException', reason: 'Cannot remove an observer <_TtCO8Defaults8Defaults26UserDefaultsKeyObservation 0x2837dad80> for the key path "someSetting" from <NSUserDefaults 0x282cd7a80> because it is not registered as an observer.

The cause seems to be that the addObserver call synchronously tries to cancels itself.

This example might be contrived, but it's the easiest reproducible case. The actual crash I ran into is more involved.

Improve stored representation

Issuehunt badges

For example, currently, a Codable that is serialized to a String is stored as "foo" because we serialize it as JSON. It would be nice to make it just foo (without the double-quotes).

The following values could be improved when serialized from a Codable:

  • String (for example string enum): "foo" => foo
  • Arrays are currently stored as a serialized string in a String plist type. We should really use a native plist Array type.
  • Dictionaries with string keys should also be stored as a native Dictionary plist type.

This is a breaking change, so it would have to be an opt-in and we could make it the default in the next major version (with still a way to opt-out so existing projects can upgrade).


This is not an easy issue. You are expected to have advanced knowledge of Swift and Codable.


IssueHunt Summary

hank121314 hank121314 has been rewarded.

Backers (Total: $113.00)

Submitted pull Requests


Tips

Support for iOS

The code should work out of the box, but I'm not sure how to set up the Xcode project to target both macOS and iOS. Help wanted 🙌

Swift 5 upgrade process

Hi

I wanna to say few words about CocaoPods spec updates. Every time you change your .podspec to new values you have to increase version. In latest changeset only swift version was changed. In this case no client of 1.0.0 (swift4.2) would get update through pod update Defaults.
May you bump spec version as well, please?

Get rid of `NSSecureCodingOptionalKey`

I got rid of OptionalKey in b2fdee2, but I was not able to remove NSSecureCodingOptionalKey as it seems optionals cannot conform to NSSecureCoding, or maybe it has something to do with NSSecureCoding being an Objective-C protocol.

Try changing

let key = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("observeNSSecureCodingOptionalKey")
to:

let key = Defaults.NSSecureCodingKey<ExamplePersistentHistory?>("observeNSSecureCodingOptionalKey")

and see the error.

Help wanted 🙏

Improve serialization

Currently, we have to wrap the Codable value in an array, since JSONEncoder can't handle top-level fragments.

This might be possible in the future though, so please comment your opinions here: https://forums.swift.org/t/allowing-top-level-fragments-in-jsondecoder/11750


// Some codable values like URL and enum are encoded as a top-level
// string which JSON can't handle, so we need to wrap it in an array
// We need this: https://forums.swift.org/t/allowing-top-level-fragments-in-jsondecoder/11750
let data = try JSONEncoder().encode([value])
let string = String(data: data, encoding: .utf8)?.dropFirst().dropLast()
set(string, forKey: key)


Radar: https://openradar.appspot.com/radar?id=4960629655863296

Cannot use dot syntax for `Defaults.publisher(keys:)`

This compiles:

extension Defaults.Keys {
	static let key = Key<Bool>("key", default: false)
	static let key2 = Key<Int>("key2", default: 1)
}

Defaults.publisher(keys: Defaults.Keys.key, Defaults.Keys.key2)

This does not:

extension Defaults.Keys {
	static let key = Key<Bool>("key", default: false)
	static let key2 = Key<Int>("key2", default: 1)
}

Defaults.publisher(keys: .key, .key2)

Handle key with a dot

Hello,
I am not sure if it's a bug or I am doing something wrong. I have a setting window that users can change settings in. It uses property wrapper '@default(.key)' for textfields.

However in other piece of code (ViewModel) I am subscribed to changes

            Defaults.observe(
                keys: .key1, .key2,
                options: [] // Initial value is not needed.
            ) { [weak self] in
                guard let self = self else { return }
                self.updateValues(array: &self.items)
            }
            .tieToLifetime(of: self)

It never gets called even though values are properly saved. I have to manually call update viewModel method after each change. Is it a bug or I'm using the API in a wrong way?

Cannot find 'key' in scope

Why this example doesn't works?

extension Defaults.Keys {
	static let isUnicornMode = Key<Bool>("isUnicornMode", default: true)
}
print(UserDefaults.standard.bool(forKey: isUnicornMode.name))  //=> true

Schermata 2020-12-29 alle 13 24 38

Publish new release

Current latest release is outdated. Could you draft a new release?
I suggest publishing everything up to 3275717, and then current master in separate releases (as there are some incompatible changes).

Add ability to subscribe to multiple keys or any change in a given suite

Issuehunt badges

This can be useful when you need to handle changes to multiple keys the same.

Should support optionally giving an array or keys or if not given subscribe to all changes.

Something like this:

Defaults.observeAll<T: Codable>(
	_ keys: Defaults.Key<T>,
	options: NSKeyValueObservingOptions = [.initial, .old, .new],
	handler: @escaping () -> Void
) -> DefaultsObservation

Defaults.observeAll<T: Codable>(
	suite: UserDefaults = .standard,
	options: NSKeyValueObservingOptions = [.initial, .old, .new],
	handler: @escaping () -> Void
) -> DefaultsObservation

They should include change objects.

It should also prevent infinite recursion if you call, for example, Defaults[.foo] = true inside the handler as that would normally emit an event, which would call it again infinitely. If it doesn't make sense as default, at least there should be an option for this.

The suite: overload would get all the keys in the given suite and listen to those events. It should not use UserDefaults.didChangeNotification.

I think it could be useful with an overload that listens to all keys defined in Defaults.Keys.

Feedback wanted.


IssueHunt Summary

fredyshox fredyshox has been rewarded.

Backers (Total: $64.00)

Submitted pull Requests


Tips

Make `@Default` usable in an `ObservableObject`

Good afternoon and thanks for this amazing package. I came across a small error, I can not get the view to be updated when I reset the value to default. When I close the application and open it again, I see that the value is written and is equal to the default.

class SessionStore: ObservableObject {
    @Default(.loginModel) var loginModel
}

extension Defaults.Keys {
    static let loginModel = Key<LoginModel?>("loginModel", default: nil)
}

Button(action:  {
    sessionStore.loginModel = nil 
}) {
     HStack {
          Image(systemName: "flame")
                   .frame(width: 24)
          Text("Выйти из аккаунта")
       }.foregroundColor(.red)
}

Crash in Observation+Combine l.36

Hi there,
I'm using a combine publisher like this

    private var myPublisher: NonFailingPublisher<Foo?> {
        Defaults
            .publisher(<FooName>, options: [.initial, .new])
            .compactMap { $0.newValue }
            .eraseToAnyPublisher()
    }

the output is assigned (through some mapping) to a text in a UILabel.

But when executed Defaults crashes in Observation+Combine l.36:

private func observationCallback(_ change: BaseChange) {
	_ = subscriber?.receive(change)
}

reason: Thread 1: Fatal error: Invalid state: Received value before receiving subscription

Maybe this issue is helpful (same error reason): CombineCommunity/RxCombine#6

One (hacky) way to fix this could be to dispatch _ = subscriber?.receive(change) to the next run loop via DispatchQueue.main.async { ... }

Infer the string key name

We currently have to define the key name twice:

static let key = Defaults.Key<Bool>("key", default: false)

Would be nice if we could infer the string "key" from the static let key name. I've not managed to find a way to do this, but help welcome.

Package Resolution Failed in Xcode 11

I attempted to add Defaults through Xcode's new SPM support, but hit an error:

Source files for target Defaults should be located under 'Sources/Defaults', or a custom sources path can be set with the 'path' property in Package.swift

It looks like Xcode isn't happy that the source files are directly in Sources rather than nested.

Combine publisher with String? emits "(null)"

when using a key which keeps an optional String like

static let optionalString = Key<String?>("optionalString", default: nil)

using defaults directly gives nil, as expected:

print(Defaults[.optionalString]) // nil

but using a Combine publisher emits "(null)" as String:

Defaults.publisher(.optionalString).sink { print($0) }.store(in: ...) // KeyChange<Optional<String>>(kind: __C.NSKeyValueChange, indexes: nil, isPrior: false, newValue: Optional("null"), oldValue: nil)

I would expect newValue to be nil as well

Cannot publish to Cocoapods

~/dev/oss/defaults main
❯ pod trunk push --allow-warnings

[!] Found podspec `Defaults.podspec`
Updating spec repo `trunk`

CocoaPods 1.10.1 is available.
To update use: `sudo gem install cocoapods`

For more information, see https://blog.cocoapods.org and the CHANGELOG for this version at https://github.com/CocoaPods/CocoaPods/releases/tag/1.10.1

Validating podspec
 -> Defaults (4.2.0)
    - WARN  | url: The URL (https://twitter.com/sindresorhus) is not reachable.
    - NOTE  | xcodebuild:  note: Using new build system
    - NOTE  | xcodebuild:  note: Building targets in parallel
    - NOTE  | xcodebuild:  note: Using codesigning identity override:
    - NOTE  | xcodebuild:  note: Planning build
    - NOTE  | xcodebuild:  note: Constructing build description
    - ERROR | xcodebuild: Returned an unsuccessful exit code. You can use `--verbose` for more information.
    - NOTE  | xcodebuild:  note: Using codesigning identity override: -
    - NOTE  | xcodebuild:  warning: Skipping code signing because the target does not have an Info.plist file and one is not being generated automatically. (in target 'App' from project 'App')
    - ERROR | [iOS] xcodebuild:  Defaults/Sources/Defaults/Observation.swift:451:20: error: 'NSSecureCodingKeyChange' is only available in iOS 11.0 or newer
    - NOTE  | xcodebuild:  Defaults/Sources/Defaults/Observation.swift:451:1: note: add @available attribute to enclosing extension
    - ERROR | [iOS] xcodebuild:  Defaults/Sources/Defaults/Observation.swift:454:20: error: 'NSSecureCodingOptionalKeyChange' is only available in iOS 11.0 or newer
    - NOTE  | xcodebuild:  Defaults/Sources/Defaults/Observation.swift:454:1: note: add @available attribute to enclosing extension
    - ERROR | xcodebuild:  Defaults/Sources/Defaults/Observation.swift:451:20: error: 'NSSecureCodingKeyChange' is only available in tvOS 11.0 or newer
    - ERROR | xcodebuild:  Defaults/Sources/Defaults/Observation.swift:454:20: error: 'NSSecureCodingOptionalKeyChange' is only available in tvOS 11.0 or newer

[!] The spec did not pass validation, due to 5 errors.

PR welcome to fix this. I don't intend to waste time on Cocoapods.

Remove value wrapper

We currently wrap all saved values in an array:

// Some codable values like URL and enum are encoded as a top-level
// string which JSON can't handle, so we need to wrap it in an array
// We need this: https://forums.swift.org/t/allowing-top-level-fragments-in-jsondecoder/11750

This issue was fixed in macOS 10.15, iOS 13, etc, so it's not needed anymore on those platforms. However, we cannot remove it as it would require us to only support the latest OS versions. Instead, we can introduce a Defaults.useLegacyWrapping (please suggest better name) property that is true by default, but new projects that only target the latest OS versions could set this to true to get a slightly nicer output in UserDefaults.

Initializer is inaccessible due to 'internal' protection level

I'm not Carthage or Swift frameworks expert, but shouldn't the initializers be public?
I've installed the library via Carthage on macOS project and I got this compile error:

'Defaults.Key<T>' initializer is inaccessible due to 'internal' protection level
extension Defaults.Keys
{
    static let myLovelySetting = Defaults.Key<Double>("myLovelySetting", default: 42.0)
}

NSSecureCoding support

Issuehunt badges

Hi
Defaults is awesome lib to manage UserDefaults in Swifty style. Unfortunately, it does not support NSSecureCoding protocol right now. It is useful to work with some Apple-specific classes which support NSSecureCoding only. For example, take a look at NSPersistentHistoryToken.


IssueHunt Summary

ybrin ybrin has been rewarded.

Backers (Total: $80.00)

Submitted pull Requests


Tips


IssueHunt has been backed by the following sponsors. Become a sponsor

Support `[String: Any]`

I'm trying to set a Defaults key to observe it later:

extension Defaults.Keys {
  static let deviceCache = Key<Dictionary>("deviceCache", default: [], suite: UserDefaults(suiteName: "/Library/Preferences/com.apple.Bluetooth")!)
}

But after typing this, an error appears and says: '<T where T : Decodable, T : Encodable> (Defaults.Key<T>.Type) -> (String, T, UserDefaults) -> Defaults.Key<T>' requires that 'Any' conform to 'Encodable'

What am I doing wrong?

Add option to only trigger on changed value

I very often need to only trigger when the value actually changes. Not just that it's set. It could be set to the same value many times and I don't want a notification for that.

Should probably do #33 first as it will simplify implementing this.

Or maybe it should just be the default behavior?

CocoaPods support

Hey, this project seems cool! I'm glad you acknowledged SwiftyUserDefaults and stated the difference. Although I would like to use this solution, we use CocoaPods at work, what do you think about adding CocoaPods installation support? 🙂

Add a `DefaultsSerializable` protocol to make it possible to do custom serializations/deserializations?

You could argue that users could just conform their type to Codable, but not everything can easily be Codable. For example, [String: String].

If we did this, it could make sense to make built-in types conform to this too (https://www.vadimbulavin.com/advanced-guide-to-userdefaults-in-swift/).

I'm looking for feedback on this.

There's another solution to this we could consider, and that would be to ship with AnyCodable support.

Mirroring feature

I just implemented a today widget in one of my apps. I needed to read a preference item in the today widget that was currently stored in my main app. Usually, this would mean having to migrate the key over to the App Groups user defaults suite. However, I'm lazy and instead just went with observing the key and then setting it in the App Groups user defaults suite when changed:

// Constants.swift

extension Defaults.Keys {
	static let timeZonesInMenu = Key<[ChosenTimeZone]>("timeZonesInMenu", default: [])
}


// SharedConstants.swift

struct Constants {
	static let appGroupId = "XXXX.com.sindresorhus.Dato.shared"
	static let sharedDefaults = UserDefaults(suiteName: appGroupId)!
}
extension Defaults.Keys {
	static let sharedTimeZones = Key<[ChosenTimeZone]>("sharedTimeZones", default: [], suite: Constants.sharedDefaults)
}


// OtherFile.swift

// Sync time zone changes to the shared item.
Defaults.observe(.timeZonesInMenu) { change in
	Defaults[.sharedTimeZones] = change.newValue
}
	.tieToLifetime(of: self)

And I was thinking this might be useful for others too.

I'm thinking:

// Key with same name
Defaults.mirror(.key, to: suite)

// Key with different name (where the key is strongly-typed and defined in a shared Constants.swift file or something)
Defaults.mirror(.key, to: suite, as: .sharedKey)

// Key with different name
Defaults.mirror(.key, to: suite, as: "sharedKey")

Would probably need a similar unmirror method too.

Note that this is not syncing. It's only one way.

Feedback welcome.

Deprecate `.observe()`

.updates() is better in every way. Having two ways to do the same is also confusing. We cannot remove .observe() for a long time, but maybe we could add availability annotations so that apps that target macOS 10.15+ (and equivalent iOS/tvOS/watchOS) gets a deprecation message.

Combine support

Issuehunt badges

Would be nice to support Combine for the observation API here. This should be in addition to the observation API, not replacing it.

https://developer.apple.com/documentation/combine/receiving_and_handling_events_with_combine

The code should be fully documented, tests added, and documented in the readme too.


This issue requires you to have advanced Swift knowledge and have studied the Combine docs.


IssueHunt Summary

fredyshox fredyshox has been rewarded.

Backers (Total: $100.00)

Submitted pull Requests


Tips


IssueHunt has been backed by the following sponsors. Become a sponsor

Support migrations

Sometimes you need to change a structure, for example, add a new property to a struct that is saved in user defaults. You can already do this without migrations by making the property optional, but that complicates a lot of code, and it doesn't work for more comprehensive changes.

I don't have a clear idea of how this would work, but I'm thinking the user would register migrations that run as early as possible at startup or on the first Defaults call if before that.

Defaults.migrations = [
	.Migration(version: "1.2.3") {
		// How to handle it here?
	}
]

I'm open to ideas.

Support initial value for optional key

I have:

extension Defaults.Keys {
	static let refreshInterval = Key<TimeInterval?>("refreshInterval")
}

And I would like to specify an initial value, meaning one that is used before any value is set. This is different from default, which is used whenever the value is nil, which would not work here as it would make it impossible to set the value to nil, which is the whole point of making the value optional.

What I'm thinking:

extension Defaults.Keys {
	static let refreshInterval = Key<TimeInterval?>("refreshInterval", initialValue: 3600)
}

The problem is that there's no good way to differentiate whether a key unset or nil.

  • object(forKey:) returns nil whether it's unset or set to nil.
  • suite.dictionaryRepresentation().keys.contains(key) returns nil whether it's unset or set to nil.
  • suite.persistentDomain(forName:).keys.contains(key) would work, but we only have the suite instance, not the suite name. And the suite instance doesn't expose the suite name...
  • CFPreferencesCopyValue returns the true value ignoring the suite.register defaults, but it requires a suite name.

Possible solutions:

  1. Force passing a suiteName parameter too if you want to use initialValue with a non-standard suite. (and use CFPreferencesCopyValue)
  2. Keep track of keys that are set in an internal UserDefaults entry.
  3. Other ideas?

Add `Defaults.updatesAll()` to only listen to the keys registered in `Defaults.Keys`

UserDefaults.didChangeNotification triggers for any kind of user defaults change in the process. Even for included SDK or even Apple's APIs. This makes it both inefficient and dangerous. I just had an infinite loop where I was listening to UserDefaults.didChangeNotification and in the handler, it was setting button.imagePosition and apparently setting that properly causes a userdefault to be set internally, which resulted in an infinite loop.

I think it would be better to only listen to the keys we know about.

Derived keys

I have encountered a need where I would like to create a new key where the value is derived from one or more existing keys.

For example, let's say you have a .name and .email key. You might want to simplify the checking and have a .hasFilledOutAllFields key. This key would be derived from the former keys.

The reason it has to be in Defaults and not just in a custom extension is that it needs to be reactive. So that, if either .name or .email changes, .hasFilledOutAllFields triggers an update too.

My initial thoughts are something like this:

extension Defaults.Keys {
	static let hasFilledOutAllFields = Key<Bool>(derivedFrom: .name, .email) { name, email in
		!name.isEmpty && !email.isEmpty
	}
}

Another use-case is needing the derived key to be settable too. Not sure how that would look like though.

And maybe it would be useful to be able to listen to external events too. For example, for the .hasFilledOutAllFields key, we might also need to ensure the keyboard is not shown or something.

Maybe there's a more general way we could handle this. I'm open to ideas.

Drop support for Carthage and Cocoapods

There's really no reason not to use Swift Package Manager. I don't want the overhead of other package managers.

I plan to drop support for these in the next major Defaults version.

Assigning nil to OptionalKey does not work

Given

extension Defaults.Keys {
    static let source = Defaults.OptionalKey<RssSource>("source")
}

if I assign nil to defaults[.source] the value does not get "saved", reading defaults[.source] gives me the previously assigned non-nil value.

v5 release notes draft

Breaking

  • Please read the migration guide.
  • Removed NSSecureCodingKey and NSSecureCodingOptionalKey.
    • You can now just use Key instead.
  • Dropped support for Carthage and CocoaPods.
    • If you use either of these, you can still use Swift Package Manager just for this package.

Improvements

  • Added support for more built-in Swift types.
  • Improved the stored representation of many types.
    • For example, Array is now stored as a native UserDefaults array instead of being stored as a JSON stringified string. Same with Set and Dictionary.
  • Enums no longer need to be Codable. (Existing usage requires migration)
  • Added support for storing NSColor and UIColor.
  • Added Defaults.Toggle.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.