Search code examples
swiftswitch-statementwatchkitapple-watch

Swift Switch statement fails to match strings: What could cause this?


I have some code to determine the Series of an Apple Watch based on its machine ID. In all testing I've been able to do on real Apple Watches, it works as expected.

However, once out in the wild, it fails to match in some cases, and falls back on the default case. I added some logging to the default to show the see the model value that was passed in when it failed to match, to see if there were unexpected values being passed into the function.

To my surprise, the values that are being passed in seem to match, but it's still hitting the default case. For example Watch3,4 is frequently being logged as the value of model that was passed in, which should match the case to produce Series 3, and does in my testing, but for some reason it's not always matching in my app once released. The same goes for many other model strings here.

Does it seem like there is something I'm missing here? Like the possibility of other non-visible characters ending up in the string I'm checking? Or something I'm missing about how switch statements work in Swift?

To be clear, it's not failing all the time. It works most of the time, but it sometimes fails even though logging shows the value passed in is an expected value that matches a non-default case.

Here's my code, minus the logging used for diagnosis:

import Foundation

func getWatchModel() -> String? {
    var size: size_t = 0
    sysctlbyname("hw.machine", nil, &size, nil, 0)
    var machine = CChar()
    sysctlbyname("hw.machine", &machine, &size, nil, 0)
    return String(cString: &machine, encoding: String.Encoding.utf8)
}

func getSeries(model: String) -> String {
    switch model {
    case "Watch2,3", "Watch2,4":
        return "Series 2"
    case "Watch2,6", "Watch2,7":
        return "Series 1"
    case "Watch3,1", "Watch3,2", "Watch3,3", "Watch3,4":
        return "Series 3"
    case "Watch4,1", "Watch4,2", "Watch4,3", "Watch4,4":
        return "Series 4"
    case "Watch5,1", "Watch5,2", "Watch5,3", "Watch5,4":
        return "Series 5"
    default:
        return "Unknown Series"
    }
}

let series: String?

if let model = getWatchModel() {
    series = getSeries(model: model)
} else {
    series = nil
}

print(series ?? "Apple Watch machine ID could not be determined")

Solution

  • I suspect the problem is actually in your getWatchModel method, specifically in the line:

    var machine = CChar()
    
    

    This allocates a single byte. You need to allocate size bytes. Here's one way of doing it:

        func getWatchModel() -> String? {
            var size: size_t = 0
            sysctlbyname("hw.machine", nil, &size, nil, 0)
            var machine = [CChar](repeating: 0, count: size)
            sysctlbyname("hw.machine", &machine, &size, nil, 0)
            return String(cString: machine, encoding: .utf8)
        }
    

    I suspect this lead to the String containing garbage. I'm actually surprised you did not see any crashes.