Skip to content

UserDefaults: Implement volatile domains, argument domain #1403

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

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 36 additions & 7 deletions Foundation.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
0383A1751D2E558A0052E5D1 /* TestStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0383A1741D2E558A0052E5D1 /* TestStream.swift */; };
03B6F5841F15F339004F25AF /* TestURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B6F5831F15F339004F25AF /* TestURLProtocol.swift */; };
1520469B1D8AEABE00D02E36 /* HTTPServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1520469A1D8AEABE00D02E36 /* HTTPServer.swift */; };
153E95162012A29900F250BE /* UserDefaults_Arguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 153E95152012A29900F250BE /* UserDefaults_Arguments.swift */; };
159884921DCC877700E3314C /* TestHTTPCookieStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 159884911DCC877700E3314C /* TestHTTPCookieStorage.swift */; };
231503DB1D8AEE5D0061694D /* TestDecimal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231503DA1D8AEE5D0061694D /* TestDecimal.swift */; };
294E3C1D1CC5E19300E4F44C /* TestNSAttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294E3C1C1CC5E19300E4F44C /* TestNSAttributedString.swift */; };
Expand Down Expand Up @@ -498,6 +499,7 @@
0383A1741D2E558A0052E5D1 /* TestStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestStream.swift; sourceTree = "<group>"; };
03B6F5831F15F339004F25AF /* TestURLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestURLProtocol.swift; sourceTree = "<group>"; };
1520469A1D8AEABE00D02E36 /* HTTPServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPServer.swift; sourceTree = "<group>"; };
153E95152012A29900F250BE /* UserDefaults_Arguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults_Arguments.swift; sourceTree = "<group>"; };
159884911DCC877700E3314C /* TestHTTPCookieStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestHTTPCookieStorage.swift; sourceTree = "<group>"; };
22B9C1E01C165D7A00DECFF9 /* TestDate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestDate.swift; sourceTree = "<group>"; };
231503DA1D8AEE5D0061694D /* TestDecimal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestDecimal.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1787,6 +1789,7 @@
isa = PBXGroup;
children = (
EADE0B871BD15DFF00C49C64 /* UserDefaults.swift */,
153E95152012A29900F250BE /* UserDefaults_Arguments.swift */,
5BDC3F3B1BCC5DCB00ED97BB /* NSLocale.swift */,
5BD70FB11D3D4CDC003B9BF8 /* Locale.swift */,
);
Expand Down Expand Up @@ -2184,6 +2187,7 @@
61E0117E1C1B55B9000037DD /* Timer.swift in Sources */,
EADE0BCD1BD15E0000C49C64 /* XMLParser.swift in Sources */,
5BDC3FD01BCF17E600ED97BB /* NSCFSet.swift in Sources */,
153E95162012A29900F250BE /* UserDefaults_Arguments.swift in Sources */,
5B1FD9DE1D6D16580080E83C /* TaskRegistry.swift in Sources */,
EADE0B931BD15DFF00C49C64 /* NSComparisonPredicate.swift in Sources */,
5B1FD9DC1D6D16580080E83C /* URLSessionDelegate.swift in Sources */,
Expand Down Expand Up @@ -2660,7 +2664,11 @@
INFOPLIST_FILE = Foundation/Info.plist;
INIT_ROUTINE = "___CFInitialize";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
OTHER_CFLAGS = (
"-DCF_BUILDING_CF",
"-DDEPLOYMENT_TARGET_MACOSX",
Expand Down Expand Up @@ -2732,7 +2740,11 @@
INFOPLIST_FILE = Foundation/Info.plist;
INIT_ROUTINE = "___CFInitialize";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
OTHER_CFLAGS = (
"-DCF_BUILDING_CF",
"-DDEPLOYMENT_TARGET_MACOSX",
Expand Down Expand Up @@ -2880,7 +2892,11 @@
/usr/include/libxml2,
);
INFOPLIST_FILE = TestFoundation/Resources/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
MACH_O_TYPE = mh_execute;
OTHER_SWIFT_FLAGS = "-DDEPLOYMENT_ENABLE_LIBDISPATCH -swift-version 4";
Expand All @@ -2906,7 +2922,11 @@
/usr/include/libxml2,
);
INFOPLIST_FILE = TestFoundation/Resources/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
MACH_O_TYPE = mh_execute;
OTHER_SWIFT_FLAGS = "-DDEPLOYMENT_ENABLE_LIBDISPATCH -swift-version 4";
Expand All @@ -2930,7 +2950,12 @@
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = TestFoundation/xdgTestHelper/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../../.. @loader_path/../../.. @executable_path/../Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../../..",
"@loader_path/../../..",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_BUNDLE_IDENTIFIER = org.swift.xdgTestHelper;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -2952,7 +2977,12 @@
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = TestFoundation/xdgTestHelper/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../../.. @loader_path/../../.. @executable_path/../Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../../..",
"@loader_path/../../..",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_BUNDLE_IDENTIFIER = org.swift.xdgTestHelper;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -3044,4 +3074,3 @@
};
rootObject = 5B5D88541BBC938800234F36 /* Project object */;
}

28 changes: 28 additions & 0 deletions Foundation/NSNumber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,34 @@ open class NSNumber : NSValue {
fatalError("unsupported CFNumberType: '\(numberType)'")
}
}

internal var _swiftValueOfOptimalType: Any {
if self === kCFBooleanTrue {
return true
} else if self === kCFBooleanFalse {
return false
}

let numberType = _CFNumberGetType2(_cfObject)
switch numberType {
case kCFNumberSInt8Type:
return Int(int8Value)
case kCFNumberSInt16Type:
return Int(int16Value)
case kCFNumberSInt32Type:
return Int(int32Value)
case kCFNumberSInt64Type:
return int64Value < Int.max ? Int(int64Value) : int64Value
case kCFNumberFloat32Type:
return floatValue
case kCFNumberFloat64Type:
return doubleValue
case kCFNumberSInt128Type:
return int128Value
default:
fatalError("unsupported CFNumberType: '\(numberType)'")
}
}

deinit {
_CFDeinit(self)
Expand Down
199 changes: 169 additions & 30 deletions Foundation/UserDefaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,128 @@

import CoreFoundation

private var registeredDefaults = [String: Any]()
private var registeredDefaults = [String: NSObject]()
private var sharedDefaults = UserDefaults()

internal func plistValueAsNSObject(_ value: Any) -> NSObject? {
let nsValue: NSObject

// Converts a value to the internal representation. Internalized values are
// stored as NSObject derived objects in the registration dictionary.
if let val = value as? String {
nsValue = val._nsObject
} else if let val = value as? URL {
nsValue = val.path._nsObject
} else if let val = value as? Int {
nsValue = NSNumber(value: val)
} else if let val = value as? Double {
nsValue = NSNumber(value: val)
} else if let val = value as? Bool {
nsValue = NSNumber(value: val)
} else if let val = value as? Data {
nsValue = val._nsObject
} else if let val = value as? Date {
nsValue = val._nsObject
} else if let val = value as? [Any] {
var nsValues: [NSObject] = []
for innerValue in val {
guard let nsInnerValue = plistValueAsNSObject(innerValue) else { return nil }
nsValues.append(nsInnerValue)
}
return NSArray(array: nsValues)
} else if let val = value as? [String: Any] {
var nsValues: [String: NSObject] = [:]
for (key, innerValue) in val {
guard let nsInnerValue = plistValueAsNSObject(innerValue) else { return nil }
nsValues[key] = nsInnerValue
}
return NSDictionary(dictionary: nsValues)
} else if let val = value as? NSObject {
nsValue = val
} else {
return nil
}

return nsValue
}

internal func plistNSObjectAsValue(_ nsValue: NSObject) -> Any {
let value: Any

// Converts a value to the internal representation. Internalized values are
// stored as NSObject derived objects in the registration dictionary.
if let val = nsValue as? NSString {
value = val._swiftObject
} else if let val = nsValue as? NSNumber {
value = val._swiftValueOfOptimalType
} else if let val = nsValue as? NSData {
value = val._swiftObject
} else if let val = nsValue as? NSArray {
value = val._swiftObject.map { plistNSObjectAsValue($0 as! NSObject) }
} else if let val = nsValue as? NSDictionary {
var values: [String: Any] = [:]
for (currentKey, currentInnerValue) in val {
let key: String

if let swiftKey = currentKey as? String {
key = swiftKey
} else if let nsKey = currentKey as? NSString {
key = nsKey._swiftObject
} else {
continue
}

if let nsInnerValue = currentInnerValue as? NSObject {
values[key] = plistNSObjectAsValue(nsInnerValue)
} else {
values[key] = currentInnerValue
}
}
value = values
} else if let val = nsValue as? NSDate {
value = val._swiftObject
} else {
value = nsValue
}

return value
}

private extension Dictionary {
func convertingValuesToNSObjects() -> [Key: NSObject]? {
var result: [Key: NSObject] = [:]

for (key, value) in self {
if let nsValue = plistValueAsNSObject(value) {
result[key] = nsValue
} else {
return nil
}
}

return result
}
}

private extension Dictionary where Value == NSObject {
mutating func merge(convertingValuesToNSObject source: [Key: Any], uniquingKeysWith block: (NSObject, NSObject) throws -> Value) rethrows -> Bool {
if let converted = source.convertingValuesToNSObjects() {
try self.merge(converted, uniquingKeysWith: block)
return true
} else {
return false
}
}

func convertingValuesFromPlistNSObject() -> [Key: Any] {
var result: [Key: Any] = [:]
for (key, value) in self {
result[key] = plistNSObjectAsValue(value)
}
return result
}
}

open class UserDefaults: NSObject {
private let suite: String?

Expand All @@ -31,9 +150,17 @@ open class UserDefaults: NSObject {
/// nil suite means use the default search list that +standardUserDefaults uses
public init?(suiteName suitename: String?) {
suite = suitename
super.init()

setVolatileDomain(UserDefaults._parsedArgumentsDomain, forName: UserDefaults.argumentDomain)
}

open func object(forKey defaultName: String) -> Any? {
let argumentDomain = volatileDomain(forName: UserDefaults.argumentDomain)
if let object = argumentDomain[defaultName] {
return object
}

func getFromRegistered() -> Any? {
return registeredDefaults[defaultName]
}
Expand Down Expand Up @@ -265,30 +392,8 @@ open class UserDefaults: NSObject {
}

open func register(defaults registrationDictionary: [String : Any]) {
for (key, value) in registrationDictionary {
let nsValue: NSObject

// Converts a value to the internal representation. Internalized values are
// stored as NSObject derived objects in the registration dictionary.
if let val = value as? String {
nsValue = val._nsObject
} else if let val = value as? URL {
nsValue = val.path._nsObject
} else if let val = value as? Int {
nsValue = NSNumber(value: val)
} else if let val = value as? Double {
nsValue = NSNumber(value: val)
} else if let val = value as? Bool {
nsValue = NSNumber(value: val)
} else if let val = value as? Data {
nsValue = val._nsObject
} else if let val = value as? NSObject {
nsValue = val
} else {
fatalError("The type of 'value' passed to UserDefaults.register(defaults:) is not supported.")
}

registeredDefaults[key] = nsValue
if !registeredDefaults.merge(convertingValuesToNSObject: registrationDictionary, uniquingKeysWith: { $1 }) {
fatalError("The type of 'value' passed to UserDefaults.register(defaults:) is not supported.")
}
}

Expand All @@ -307,16 +412,50 @@ open class UserDefaults: NSObject {
var allDefaults = registeredDefaults

for (key, value) in bPref {
allDefaults[key._swiftObject] = value
if let value = plistValueAsNSObject(value) {
allDefaults[key._swiftObject] = value
}
}

return allDefaults
}

open var volatileDomainNames: [String] { NSUnimplemented() }
open func volatileDomain(forName domainName: String) -> [String : Any] { NSUnimplemented() }
open func setVolatileDomain(_ domain: [String : Any], forName domainName: String) { NSUnimplemented() }
open func removeVolatileDomain(forName domainName: String) { NSUnimplemented() }
private static let _parsedArgumentsDomain: [String: NSObject] = UserDefaults._parseArguments(ProcessInfo.processInfo.arguments).convertingValuesToNSObjects() ?? [:]

private var _volatileDomains: [String: [String: NSObject]] = [:]
private let _volatileDomainsLock = NSLock()

open var volatileDomainNames: [String] {
_volatileDomainsLock.lock()
let names = Array(_volatileDomains.keys)
_volatileDomainsLock.unlock()

return names
}

open func volatileDomain(forName domainName: String) -> [String : Any] {
_volatileDomainsLock.lock()
let domain = _volatileDomains[domainName]
_volatileDomainsLock.unlock()

return domain?.convertingValuesFromPlistNSObject() ?? [:]
}

open func setVolatileDomain(_ domain: [String : Any], forName domainName: String) {
_volatileDomainsLock.lock()
var convertedDomain: [String: NSObject] = _volatileDomains[domainName] ?? [:]
if !convertedDomain.merge(convertingValuesToNSObject: domain, uniquingKeysWith: { $1 }) {
fatalError("The type of 'value' passed to UserDefaults.setVolatileDomain(_:forName:) is not supported.")
}
_volatileDomains[domainName] = convertedDomain
_volatileDomainsLock.unlock()
}

open func removeVolatileDomain(forName domainName: String) {
_volatileDomainsLock.lock()
_volatileDomains.removeValue(forKey: domainName)
_volatileDomainsLock.unlock()
}

open func persistentDomain(forName domainName: String) -> [String : Any]? { NSUnimplemented() }
open func setPersistentDomain(_ domain: [String : Any], forName domainName: String) { NSUnimplemented() }
Expand Down
Loading