31

I am very new to Swift.

I want to create something like API on Swift for my educational app.

I have this code:

static func getFilm(filmID: Int) -> String {
    
    print("getFilm")
    
    let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
    var request = URLRequest(url: url)
    
    var returnData: String = ""
    
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        if var responseVar = response, var dataVar = data {
            print(responseVar)
            returnData = String(data: dataVar, encoding: .utf8)
        } else {
            print(error)
        }
    }
    
    task.resume()
    
    return returnData
}

And I try to convert Data to String in this line: returnData = String(data: dataVar, encoding: .utf8)

Swift compiler gives me an error, and change this line to returnData = String(data: dataVar, encoding: .utf8)! , when I execute this line I get empty returnData variable.

If I use basic example line print(String(data: data, encoding: .utf8)) everything will be OK and I can see data in XCode console.

So, how I can convert Data to String?

Arasuvel
  • 2,971
  • 1
  • 25
  • 40
IlyaGutnikov
  • 627
  • 1
  • 7
  • 19

4 Answers4

52

This is an example using a completion handler:

class func getFilm(filmID: Int, completion: @escaping (String) -> ()) {
    let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
    
    URLSession.shared.dataTask(with:url) { (data, response, error) in
      if error != nil {
        print(error!)
        completion("")
      } else {
        if let returnData = String(data: data!, encoding: .utf8) {
          completion(returnData)
        } else {
          completion("")
        }
      }
    }.resume()
}

And you call it

MyClass.getFilm(filmID:12345) { result in
   print(result)
}

In case of an error the completion handler returns an empty string.

MyClass is the enclosing class of getFilm method. Most likely the web service will return JSON, so you might need to deserialize the JSON to an array or dictionary.


In a more sophisticated version create an enum with two cases and associated values

enum ConnectionResult {
  case success(String), failure(Error)
}

With a little more effort demonstrating the subtle power of Swift you can return either the converted string on success of the error on failure in a single object.

class func getFilm(filmID: Int, completion: @escaping (ConnectionResult) -> ()) {
    let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
    
    URLSession.shared.dataTask(with:url) { (data, response, error) in
      if error != nil {
        completion(.failure(error!))
      } else {
        if let returnData = String(data: data!, encoding: .utf8) {
          completion(.success(returnData))
        } else {
          completion(.failure(NSError(domain: "myDomain", code: 9999, userInfo: [NSLocalizedDescriptionKey : "The data is not converible to 'String'"])))
        }
      }
    }.resume()
}

On the caller side a switch statement separates the cases.

MyClass.getFilm(filmID:12345) { result in
    switch result {
    case .success(let string) : print(string)
    case .failure(let error) : print(error)
    }
}

Meanwhile – with async/await in Swift 5.5+ – it has become much more comfortable

class func getFilm(filmID: Int) async throws -> String {
    print("getFilm")
    
    let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return String(data: data, encoding: .utf8)!
}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • Excuse me, but I dont understand, how I can get return value from getFilm? I mean something like this `var test = getFilm()` – IlyaGutnikov Oct 21 '16 at 21:20
  • Your `MyClass.getFilm(filmID:12345) { result in print(result) }` just blow my mind. I think it would be simple - I have function with input and output parameters, and I understand how I can get `result` in outer code. – IlyaGutnikov Oct 21 '16 at 21:23
  • I think, what's really confusing is that how `result in print(result)` gets injected into `completion(returnData)`. I wished there was a bit more elaboration how this magic happens. – Houman Dec 08 '17 at 09:16
  • @Houman `result` is a short form for `(result : Result) -> ()`. The `completion` call passes the closure with the appropriate `Result` parameter – vadian Dec 08 '17 at 09:46
  • Many errors. Can't implement as a class. If I do so: Cannot call value of non-function type 'String'. – J A S K I E R Sep 17 '18 at 10:44
  • @Oleksandr The code is supposed to be executed within a class `MyClass`. It works also as an instance method in a view controller without the `class` keyword. – vadian Sep 17 '18 at 10:54
3

I had this problem, you can't use encoding: .utf8 for unpredictable data. It will return nil every time.

Use this instead:

String(decoding: data, as: UTF8.self)

redbastie
  • 199
  • 1
  • 8
3

For anyone coming in future (which are probably not interested in OP's film code?!);

Simply, try something like:

extension Data {
    public func toString() -> String {
        return String(data: self, encoding: .utf8) ?? "";
    }
}

See also my toHex related answer

Top-Master
  • 7,611
  • 5
  • 39
  • 71
0

If you know an instance of Data contains a String and you want to convert it, you should use the String(decoding:as:) initializer, like this:

let str = String(decoding: data, as: UTF8.self)

If the Data instance can’t be converted to a UTF-8 string, you’ll get sent back an empty string. This means you do need to know which format is used to store the string, but UTF-8 is usually the best to go with.