Skip to content

Commit b6ca8f3

Browse files
Dietmar Planitzerphausler
authored andcommitted
Align the UserDefaults implementation with Darwin Foundation (#1228)
- the register(defaults:) function now ensures that values are stored in the internal registration dictionary as NSObject objects which allows object(forKey:) and friends to actually find those values - the bool(forKey:), integer(forKey:), double(forKey:) and float(forKey:) functions now implicitly convert string represetations to the requested type - the set(_:forKey:) function now ensures that values of type String, Int and Double are first converted to the proper CFType before they are stored in the CFPreferences object - register(defaults:) and set(_:forKey:) now cause a fatalError() if an attempt is made to pass an unsupported type. This more closely resembles what the ObjC Foundation does and makes bugs in the code obvious - added unit tests to cover the above problems - added the CFXMLPreferencesDomain.c file to the macOS project and enabled the UserDefaults unit tests on macOS - enabled support for CFXMLPreferencesDomain on Linux, added it to the Linux build process and enabled the UserDefaults unit tests on Linux
1 parent f886620 commit b6ca8f3

File tree

7 files changed

+315
-41
lines changed

7 files changed

+315
-41
lines changed

CoreFoundation/Preferences.subproj/CFApplicationPreferences.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#include <CoreFoundation/CFNumberFormatter.h>
1818
#include <CoreFoundation/CFDateFormatter.h>
1919
#include <sys/types.h>
20-
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
20+
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_LINUX
2121
#include <unistd.h>
2222
#endif
2323

CoreFoundation/Preferences.subproj/CFPreferences.c

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ static CFStringRef _CFPreferencesStandardDomainCacheKey(CFStringRef domainName
445445
static CFURLRef _CFPreferencesURLForStandardDomainWithSafetyLevel(CFStringRef domainName, CFStringRef userName, CFStringRef hostName, unsigned long safeLevel) {
446446
CFURLRef theURL = NULL;
447447
CFAllocatorRef prefAlloc = __CFPreferencesAllocator();
448-
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_WINDOWS
448+
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_LINUX || DEPLOYMENT_TARGET_WINDOWS
449449
CFURLRef prefDir = _preferencesDirectoryForUserHostSafetyLevel(userName, hostName, safeLevel);
450450
CFStringRef appName;
451451
CFStringRef fileName;
@@ -479,7 +479,7 @@ static CFURLRef _CFPreferencesURLForStandardDomainWithSafetyLevel(CFStringRef do
479479
CFRelease(appName);
480480
}
481481
if (fileName) {
482-
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
482+
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_LINUX
483483
theURL = CFURLCreateWithFileSystemPathRelativeToBase(prefAlloc, fileName, kCFURLPOSIXPathStyle, false, prefDir);
484484
#elif DEPLOYMENT_TARGET_WINDOWS
485485
theURL = CFURLCreateWithFileSystemPathRelativeToBase(prefAlloc, fileName, kCFURLWindowsPathStyle, false, prefDir);
@@ -497,10 +497,6 @@ static CFURLRef _CFPreferencesURLForStandardDomain(CFStringRef domainName, CFStr
497497
return _CFPreferencesURLForStandardDomainWithSafetyLevel(domainName, userName, hostName, __CFSafeLaunchLevel);
498498
}
499499

500-
const _CFPreferencesDomainCallBacks __kCFXMLPropertyListDomainCallBacks = {
501-
502-
};
503-
504500
CFPreferencesDomainRef _CFPreferencesStandardDomain(CFStringRef domainName, CFStringRef userName, CFStringRef hostName) {
505501
CFPreferencesDomainRef domain;
506502
CFStringRef domainKey;

CoreFoundation/Preferences.subproj/CFXMLPreferencesDomain.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
#include <CoreFoundation/CFDate.h>
1717
#include "CFInternal.h"
1818
#include <time.h>
19-
#if DEPLOYMENT_TARGET_MACOSX
19+
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_LINUX
2020
#include <unistd.h>
2121
#include <stdio.h>
2222
#include <sys/stat.h>
23+
#endif
24+
#if DEPLOYMENT_TARGET_MACOSX
2325
#include <mach/mach.h>
2426
#include <mach/mach_syscalls.h>
2527
#endif
@@ -102,7 +104,7 @@ static Boolean _createDirectory(CFURLRef dirURL, Boolean worldReadable) {
102104
if (parentURL) CFRelease(parentURL);
103105
if (!parentExists) return false;
104106

105-
#if DEPLOYMENT_TARGET_MACOSX
107+
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_LINUX
106108
mode = worldReadable ? S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH : S_IRWXU;
107109
#else
108110
mode = 0666;
@@ -306,7 +308,7 @@ static Boolean _writeXMLFile(CFURLRef url, CFMutableDictionaryRef dict, Boolean
306308
CFDataRef data = CFPropertyListCreateData(alloc, dict, desiredFormat, 0, NULL);
307309
if (data) {
308310
SInt32 mode;
309-
#if DEPLOYMENT_TARGET_MACOSX
311+
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_LINUX
310312
mode = isWorldReadable ? S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH : S_IRUSR|S_IWUSR;
311313
#else
312314
mode = 0666;

Foundation.xcodeproj/project.pbxproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
528776141BF2629700CB0090 /* FoundationErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522C253A1BF16E1600804FC6 /* FoundationErrors.swift */; };
2222
528776191BF27D9500CB0090 /* Test.plist in Resources */ = {isa = PBXBuildFile; fileRef = 528776181BF27D9500CB0090 /* Test.plist */; };
2323
555683BD1C1250E70041D4C6 /* TestUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 555683BC1C1250E70041D4C6 /* TestUserDefaults.swift */; };
24+
559451EC1F706BFA002807FB /* CFXMLPreferencesDomain.c in Sources */ = {isa = PBXBuildFile; fileRef = 559451EA1F706BF5002807FB /* CFXMLPreferencesDomain.c */; };
2425
5B0163BB1D024EB7003CCD96 /* DateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0163BA1D024EB7003CCD96 /* DateComponents.swift */; };
2526
5B13B3251C582D4700651CE2 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA66F6381BF1619600136161 /* main.swift */; };
2627
5B13B3261C582D4C00651CE2 /* TestAffineTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = C93559281C12C49F009FD6A9 /* TestAffineTransform.swift */; };
@@ -504,6 +505,7 @@
504505
52829AD61C160D64003BC4EF /* TestCalendar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCalendar.swift; sourceTree = "<group>"; };
505506
528776181BF27D9500CB0090 /* Test.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Test.plist; sourceTree = "<group>"; };
506507
555683BC1C1250E70041D4C6 /* TestUserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUserDefaults.swift; sourceTree = "<group>"; usesTabs = 1; };
508+
559451EA1F706BF5002807FB /* CFXMLPreferencesDomain.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFXMLPreferencesDomain.c; sourceTree = "<group>"; };
507509
5B0163BA1D024EB7003CCD96 /* DateComponents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateComponents.swift; sourceTree = "<group>"; };
508510
5B0C6C211C1E07E600705A0E /* TestNSRegularExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSRegularExpression.swift; sourceTree = "<group>"; };
509511
5B1FD9C11D6D160F0080E83C /* CFURLSessionInterface.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFURLSessionInterface.c; sourceTree = "<group>"; };
@@ -1034,6 +1036,7 @@
10341036
isa = PBXGroup;
10351037
children = (
10361038
5B5D886A1BBC948300234F36 /* CFApplicationPreferences.c */,
1039+
559451EA1F706BF5002807FB /* CFXMLPreferencesDomain.c */,
10371040
5B5D898B1BBDBF6500234F36 /* CFPreferences.c */,
10381041
5B5D886C1BBC94C400234F36 /* CFPreferences.h */,
10391042
);
@@ -2027,7 +2030,7 @@
20272030
TargetAttributes = {
20282031
5B5D885C1BBC938800234F36 = {
20292032
CreatedOnToolsVersion = 7.1;
2030-
LastSwiftMigration = 0800;
2033+
LastSwiftMigration = 0900;
20312034
LastSwiftUpdateCheck = 0800;
20322035
ProvisioningStyle = Manual;
20332036
};
@@ -2306,6 +2309,7 @@
23062309
5B7C8A8B1BEA7FE200C5B690 /* CFBigNumber.c in Sources */,
23072310
5B7C8A7D1BEA7FCE00C5B690 /* CFBinaryHeap.c in Sources */,
23082311
5B7C8A7E1BEA7FCE00C5B690 /* CFBitVector.c in Sources */,
2312+
559451EC1F706BFA002807FB /* CFXMLPreferencesDomain.c in Sources */,
23092313
5B7C8A8F1BEA7FEC00C5B690 /* CFBinaryPList.c in Sources */,
23102314
5B7C8A911BEA7FEC00C5B690 /* CFPropertyList.c in Sources */,
23112315
5B7C8AB41BEA801700C5B690 /* CFStringEncodingDatabase.c in Sources */,

Foundation/UserDefaults.swift

Lines changed: 88 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -45,37 +45,39 @@ open class UserDefaults: NSObject {
4545
//Force the returned value to an NSObject
4646
switch CFGetTypeID(anObj) {
4747
case CFStringGetTypeID():
48-
return (anObj as! CFString)._nsObject
48+
return unsafeBitCast(anObj, to: NSString.self)
4949

5050
case CFNumberGetTypeID():
51-
return (anObj as! CFNumber)._nsObject
51+
return unsafeBitCast(anObj, to: NSNumber.self)
5252

5353
case CFURLGetTypeID():
54-
return (anObj as! CFURL)._nsObject
54+
return unsafeBitCast(anObj, to: NSURL.self)
5555

5656
case CFArrayGetTypeID():
57-
return (anObj as! CFArray)._nsObject
57+
return unsafeBitCast(anObj, to: NSArray.self)
5858

5959
case CFDictionaryGetTypeID():
60-
return (anObj as! CFDictionary)._nsObject
61-
60+
return unsafeBitCast(anObj, to: NSDictionary.self)
61+
6262
case CFDataGetTypeID():
63-
return (anObj as! CFData)._nsObject
63+
return unsafeBitCast(anObj, to: NSData.self)
6464

6565
default:
6666
return getFromRegistered()
6767
}
6868
}
69+
6970
open func set(_ value: Any?, forKey defaultName: String) {
7071
guard let value = value else {
7172
CFPreferencesSetAppValue(defaultName._cfObject, nil, suite?._cfObject ?? kCFPreferencesCurrentApplication)
7273
return
7374
}
7475

75-
var cfType: CFTypeRef? = nil
76+
let cfType: CFTypeRef
7677

77-
//FIXME: is this needed? Am I overcomplicating things?
78-
//Foundation types
78+
// Convert the input value to the internal representation. All values are
79+
// represented as CFTypeRef objects internally because we store the defaults
80+
// in a CFPreferences type.
7981
if let bType = value as? NSNumber {
8082
cfType = bType._cfObject
8183
} else if let bType = value as? NSString {
@@ -84,11 +86,26 @@ open class UserDefaults: NSObject {
8486
cfType = bType._cfObject
8587
} else if let bType = value as? NSDictionary {
8688
cfType = bType._cfObject
89+
} else if let bType = value as? NSData {
90+
cfType = bType._cfObject
91+
} else if let bType = value as? NSURL {
92+
set(URL(reference: bType), forKey: defaultName)
93+
return
94+
} else if let bType = value as? String {
95+
cfType = bType._cfObject
8796
} else if let bType = value as? URL {
8897
set(bType, forKey: defaultName)
8998
return
99+
} else if let bType = value as? Int {
100+
var cfValue = Int64(bType)
101+
cfType = CFNumberCreate(nil, kCFNumberSInt64Type, &cfValue)
102+
} else if let bType = value as? Double {
103+
var cfValue = bType
104+
cfType = CFNumberCreate(nil, kCFNumberDoubleType, &cfValue)
90105
} else if let bType = value as? Data {
91106
cfType = bType._cfObject
107+
} else {
108+
fatalError("The type of 'value' passed to UserDefaults.set(forKey:) is not supported.")
92109
}
93110

94111
CFPreferencesSetAppValue(defaultName._cfObject, cfType, suite?._cfObject ?? kCFPreferencesCurrentApplication)
@@ -140,10 +157,10 @@ open class UserDefaults: NSObject {
140157
}
141158
open func data(forKey defaultName: String) -> Data? {
142159
guard let aVal = object(forKey: defaultName),
143-
let bVal = aVal as? Data else {
160+
let bVal = aVal as? NSData else {
144161
return nil
145162
}
146-
return bVal
163+
return Data(referencing: bVal)
147164
}
148165
open func stringArray(forKey defaultName: String) -> [String]? {
149166
guard let aVal = object(forKey: defaultName),
@@ -153,39 +170,61 @@ open class UserDefaults: NSObject {
153170
return _SwiftValue.fetch(nonOptional: bVal) as? [String]
154171
}
155172
open func integer(forKey defaultName: String) -> Int {
156-
guard let aVal = object(forKey: defaultName),
157-
let bVal = aVal as? NSNumber else {
173+
guard let aVal = object(forKey: defaultName) else {
158174
return 0
159175
}
160-
return bVal.intValue
176+
if let bVal = aVal as? NSNumber {
177+
return bVal.intValue
178+
}
179+
if let bVal = aVal as? NSString {
180+
return bVal.integerValue
181+
}
182+
return 0
161183
}
162184
open func float(forKey defaultName: String) -> Float {
163-
guard let aVal = object(forKey: defaultName),
164-
let bVal = aVal as? NSNumber else {
185+
guard let aVal = object(forKey: defaultName) else {
165186
return 0
166187
}
167-
return bVal.floatValue
188+
if let bVal = aVal as? NSNumber {
189+
return bVal.floatValue
190+
}
191+
if let bVal = aVal as? NSString {
192+
return bVal.floatValue
193+
}
194+
return 0
168195
}
169196
open func double(forKey defaultName: String) -> Double {
170-
guard let aVal = object(forKey: defaultName),
171-
let bVal = aVal as? NSNumber else {
197+
guard let aVal = object(forKey: defaultName) else {
172198
return 0
173199
}
174-
return bVal.doubleValue
200+
if let bVal = aVal as? NSNumber {
201+
return bVal.doubleValue
202+
}
203+
if let bVal = aVal as? NSString {
204+
return bVal.doubleValue
205+
}
206+
return 0
175207
}
176208
open func bool(forKey defaultName: String) -> Bool {
177-
guard let aVal = object(forKey: defaultName),
178-
let bVal = aVal as? NSNumber else {
209+
guard let aVal = object(forKey: defaultName) else {
179210
return false
180211
}
181-
return bVal.boolValue
212+
if let bVal = aVal as? NSNumber {
213+
return bVal.boolValue
214+
}
215+
if let bVal = aVal as? NSString {
216+
return bVal.boolValue
217+
}
218+
return false
182219
}
183220
open func url(forKey defaultName: String) -> URL? {
184221
guard let aVal = object(forKey: defaultName) else {
185222
return nil
186223
}
187224

188-
if let bVal = aVal as? NSString {
225+
if let bVal = aVal as? NSURL {
226+
return URL(reference: bVal)
227+
} else if let bVal = aVal as? NSString {
189228
let cVal = bVal.expandingTildeInPath
190229

191230
return URL(fileURLWithPath: cVal)
@@ -227,10 +266,32 @@ open class UserDefaults: NSObject {
227266

228267
open func register(defaults registrationDictionary: [String : Any]) {
229268
for (key, value) in registrationDictionary {
230-
registeredDefaults[key] = value
269+
let nsValue: NSObject
270+
271+
// Converts a value to the internal representation. Internalized values are
272+
// stored as NSObject derived objects in the registration dictionary.
273+
if let val = value as? String {
274+
nsValue = val._nsObject
275+
} else if let val = value as? URL {
276+
nsValue = val.path._nsObject
277+
} else if let val = value as? Int {
278+
nsValue = NSNumber(value: val)
279+
} else if let val = value as? Double {
280+
nsValue = NSNumber(value: val)
281+
} else if let val = value as? Bool {
282+
nsValue = NSNumber(value: val)
283+
} else if let val = value as? Data {
284+
nsValue = val._nsObject
285+
} else if let val = value as? NSObject {
286+
nsValue = val
287+
} else {
288+
fatalError("The type of 'value' passed to UserDefaults.register(defaults:) is not supported.")
289+
}
290+
291+
registeredDefaults[key] = nsValue
231292
}
232293
}
233-
294+
234295
open func addSuite(named suiteName: String) {
235296
CFPreferencesAddSuitePreferencesToApp(kCFPreferencesCurrentApplication, suiteName._cfObject)
236297
}

0 commit comments

Comments
 (0)