2

Now apple have handily gotten rid of the NSString and String automatic compatibility, I'm having a bit of a nightmare going between the two. I'm getting a couple of NSStrings out of a dictionary and I can't convert them to regular Strings...

I've tried:

let fileNameString: String = String(format: "%@", filename!)

let fileNameString: String = (filename as! String)

let fileNameString = filename as? String

let fileNameString = (filename as? String) ?? ""

if let fileNameString = filename as? String {
    println("\(fileNameString)")
}

But all produce the error.

I've broken in at the point of conversion and can see neither NSStrings are nil:

But no joy with either. Getting Thread 1: EXC_BAD_ACCESS (code=1, address=0x20) . Am I missing something obvious here?

Even just trying to print the NSString filename before conversion causes the same error..

Posting the code prior to conversion attempt to see if that has anything to do with it...

// First we create a head request as the info I need is in the headers
var newRequest: NSMutableURLRequest = NSMutableURLRequest(URL: request.URL!)
newRequest.HTTPMethod = "HEAD"
var response: NSURLResponse?
NSURLConnection.sendSynchronousRequest(newRequest, returningResponse: &response, error: nil)

// Unwrap response as httpResponse in order to access allHeaderFields        
if let httpResponse = response as? NSHTTPURLResponse {

    let headerString = "sfn-Document-Filename"
    let headerNSString = headerString as NSString
    let filetypeString = "Content-Type"
    let filetypeNSString = filetypeString as NSString

    // This is a dictionary where the keys are NSCFStrings
    // (NSStrings, hence creating the NSStrings above)
    var allHeaders = httpResponse.allHeaderFields

    // Getting the filename out here only works with as? NSString. as? String creates the same error as converting.
    let filename = allHeaders[headerNSString] as? NSString

    // This is a string which contains the type as 'application/pdf' for example.  We only need the part after the /.
    // Again, trying to get this out as a String fails
    let typeString = allHeaders[filetypeNSString] as? NSString
    var typeArray = typeString?.componentsSeparatedByString("/") as! [NSString]
    let filetype = typeArray[1]
}

Chris Byatt
  • 3,689
  • 3
  • 34
  • 61
  • Converting NSString to String does not require forced down casting anymore so you can use `filename as String` only. About your problem what type does filename has ? – Zell B. May 14 '15 at 10:28
  • what is filename here? – user3182143 May 14 '15 at 10:30
  • @zellb Xcode is telling me it requires it as it is `NSString?`. – Chris Byatt May 14 '15 at 10:33
  • Are you sure that filename its not nil before trying to unwrap it? Can you try to optionally unwrap it with `if let myNSString = filename{ let fileNameString: String = (filename as! String) }` and check if condition is met ? – Zell B. May 14 '15 at 10:42
  • I can run that code without error on Xcode 6.3.1. Changing the fileName or typeString line to cast to String also works. – JeremyP May 14 '15 at 14:29
  • @JeremyP I've added a couple of screenshots so you can see. – Chris Byatt May 14 '15 at 14:33

1 Answers1

2

If this were an NSString, then all you’d need to do is filename as String (no !). But it sounds like the problem is your filename, of optional type NSString?, is nil. (option-click filename to confirm its type)

If there’s a reasonable default (say, an empty string), try

let fileNameString = (filename as? String) ?? ""

Or if you need to handle the nil with specific code:

if let fileNameString = filename as? String {
    // use fileNameString, which will be unwrapped and of type String
}
else {
    // log error or similar
}

Or, if you want to defer unwrapping, but want to change the type inside the possible value, you can do

let fileNameString = filename as? String
// which is a less long-winded way of saying
let fileNameString = filename.map { $0 as String }

Generally speaking, you should try and cut down on the ! usage, since it leads to problems like this. ! is only for those times when you know from the code that the value absolutely positively cannot be nil.

edit: based on your sample code, try the following:

let url = NSURL(string: "http://www.google.com")
let request = url.map { NSMutableURLRequest(URL: $0) }
request?.HTTPMethod = "HEAD"

let response: NSHTTPURLResponse? = request.flatMap {
    var response: NSURLResponse?
    NSURLConnection.sendSynchronousRequest($0, returningResponse: &response, error: nil)
    return response as? NSHTTPURLResponse
}

let headers = response?.allHeaderFields as? [String:String]

// google.com has no such header but still...
let filename = headers?["sfn-Document-Filename"]

// bear in mind someArray[1] will also crash if there's no such entry,
// first/last are better if that's what you want
let type = headers?["Content-Type"]?
              .componentsSeparatedByString(";").first?
              .componentsSeparatedByString("/").last

Everything here is optional, but safe, so you can test at various points for nil for logging/error reporting purposes.

Airspeed Velocity
  • 40,491
  • 8
  • 113
  • 118
  • Hmm, strange. The only odd thing about that is that it’s showing `(NSString?)` rather than `NSString`. Try one of the techniques above anyway, it may well be a compiler quirk and those will work as a workaround. And if that doesn’t work, maybe try `filename.0 ?? “”` – I’ve sometimes seen odd 1-tuple compiler bugs crop up and that sometimes works around them. – Airspeed Velocity May 14 '15 at 10:36
  • I've tried `filename as? String` but due to it being an `NSString?` it tells me it requires `!`. Still getting `Thread 1: EXC_BAD_ACCESS (code=1, address=0x20)` though – Chris Byatt May 14 '15 at 10:38
  • @ChrisByatt Remove `: String` (`let s = (filename as? String)` vs `let s: String = (filename as? String)`, use the first) or use `(filename as? String) ?? ""` (Airspeed's first example). And then please take some time to understand how optionals work and move away from abusing the `!` operator. – nhgrif May 14 '15 at 10:41
  • No `!` is necessary despite what it might be saying. The following works in a fresh playground in both 6.4 and 6.3.1 for me: `import Foundation;let filename: NSString? = "hello";let fileNameString = filename as? String` – Airspeed Velocity May 14 '15 at 10:41
  • Ah I see the problem (thanks @nhgrif). When you write `let fileNameString = filename as? String` you create an _optional_ string. So you can’t then specify the type of the variable being assigned to as `String`. It needs to be `String?` (or much better left implicitly typed). – Airspeed Velocity May 14 '15 at 10:43
  • Try reading [this](http://stackoverflow.com/a/27623568/3925941) and [this](http://stackoverflow.com/a/29717211/3925941) for more details on optional techniques and avoiding `!`. – Airspeed Velocity May 14 '15 at 10:44
  • If you stick in a `println(filename)`, what does it show? – Airspeed Velocity May 14 '15 at 11:05
  • @AirspeedVelocity The same Thread error. Even though I can see filename is not nil in the debugger... – Chris Byatt May 14 '15 at 11:32
  • are you certain the crash is happening on that line? if you step through, does it crash as you step over that exact line? if so, something very screwy going on, `println` shouldn’t crash unless possibly there’s an implicitly-unwrapped optional in there – Airspeed Velocity May 14 '15 at 11:34
  • Yes, it crashes as I step over that line. As you can see from above, filename is an `NSString?` – Chris Byatt May 14 '15 at 11:57
  • I've updated my question with some more code... I've no idea what's going on here. – Chris Byatt May 14 '15 at 13:41
  • OK so having seen your code – you really don't need any of those `NSString` casts at all. I'll post some alternative code to try. – Airspeed Velocity May 14 '15 at 14:31
  • @AirspeedVelocity Thanks for the attempt but... https://gyazo.com/9f1b211d2e6de667b8427385a2b1a3a1 – Chris Byatt May 14 '15 at 16:22