blob: a1eba75d147fe1406e85eacb8ca92d449c0e7f01 [file] [log] [blame]
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
import CoreFoundation
private var registeredDefaults = [String: Any]()
private var sharedDefaults = UserDefaults()
open class UserDefaults: NSObject {
private let suite: String?
open class var standard: UserDefaults {
return sharedDefaults
}
open class func resetStandardUserDefaults() {
//sharedDefaults.synchronize()
//sharedDefaults = UserDefaults()
}
public convenience override init() {
self.init(suiteName: nil)!
}
/// nil suite means use the default search list that +standardUserDefaults uses
public init?(suiteName suitename: String?) {
suite = suitename
}
open func object(forKey defaultName: String) -> Any? {
func getFromRegistered() -> Any? {
return registeredDefaults[defaultName]
}
guard let anObj = CFPreferencesCopyAppValue(defaultName._cfObject, suite?._cfObject ?? kCFPreferencesCurrentApplication) else {
return getFromRegistered()
}
//Force the returned value to an NSObject
switch CFGetTypeID(anObj) {
case CFStringGetTypeID():
return unsafeBitCast(anObj, to: NSString.self)
case CFNumberGetTypeID():
return unsafeBitCast(anObj, to: NSNumber.self)
case CFURLGetTypeID():
return unsafeBitCast(anObj, to: NSURL.self)
case CFArrayGetTypeID():
return unsafeBitCast(anObj, to: NSArray.self)
case CFDictionaryGetTypeID():
return unsafeBitCast(anObj, to: NSDictionary.self)
case CFDataGetTypeID():
return unsafeBitCast(anObj, to: NSData.self)
default:
return getFromRegistered()
}
}
open func set(_ value: Any?, forKey defaultName: String) {
guard let value = value else {
CFPreferencesSetAppValue(defaultName._cfObject, nil, suite?._cfObject ?? kCFPreferencesCurrentApplication)
return
}
let cfType: CFTypeRef
// Convert the input value to the internal representation. All values are
// represented as CFTypeRef objects internally because we store the defaults
// in a CFPreferences type.
if let bType = value as? NSNumber {
cfType = bType._cfObject
} else if let bType = value as? NSString {
cfType = bType._cfObject
} else if let bType = value as? NSArray {
cfType = bType._cfObject
} else if let bType = value as? NSDictionary {
cfType = bType._cfObject
} else if let bType = value as? NSData {
cfType = bType._cfObject
} else if let bType = value as? NSURL {
set(URL(reference: bType), forKey: defaultName)
return
} else if let bType = value as? String {
cfType = bType._cfObject
} else if let bType = value as? URL {
set(bType, forKey: defaultName)
return
} else if let bType = value as? Int {
var cfValue = Int64(bType)
cfType = CFNumberCreate(nil, kCFNumberSInt64Type, &cfValue)
} else if let bType = value as? Double {
var cfValue = bType
cfType = CFNumberCreate(nil, kCFNumberDoubleType, &cfValue)
} else if let bType = value as? Data {
cfType = bType._cfObject
} else {
fatalError("The type of 'value' passed to UserDefaults.set(forKey:) is not supported.")
}
CFPreferencesSetAppValue(defaultName._cfObject, cfType, suite?._cfObject ?? kCFPreferencesCurrentApplication)
}
open func removeObject(forKey defaultName: String) {
CFPreferencesSetAppValue(defaultName._cfObject, nil, suite?._cfObject ?? kCFPreferencesCurrentApplication)
}
open func string(forKey defaultName: String) -> String? {
guard let aVal = object(forKey: defaultName),
let bVal = aVal as? NSString else {
return nil
}
return bVal._swiftObject
}
open func array(forKey defaultName: String) -> [Any]? {
guard let aVal = object(forKey: defaultName),
let bVal = aVal as? NSArray else {
return nil
}
return bVal._swiftObject
}
open func dictionary(forKey defaultName: String) -> [String : Any]? {
guard let aVal = object(forKey: defaultName),
let bVal = aVal as? NSDictionary else {
return nil
}
//This got out of hand fast...
let cVal = bVal._swiftObject
enum convErr: Swift.Error {
case convErr
}
do {
let dVal = try cVal.map({ (key, val) -> (String, Any) in
if let strKey = key as? NSString {
return (strKey._swiftObject, val)
} else {
throw convErr.convErr
}
})
var eVal = [String : Any]()
for (key, value) in dVal {
eVal[key] = value
}
return eVal
} catch _ { }
return nil
}
open func data(forKey defaultName: String) -> Data? {
guard let aVal = object(forKey: defaultName),
let bVal = aVal as? NSData else {
return nil
}
return Data(referencing: bVal)
}
open func stringArray(forKey defaultName: String) -> [String]? {
guard let aVal = object(forKey: defaultName),
let bVal = aVal as? NSArray else {
return nil
}
return _SwiftValue.fetch(nonOptional: bVal) as? [String]
}
open func integer(forKey defaultName: String) -> Int {
guard let aVal = object(forKey: defaultName) else {
return 0
}
if let bVal = aVal as? NSNumber {
return bVal.intValue
}
if let bVal = aVal as? NSString {
return bVal.integerValue
}
return 0
}
open func float(forKey defaultName: String) -> Float {
guard let aVal = object(forKey: defaultName) else {
return 0
}
if let bVal = aVal as? NSNumber {
return bVal.floatValue
}
if let bVal = aVal as? NSString {
return bVal.floatValue
}
return 0
}
open func double(forKey defaultName: String) -> Double {
guard let aVal = object(forKey: defaultName) else {
return 0
}
if let bVal = aVal as? NSNumber {
return bVal.doubleValue
}
if let bVal = aVal as? NSString {
return bVal.doubleValue
}
return 0
}
open func bool(forKey defaultName: String) -> Bool {
guard let aVal = object(forKey: defaultName) else {
return false
}
if let bVal = aVal as? NSNumber {
return bVal.boolValue
}
if let bVal = aVal as? NSString {
return bVal.boolValue
}
return false
}
open func url(forKey defaultName: String) -> URL? {
guard let aVal = object(forKey: defaultName) else {
return nil
}
if let bVal = aVal as? NSURL {
return URL(reference: bVal)
} else if let bVal = aVal as? NSString {
let cVal = bVal.expandingTildeInPath
return URL(fileURLWithPath: cVal)
} else if let bVal = aVal as? Data {
return NSKeyedUnarchiver.unarchiveObject(with: bVal) as? URL
}
return nil
}
open func set(_ value: Int, forKey defaultName: String) {
set(NSNumber(value: value), forKey: defaultName)
}
open func set(_ value: Float, forKey defaultName: String) {
set(NSNumber(value: value), forKey: defaultName)
}
open func set(_ value: Double, forKey defaultName: String) {
set(NSNumber(value: value), forKey: defaultName)
}
open func set(_ value: Bool, forKey defaultName: String) {
set(NSNumber(value: value), forKey: defaultName)
}
open func set(_ url: URL?, forKey defaultName: String) {
if let url = url {
//FIXME: CFURLIsFileReferenceURL is limited to OS X/iOS
#if os(OSX) || os(iOS)
//FIXME: no SwiftFoundation version of CFURLIsFileReferenceURL at time of writing!
if CFURLIsFileReferenceURL(url._cfObject) {
let data = NSKeyedArchiver.archivedData(withRootObject: url._nsObject)
set(data._nsObject, forKey: defaultName)
return
}
#endif
set(url.path._nsObject, forKey: defaultName)
} else {
set(nil, forKey: defaultName)
}
}
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
}
}
open func addSuite(named suiteName: String) {
CFPreferencesAddSuitePreferencesToApp(kCFPreferencesCurrentApplication, suiteName._cfObject)
}
open func removeSuite(named suiteName: String) {
CFPreferencesRemoveSuitePreferencesFromApp(kCFPreferencesCurrentApplication, suiteName._cfObject)
}
open func dictionaryRepresentation() -> [String : Any] {
guard let aPref = CFPreferencesCopyMultiple(nil, kCFPreferencesCurrentApplication, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost),
let bPref = (aPref._swiftObject) as? [NSString: Any] else {
return registeredDefaults
}
var allDefaults = registeredDefaults
for (key, value) in bPref {
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() }
open func persistentDomain(forName domainName: String) -> [String : Any]? { NSUnimplemented() }
open func setPersistentDomain(_ domain: [String : Any], forName domainName: String) { NSUnimplemented() }
open func removePersistentDomain(forName domainName: String) { NSUnimplemented() }
open func synchronize() -> Bool {
return CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication)
}
open func objectIsForced(forKey key: String) -> Bool { NSUnimplemented() }
open func objectIsForced(forKey key: String, inDomain domain: String) -> Bool { NSUnimplemented() }
}
extension UserDefaults {
public static let didChangeNotification = NSNotification.Name(rawValue: "NSUserDefaultsDidChangeNotification")
public static let globalDomain: String = "NSGlobalDomain"
public static let argumentDomain: String = "NSArgumentDomain"
public static let registrationDomain: String = "NSRegistrationDomain"
}