Skip to content

Implement missing parts of UserDefaults and fix consistency issues #1408

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 3, 2018
Merged

Implement missing parts of UserDefaults and fix consistency issues #1408

merged 12 commits into from
Feb 3, 2018

Conversation

millenomi
Copy link
Contributor

This supersedes the patch at #1403.

While implementing persistent domains as a follow-up to volatile domains (and the arguments domain), I've found several issues in UserDefaults that required a whole bit of rethinking of the way this class works. It has been substantially changed.

This patch:

  • Implements volatile domains
  • Implements the arguments domain as a volatile domain that is searched at the appropriate time
  • Implements persistent domains
  • Changes UserDefaults so that all methods that return Any values return value types instead of reference types. On Linux, where we don't (yet?) have Foundation reference <-> value type bridging, we have to pick one of the two, and I feel it is more likely and desirable for an end user to want the value type rather than the reference type.
  • Changes the internals of the class so that any reference types are converted to value types ASAP where possible, and adopt _SwiftValue instead of trying to do ad-hoc conversions.

@millenomi millenomi changed the title Ud persistent domains Implement missing parts of UserDefaults and fix consistency issues Jan 25, 2018
@millenomi
Copy link
Contributor Author

@swift-ci Please test.

NotificationCenter.default.post(name: UserDefaults.didChangeNotification, object: self)
_ = defaults.synchronize()

if removedAny {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This notification is posted unconditionally in the ObjC implementation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will change.

}
}

open func synchronize() -> Bool {
return CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication)
return CFPreferencesAppSynchronize(suite?._cfObject ?? kCFPreferencesCurrentApplication)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something I did in the ObjC implementation is add CF SPI for “synchronize everything” and call it here, since a ubiquitous pattern of misuse is synchronizing standard user defaults when you set something else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

… ah. hm. I'm inclined to have a follow-up on this one.

while index < count - 1 { // We're looking for pairs, so stop at the second-to-last argument.
let current = arguments[index]
let next = arguments[index + 1]
if current.hasPrefix("-") && !next.hasPrefix("-") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I bet we have subtle unicode behavior handling differences due to Swift string vs NSString, but I don’t think that’s a problem we need to solve here, at least not right now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can see here, this allows a subset of all defaults we allow on Darwin — namely, we skip all defaults that start with a '-' code point but where that code point forms a grapheme, e.g. '-⃣'. It still allows basically all defaults keys of actual interest, though. But yep.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

… all defaults arguments.

… only fetch values that are objects — it makes a new warning happen on Darwin that I can't silence, but it will work on both platforms and without warnings on Linux.
@millenomi
Copy link
Contributor Author

@swift-ci please test

@millenomi
Copy link
Contributor Author

OK, this is ready for review and test (once CI comes back up.) @Catfish-Man @parkera

@millenomi
Copy link
Contributor Author

@swift-ci please test

1 similar comment
@alblue
Copy link
Contributor

alblue commented Jan 26, 2018

@swift-ci please test

Copy link
Contributor

@Catfish-Man Catfish-Man left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few review comments, but overall this looks fine

}
return 0
}

open func bool(forKey defaultName: String) -> Bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One minor incompatibility between this and what darwin does, which we probably don’t want/need to fix: NSUserDefaults historically coerced values to int/bool/float using different logic from CF. I changed it so that subclasses maintain the old behavior (for compatibility reasons), but un-subclassed NSUserDefaults always uses the CF logic.

I haven’t checked whether this implements the NSUserDefaults version or the CFPrefs version, but I think we should use the CFPrefs behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll double-check. If they're different, there will be a follow-up.

let value = bridgeFromNSCFTypeIfNeeded(nonbridgedValue)

if let value = value as? [Any] {
for innerValue in value {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The swift tinkerer in me wants to say .lazy.filter { _isValueAllowed($0) }.count > 0, but tbh I’m not sure it’s worth it :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will reply with my standard reply, which is 'if it turns out to be a hot spot'.

return _dictionaryRepresentation(searchingOutsideOfSuite: true)
}

private func _dictionaryRepresentation(searchingOutsideOfSuite: Bool) -> [String: Any] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

searchingOutsideOfSuite seems like a slightly odd name for this. “Suite” is a very fuzzy term in NSUD, but to the extent that it means anything I tend to think of it as referring to a search list, which would generally include registered prefs, etc...

I’m guessing the sense you mean here is “outside of the set of domains matching this identifier”, which is a subset of the search list for that identifier?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. What about _dictionaryRepresentation(includingOnlySuiteNameDomains:)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We chatted privately and agreed on _dictionaryRepresentation(includingVolatileDomains:).

@millenomi
Copy link
Contributor Author

@swift-ci Please test

@millenomi
Copy link
Contributor Author

@swift-ci Please test and merge

@swift-ci swift-ci merged commit a17cdd0 into swiftlang:master Feb 3, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants