5

In my iPhone application (develops in SWIFT) I've got to communicate with a https service (with parameters) and needs to analyse the response.

All works ok but in some cases noticed it does NOT getting the expected result... Further analysing I found it's the problem about converting server respone data to string (NSData -> NSString)...

1). When I use UTF8 Encoding I am getting nil as converted String (responseString )

    let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)

2). But with ASCII encoding it's fine (Gets the correct response server provides)

    let responseString = NSString(data: data, encoding: NSASCIIStringEncoding)

Following is a full sample code I am trying...

    let myUrl = NSURL(string: "https://myurl.com/myservice.asp")
    let request = NSMutableURLRequest(URL: myUrl!)

    request.HTTPMethod = "POST"
    request.timeoutInterval = 55.0
    let postString = "paramone=\(para1)&paramtwo=\(para2)&paramthree=\(para3)"

    // NOTE: Works ok with ASCII AND UTF8 both encoding types at this point...
    // request.HTTPBody = postString.dataUsingEncoding(NSASCIIStringEncoding)
    request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)

    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in

        if (error != nil)
        {
            println("Error: \(error)")
            println("Description: \(error.description)")
            println("Domain     : \(error.domain)")
            println("Error code : \(error.code)")
        }
        else
        {
            //???? => ENCODING PROBLEM
            // let responseString = NSString(data: data, encoding: NSASCIIStringEncoding)
            let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)

            println("Response: \(responseString)")
        }
    }
    task.resume()

I came across with few other POSTS explaining the same issue... But NOT sure if it's good to go with ASCII rather than UTF8...

Also I can't understand the response contains '£' sign and works ok with ASCII encoding (eventhough '£' is NOT in ASCII character Set), BUT NOT with UTF8.

Like to hear if I am missing anything or what the best way to go with this... Thanks.

JibW
  • 4,538
  • 17
  • 66
  • 101

2 Answers2

7

NSASCIIStringEncoding is documented as a strict 7-bit encoding for the ASCII values 0 .. 127. However, experiments show that when decoding NSData to (NS)String, it accepts arbitrary data and interprets the bytes 0 .. 255 as the Unicode characters U+0000 .. U+00FF. So when decoding, NSASCIIStringEncoding behaves identically to NSISOLatin1StringEncoding:

let bytes = (0 ..< 256).map { UInt8($0) }
let data = NSData(bytes: bytes, length: bytes.count)

let s1 = String(data: data, encoding: NSASCIIStringEncoding)!
let s2 = String(data: data, encoding: NSISOLatin1StringEncoding)!
print(s1 == s2) // true

This can explain why a character like "£" is decoded correctly even if it is not in the ASCII character set.

But note that this behavior is (as far as I know) not documented, so you should not rely on it. Also this does not work when encoding (NS)String to NSData:

let d1 = s1.dataUsingEncoding(NSASCIIStringEncoding) // nil

If the server sends a HTTP response header with a Content-Type = charset=... field then you can detect the encoding automatically, see https://stackoverflow.com/a/32051684/1187415.

If the server does not send the response encoding in the HTTP response header then you can only try different encodings. Frequently used encodings are

  • NSUTF8StringEncoding for the UTF-8 encoding,
  • NSWindowsCP1252StringEncoding for the Windows-1252 encoding,
  • NSISOLatin1StringEncoding for the ISO-8859-1 encoding.

There is also a NSString method which can detect the used encoding, however this requires that you write the data to a file first, see Convert TXT File of Unknown Encoding to String.

Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thanks Martin R... I just tried this (let encodingName = response.textEncodingName) as you suggest in the LINK. but this is nil when I check this.NOT quite sure why this is nil... Therfore I can't get the 'usedEncoding' as you suggest there.... I can use 'NSASCIIStringEncoding' and get the thing done, but as you say that behaviour is NOT documented feel NOT safe... – JibW Sep 21 '15 at 15:03
  • @JibW: As I said, if the server does not send the used encoding then you have to guess. You could try the three listed encoding (in that order) and use the first one where the conversion does not give nil. – Martin R Sep 21 '15 at 15:05
  • Hi Martin R Thanks. One last thing.... The thing I can't understand is 'NSUTF8StringEncoding' works well in all the cases except one case. Only additional character I can see that time is '£'. ASCII works in this case NOT UTF8.... Definitely the service should use the same encoding in all the cases... As a solution all I can do is to check 3 major types as you suggest. – JibW Sep 21 '15 at 15:43
  • @JibW: UTF-8 and ASCII encoding are identical for the 7-bit ASCII characters. Other characters like "à" would make the same problem. – Martin R Sep 21 '15 at 16:18
  • Just done a small test... let responseString = NSString(data: data, encoding: NSStringEncoding()) works as well... Do you know what this is... Think will be helpful for others as well... – JibW Sep 21 '15 at 16:27
  • 1
    @JibW: `NSStringEncoding()` just returns the value zero, which is not a valid encoding. It is treated like the ASCII encoding, but can cause warnings like *"Incorrect NSStringEncoding value 0x0000 detected. Assuming NSASCIIStringEncoding. Will stop this compatiblity mapping behavior in the near future."* – Martin R Sep 21 '15 at 19:53
6

@JibW this code will helps you to analyse response and easy to understand...do some additional changes as per your requirements.!

    let URL = NSURL(string: "Paste your url here")!
    var request = NSMutableURLRequest(URL: URL)
    var session = NSURLSession.sharedSession()
    request.HTTPMethod = "POST"
    request.timeInterval = 55.0
    var error: NSError?
    request.HTTPBody = NSJSONSerialization.dataWithJSONObject(parameters, options: nil, error: &error)
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")


    var data: NSData!
    var response: NSURLResponse!

    var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in

        var httpResponse = response as! NSHTTPURLResponse
        println("\(httpResponse.statusCode)")

        var strData = NSString(data: data, encoding: NSUTF8StringEncoding)
        println("Body: \(strData)")

         var err: NSError?
        var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves, error: &err) as? NSDictionary

         if(err != nil) {
            println(err!.localizedDescription)
            let jsonStr = NSString(data: data, encoding: NSUTF8StringEncoding)
            println("Error could not parse JSON: '\(jsonStr)'")
        }

        else

        {
            // The JSONObjectWithData constructor didn't return an error. But, we should still
            // check and make sure that json has a value using optional binding.
            if let parseJSON = json {
                // Okay, the parsedJSON is here, let's get the value for 'success' out of it
                var success = parseJSON["success"] as? Int
                println("Succes: \(success)")
            }

            else {

                // Woa, okay the json object was nil, something went worng. Maybe the server isn't running?
                let jsonStr = NSString(data: data, encoding: NSUTF8StringEncoding)
                println("Error could not parse JSON: \(jsonStr)")
            }
        }
    })

    task.resume()
Princess
  • 309
  • 3
  • 17