Temperature unit stored in UserDefaults switches from °F to \U00b0F

This is really weird. My app involves a number of unit-of-measure choices made by the user for expressing pressure, temperature and so on. I save these user preferences in UserDefaults and for the most part it all works great. Except for one specific case, when "°F" comes back as "\U00b0F".


The only way I know to see this anomaly is to store the choice of "°F" in an array stored in UserDefaults...


var oldUnit = Central().getKey("tempUnit", defaultValue: "°F")  // The user-entered, preferred temperature unit
oldUnits[0] = oldUnit  // Puts the stored unit preference into an array with other units choices
defaults.set(oldUnits, forKey: "oldUnits")   // Saves the array to UserDefaults


...and then write the userdefaults to a text file to examine the contents:


let filemgr = FileManager.default
let dir: URL = filemgr.urls(for: .documentDirectory, in: .userDomainMask).last! as URL
let url = dir.appendingPathComponent("defaultsFile.txt")
if filemgr.fileExists(atPath: url.path) {  // Try to get rid of an existing file
     do {
     try! filemgr.removeItem(at: url as URL)
       } catch {
            }
        } else {
        }
        
 var result = " "
        logSB.verbose("Writing the userDefaults to the iCloud defaults file...")
        for (key, value) in defaults.dictionaryRepresentation() {
            keyStore.set(value, forKey: key)
            do {
                try "\(key), \(value)".appendLineToURL(fileURL: url as URL)  // Creates a new file if one is not pres//                logSB.info("Writing \(value) for \(key) to file (not Keystore)")
            }
            catch {
                logSB.error("Could not write to local file")
            }
        }
        do {
            result = try String(contentsOf: url as URL, encoding: String.Encoding.utf8)
            //   
        } catch {
            logSB.error("Could not read the defaults file after writing it")
        }

Any key-value pair set to °F remains that way thorugh this process, but the °F placed into the array and then stored, shows up as \U00b0F.


My app has a rare problem where it crashes at startup after a reinstall. I have no idea if this is the cause of that but it sure is suspicious.


Any ideas?

Replies

Do you observe the same for °C ?


I would guess that ° being a punctuation character should be escaped.

It’s hard to say what’s going on here because the code snippets you posted are incomplete. For example, what is the

Central
type referenced in line 1 of your first snippet? And where does
appendLineToURL(_:)
in line 17 of your second snippet come from?

My recommendation is that you distill this problem down into a small but complete test case that you can post here. It’s possible that this act of distillation will reveal the source of the problem. If so, you’re all set, but if not then folks here can take a look at your code to see if they can spot what’s wrong.

My app has a rare problem where it crashes at startup after a reinstall.

It’s hard to say without more info. My recommendation is that you first get to the bottom of this problem. Once that’s done, start a new thread with a crash report showing the crash and we can pick things up from there.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Will do. I intentionally simpified things and overdid it.


Regarding the rare crash on startup, a user reports getting past that by going to Settings => User name => iCloud and switching off iCloud usage by my app. This allowed the new install to fire up and the app set that switch back to "on" all by itself. Good to go.


I have no idea what's happening but it does seem to be related to synching of the UserDefaults with the iCloud NSUbiquitousKeyValueStore. Turning off internet access for a minute might have also fixed the problem. I hope I can remember this if the problem ever appears again!

Yes, it's the identical situation with °C (or °R or °K). When stored directly, these all remain as set. But when any of those is placed into an array of strings, and then written to a text file, the degree symbol comes back out as "\U00b0C".

And what about escaping the ° punctuation character

"\°F"

Here's all the code you need to reproduce the problem in a Playground. Give it a try.


Placing °F directly into UserDefaults comes back (prints) as °F. Placing °F into an array first and then into UserDefaults prints as \U00b0F.


import UIKit
let defaults: UserDefaults = UserDefaults.standard

var oldUnits = [String](repeating: "", count: 5)

oldUnits[0] = "°F"

defaults.set(oldUnits, forKey: "oldUnits")
defaults.set("°F", forKey: "TempUnit")

for (key, value) in defaults.dictionaryRepresentation() {
    do {
        print("\(key), \(value)")
    }
}



OUTPUT

NSLanguages, (
    en
)
NSInterfaceStyle, macintosh
AppleITunesStoreItemKinds, (
    "itunes-u",
    movie,
    album,
    ringtone,
    "software-update",
    booklet,
    tone,
    "music-video",
    "tv-episode",
    "tv-season",
    song,
    podcast,
    software,
    audiobook,
    "podcast-episode",
    wemix,
    eBook,
    mix,
    artist,
    document
)
TempUnit, °F
oldUnits, (
    "\U00b0F",
    "",
    "",
    "",
    ""
)
AppleLanguages, (
    en
)
AKLastIDMSEnvironment, 0
AppleLocale, en_US
DVTDeviceRunExecutableOptionREPLMode, 1

That simple syntax doesn't work. The following shows no error but produces the same result.

oldUnits[0] = "\u{00b0}F"


See my Playground example in my reply to Eskimo.

This is a workaround. It requires knowing the name of an array and testing for it.

let defaults: UserDefaults = UserDefaults.standard

var oldUnits = [String](repeating: "", count: 5)

oldUnits[0] = "°F"

defaults.set(oldUnits, forKey: "oldUnits")
defaults.set("°F", forKey: "TempUnit")
for (key, value) in defaults.dictionaryRepresentation() {
    if key == "oldUnits" {
        let a: [String] = value as! [String]
        print(a)
    }
        do {
        print("\(key), \(value)")
    }
}



OUTPUT (excerpted to relevant lines)

["°F", "", "", "", ""]
oldUnits, (
    "\U00b0F",
    "",
    "",
    "",
    ""
TempUnit, °F

That's just a problem of default output format of `print`. The content String are equivalent even when displayed as "°F" or "\U00b0F".


for (key, value) in defaults.dictionaryRepresentation() {
    do {
        if let array = value as? NSArray {
            print(key, array)
            print("\(key)[0]", array[0])
        } else {
            print(key, value)
        }
    }
}


Output:

oldUnits (

"\U00b0F",

"",

"",

"",

""

)

oldUnits[0] °F

OK, totally reproductible.


In fact this seem to occur with any character beyond U+00A0


Tested with ¡ (U+00A1)

Or ± (U+00B1)

Or µ (U+00B5)

However, the problem does not show in these prints


oldUnits[0] = "°F"
let value = oldUnits[0]
print("oldUnits[0]", oldUnits[0])
print("value \(value)")
for val in oldUnits {
    print("val \(val)")
}



oldUnits[0] °F

value °F

val °F

I intentionally simpified things and overdid it.

No worries. I believe that OOPer has identified the root cause of this particular mystery. Please do file a bug report about the print problem, and post your bug number, just for the record.

Regarding the rare crash on startup …

You should start a new thread for that, ideally over in Xcode > Debugger unless you have specific reason to believe that the crash is Swift-specific. Please include a symbolicated Apple crash report in your initial post, using the

<>
button to make it more readable by formatting it as code.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Yes, I confirm that the "anomaly" does not appear without sending the array into the UserDefaults.


I'm tempted to say this is a bug.

There is likely some internal formatting when sending to UserDefaults (and that makes sense, to avoid characters that could be ill-interpreted in the file.

If not a bug, thet requires at least some explanation.


I would file a bug against documentation.

UserDefaults uses NSArray and NSDictionary internally when storing arrays and dictionaries. Both prefers plist-like format when generating default `description` for the contents. All strings containing non-ASCII characters are represented in escaped format like \Uhhhh in plist-like format.