1

I'm trying to parse csv file from my ios project(swift 2.3) and found this website. In the tutorial code, it has the following section of code :

if let content = String(contentsOfURL: contentsOfURL, 
                        encoding: encoding, error: error) {
  ...........

}

And I'm not sure what it does. Does it create a String object?

KMC
  • 1,677
  • 3
  • 26
  • 55
  • Yes, but look for a Swift 2 tutorial. This code does not compile because in Swift 2 it *throws* – vadian Dec 12 '16 at 20:44
  • I believed my project uses swift 2.3. ANd I'm getting "initializer for conditional binding must have optional type, not string." – KMC Dec 12 '16 at 20:51
  • 1
    The initializer in Swift 2 is `let content = try String(contentsOfURL: contentsOfURL, encoding: encoding)` wrapped in a `do - catch` block – vadian Dec 12 '16 at 20:54
  • @user30646 - Or, if you don't care about the details of the error object, only whether it succeeded, you can do `if let content = try? String(contentsOfURL: contentsOfURL, encoding: encoding) { ... }`, avoiding the syntactic noise of the `do`-`catch` block. – Rob Dec 12 '16 at 22:35
  • By the way, is your `NSURL` a local file URL or a remote web address? If the latter, you should use the asynchronous `NSURLSession` to retrieve the `NSData` and the convert that to a `String`. – Rob Dec 12 '16 at 22:52
  • I haven't gone through the whole tutorial. But I think it's local file. – KMC Dec 13 '16 at 00:15

2 Answers2

0

Does it create a String object?

Yes, it creates a string from the contents of the URL given by contentsOfURL and using the character encoding given by encoding. It's analogous to the following Objective-C code:

NSString *content = [NSString stringWithContentsOfURL:contentsOfURL
                                             encoding:encoding
                                                error:&error];

The if let part is a form of conditional statement. let is used to assign a value to an immutable variable. Using it in a conditional as in your example only allows the body of the conditional statement to execute if that assignment succeeds. So, if some error occurs while the data at the given URL is being fetched or if the string cannot be created for some reason, the condition fails and the body isn't executed. The whole snippet might be written like this in Objective-C:

NSString *content = [NSString stringWithContentsOfURL:contentsOfURL
                                             encoding:encoding
                                                error:&error];
if (content != nil) {
    // do something with content
}
Caleb
  • 124,013
  • 19
  • 183
  • 272
  • Thanks for the reply. That line is giving me an error though : "initializer for conditional binding must have optional type, not string." Is ti because of the swift version that I'm using? – KMC Dec 12 '16 at 20:37
  • 1
    @user30646 It's because you don't need to use the `if let` form here, the `String` initializer will never fail, so doesn't return an optional type. See [Conditional Binding: if let error – Initializer for conditional binding must have Optional type](http://stackoverflow.com/q/31038759/643383) for a full explanation. – Caleb Dec 12 '16 at 21:05
0

That code creates a string, but it does it by fetching the contents of a URL. Usually that URL points to a resource on the Internet. In that case it's a very bad way to fetch a string, since it is a synchronous network call that can hang or fail. It's a very bad idea to do synchronous networking calls on the main thread.

You could wrap that code in a GCD call to a background queue, but instead I'd suggest using NSURLSession and submitting a data task. Your search terms would be NSURLSession (or just URLSession in Swift 3) and the function func dataTask(with url: URL). (It might be easier search on it's Objective-C name, dataTaskWithURL since Google searches don't work very well with special characters.)

Take a look at a GitHub project I created called Async_demo. It has a singleton class called DownloadManager that downloads a blob of data from a specified URL. It's written to return the data as a Data object, but it would be a simple matter to convert that result from Data to a String.

The key bit of code is this:

typealias DataClosure = (Data?, Error?) -> Void

func downloadFileAtURL(_ url: URL, completion: @escaping DataClosure) {

  //We create a URLRequest that does not allow caching so you can see the download take place
  let request = URLRequest(url: url,
                           cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
                           timeoutInterval: 30.0)
  let dataTask = URLSession.shared.dataTask(with: request) {
    //------------------------------------------
    //This is the completion handler, which runs LATER,
    //after downloadFileAtURL has returned.
    data, response, error in

    //Perform the completion handler on the main thread
    DispatchQueue.main.async() {
      //Call the copmletion handler that was passed to us
      completion(data, error)
    }
    //------------------------------------------
  }
  dataTask.resume()

  //When we get here the data task will NOT have completed yet!
}
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • I don't really need the downloading part of this code since downloading is from S3 Bucket and handled by another section of code. I just want to modify this code so that it takes NSData that contains CSV as input and output object or something more appropriate for importing into core data. Thanks for explination. I will take a look at your project. – KMC Dec 12 '16 at 21:07
  • 2
    If you're using `String(contentsOfURL:)` then no, you are doing the download directly, and synchronously (unless the URL in question is a URL that points to a local file). That is a very bad idea. – Duncan C Dec 12 '16 at 21:09