Make Rest API Call in Swift

Make REST API call in Swift

You can do like this :

var url : String = "http://google.com?test=toto&test2=titi"
var request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"

NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: error) as? NSDictionary

if (jsonResult != nil) {
// process jsonResult
} else {
// couldn't load JSON, look at error
}

})

EDIT : For people have problem with this maybe your JSON stream is an
array [] and not an object {} so you have to change jsonResult to
NSArray instead of NSDictionary

How to make an API call with Swift?

Modified your variable name and data type exactly as your API response.

struct Example: Codable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}

func getJson(completion: @escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {

let decoder = JSONDecoder()
do {
let json: Example = try! decoder.decode(Example.self, from: data)
completion(json)
}catch let error {
print(error.localizedDescription)
}
}
}.resume()
}
}

getJson() { (json) in
print(json.id)
}

You can also use CodingKey and can change your response during the init period.

struct Example: Codable {
var userID: Int
var ID: Int
var title: String
var completed: Bool

enum CodingKeys: String, CodingKey {
case userID = "userId"
case ID = "id"
case title = "title"
case completed = "completed"
}

init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
userID = try values.decode(Int.self, forKey: .userID)
ID = try values.decode(Int.self, forKey: .ID)
title = try values.decode(String.self, forKey: .title)
completed = try values.decode(Bool.self, forKey: .completed)
title = "Title: \(title)"
}
}

func getJson(completion: @escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
do {
let json: Example = try JSONDecoder().decode(Example.self, from: data)
completion(json)
}catch let error {
print(error.localizedDescription)
}
}
}.resume()
}
}

getJson() { (json) in
print("ID: \(json.ID) \(json.title)")
}

How to call REST full API by providing headers in Swift

By using Alamofire it's pretty simple.

let headers: HTTPHeaders = [
"Content-Type": "application/json",
"Session": "fb4e7f9b-0f31-4709-"
]

Alamofire.request("https://httpbin.org/headers", headers: headers).responseJSON { response in
//Parse or print your response.
}

By using urlRequest

let header: HTTPHeaders = [
"Content-Type": "application/json",
"Session": "fb4e7f9b-0f31-4709-"
]
var urlRequest = URLRequest(url: URL(string: "your request url goes here."), cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10)
urlRequest.allHTTPHeaderFields = header
urlRequest.httpMethod = //.get, .post, .put
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
print(error)
} else if let data = data ,let responseCode = response as? HTTPURLResponse {
do {
// Parse your response here.
}
}
catch let parseJSONError {
print("error on parsing request to JSON : \(parseJSONError)")
}
}
}.resume()

REST API call with Swift 5.2

As the error says:

GET method must not have a body

So the error reason is the line:

request.httpBody

You have to add URL parameters(query items) instead.

let url = URL(string: "https:// Your url path")!
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
let queryItems: [URLQueryItem] = params.map { parameter in
URLQueryItem(name: parameter.key, value: parameter.value)
}

components.queryItems = queryItems

var request = URLRequest(url: components.url!)
// ...

Performing a synchronous API-Call in Swift

let response = try Data(contentsOf: url)

As long as this is a command-line app, this is fine (and generally even pretty good). If this is an iOS app, this can crash the program if it takes too long to return, so don't do that. Use the async methods. They are not too complex; they solve the required problem. (This can be done on background queues, but generally shouldn't be. It causes thread-explosion.)

If you are using Swift 5.5 with async/await, then use URLSession.bytes(for:):

let response = await try urlSession.bytes(for: url)

This is appropriate in any async code. (It is not a "synchronous call," but it is written in that way.)

There is no solution where you can fetch data from the network in synchronous code on iOS on the main thread. That's by design. You must never block the main thread. No library or framework can change that. It would even be true in Python if you were using Python on an event loop that drives a UI. It's not a language issue. It's the fact that the main runloop needs to keep processing or the UI will hang.

Cocoa And Swift - I want to make a simple rest API call using Swift 4 and Cocoa for Mac OS Application

I solved it. In new update of swift the NSAppTransportSecurity configuration is needed. I added following code to my info.plist file.

<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>yourdomain.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
</dict>
</dict>

Then also have to allow the network incoming and outgoing connection in AppSandBox->Capabilities, then all set.

How to make GET API calls that has large JSON response in Swift 5?

You should avoid using try ? except in situations where you really don't care about failures. do/try/catch is a better approach since it will tell you why something failed.

Changing your code to

do {
let countries = try JSONDecoder().decode([Country].self, from: data)
DispatchQueue.main.async {
completion(countries)
}
} catch {
print(error)
completion(nil)
}

Gives us an error on the console -

Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 509", intValue: 509), CodingKeys(stringValue: "province", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil))

which makes sense, since not all countries have provinces. To fix this, make province an optional in your data model

struct Country: Decodable {

var country: String
var updatedAt: String
var stats: Stats
var coordinates: Coordinates
var province: String?
}

How to wait for an API request to finish before storing data from function callback?

First you need to call completion when all of your data is loaded. In your case you call completion(tracksArray) before any of the getSavedTracks return.

For this part I suggest you to recursively accumulate tracks by going through all pages. There are multiple better tools to do so but I will give a raw example of it:

class TracksModel {

static func fetchAllPages(completion: @escaping ((_ tracks: [Track]?) -> Void)) {
var offset: Int = 0
let limit: Int = 50
var allTracks: [Track] = []

func appendPage() {
fetchSavedMusicPage(offset: offset, limit: limit) { tracks in
guard let tracks = tracks else {
completion(allTracks) // Most likely an error should be handled here
return
}
if tracks.count < limit {
// This was the last page because we got less than limit (50) tracks
completion(allTracks+tracks)
} else {
// Expecting another page to be loaded
offset += limit // Next page
allTracks += tracks
appendPage() // Recursively call for next page
}
}
}

appendPage() // Load first page

}

private static func fetchSavedMusicPage(offset: Int, limit: Int, completion: @escaping ((_ tracks: [Track]?) -> Void)) {
APICaller.shared.getUsersSavedTracks(limit: limit, offset: offset) { result in
switch result {
case .success(let model):
completion(model)
case .failure(let error):
print(error)
completion(nil) // Error also needs to call a completion
}
}
}

}

I hope comments will clear some things out. But the point being is that I nested an appendPage function which is called recursively until server stops sending data. In the end either an error occurs or the last page returns fewer tracks than provided limit.
Naturally it would be nicer to also forward an error but I did not include it for simplicity.

In any case you can now anywhere TracksModel.fetchAllPages { } and receive all tracks.

When you load and show your data (createSpinnerView) you also need to wait for data to be received before continuing. For instance:

func createSpinnerView() {

let loadViewController = LoadViewController.instantiateFromAppStoryboard(appStoryboard: .OrganizeScreen)
add(asChildViewController: loadViewController)

TracksModel.fetchAllPages { tracks in
DispatchQueue.main.async {
self.tracksArray = tracks
self.remove(asChildViewController: loadViewController)
self.navigateToFilterScreen(tracksArray: tracks)
}
}

}

A few components may have been removed but I hope you see the point. The method should be called on main thread already. But you are unsure what thread the API call returned on. So you need to use DispatchQueue.main.async within the completion closure, not outside of it. And also call to navigate within this closure because this is when things are actually complete.

Adding situation for fixed number of requests

For fixed number of requests you can do all your requests in parallel. You already did that in your code.
The biggest problem is that you can not guarantee that responses will come back in same order than your requests started. For instance if you perform two request A and B it can easily happen due to networking or any other reason that B will return before A. So you need to be a bit more sneaky. Look at the following code:

private func loadPage(pageIndex: Int, perPage: Int, completion: @escaping ((_ items: [Any]?, _ error: Error?) -> Void)) {
// TODO: logic here to return a page from server
completion(nil, nil)
}

func load(maximumNumberOfItems: Int, perPage: Int, completion: @escaping ((_ items: [Any], _ error: Error?) -> Void)) {
let pageStartIndicesToRetrieve: [Int] = {
var startIndex = 0
var toReturn: [Int] = []
while startIndex < maximumNumberOfItems {
toReturn.append(startIndex)
startIndex += perPage
}
return toReturn
}()

guard pageStartIndicesToRetrieve.isEmpty == false else {
// This happens if maximumNumberOfItems == 0
completion([], nil)
return
}

enum Response {
case success(items: [Any])
case failure(error: Error)
}

// Doing requests in parallel
// Note that responses may return in any order time-wise (we can not say that first page will come first, maybe the order will be [2, 1, 5, 3...])

var responses: [Response?] = .init(repeating: nil, count: pageStartIndicesToRetrieve.count) { // Start with all nil
didSet {
// Yes, Swift can do this :D How amazing!
guard responses.contains(where: { $0 == nil }) == false else {
// Still waiting for others to complete
return
}

let aggregatedResponse: (items: [Any], errors: [Error]) = responses.reduce((items: [], errors: [])) { partialResult, response in
switch response {
case .failure(let error): return (partialResult.items, partialResult.errors + [error])
case .success(let items): return (partialResult.items + [items], partialResult.errors)
case .none: return (partialResult.items, partialResult.errors)
}
}

let error: Error? = {
let errors = aggregatedResponse.errors
if errors.isEmpty {
return nil // No error
} else {
// There was an error.
return NSError(domain: "Something more meaningful", code: 500, userInfo: ["all_errors": errors]) // Or whatever you wish. Perhaps just "errors.first!"
}
}()

completion(aggregatedResponse.items, error)
}
}

pageStartIndicesToRetrieve.enumerated().forEach { requestIndex, startIndex in
loadPage(pageIndex: requestIndex, perPage: perPage) { items, error in
responses[requestIndex] = {
if let error = error {
return .failure(error: error)
} else {
return .success(items: items ?? [])
}
}()
}
}

}

The first method is not interesting. It just loads a single page. The second method now collects all the data.

First thing that happens is we calculate all possible requests. We need a start index and per-page. So the pageStartIndicesToRetrieve for case of 145 items using 50 per page will return [0, 50, 100]. (I later found out we only need count 3 in this case but that depends on the API, so let's stick with it). We expect 3 requests starting with item indices [0, 50, 100].

Next we create placeholders for our responses using

var responses: [Response?] = .init(repeating: nil, count: pageStartIndicesToRetrieve.count)

for our example of 145 items and using 50 per page this means it creates an array as [nil, nil, nil]. And when all of the values in this array turn to not-nil then all requests have returned and we can process all of the data. This is done by overriding the setter didSet for a local variable. I hope the content of it speaks for itself.

Now all that is left is to execute all requests at once and fill the array. Everything else should just resolve by itself.

The code is not the easiest and again; there are tools that can make things much easier. But for academical purposes I hope this approach explains what needs to be done to accomplish your task correctly.



Related Topics



Leave a reply



Submit