Results won't append from JSON - ios

The JSON file I am using: https://api.myjson.com/bins/49jw2
I am using SwiftyJSON for parsing.
The variable chores wont be populated outside the method parseJson
var chores: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tfWhat.delegate = self
tfHowMuch.delegate = self
loadJson()
// wont even print
for chore in self.chores {
print("beschrijving: " + chore)
}
// prints 0
print(chores.count)
}
func loadJson() -> Void {
let url = NSURL(string: "https://api.myjson.com/bins/49jw2")
NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, response, error) in
if let error = error {
print("Error: \(error.localizedDescription)")
} else {
if let data = data {
let json = JSON(data: data)
self.parseJson(json["appdata"]["klusjes"][])
} else {
print("no data")
}
}
}).resume()
}
func parseJson(jsonObject : JSON) -> Void {
for (_, value) in jsonObject {
self.chores.append(value["beschrijving"].stringValue)
}
// prints:
// beschrijving: Heg knippen bij de overburen
// beschrijving: Auto van papa wassen
for chore in self.chores {
print("beschrijving: " + chore)
}
// prints:
// 2
print(chores.count)
}

When you call an asynchronous method like NSURLSession.sharedSession().dataTaskWithURL it gets executed in the background, so whatever you launch just after this instruction is actually executed while the background task is running, so your array is not populated yet when you look at it.
A simple way to overcome this mistake is to use a "callback": a closure that will be executed once the data is available.
For example, let's add a callback
(json: JSON)->()
to the loadJson method:
func loadJson(completion: (json: JSON)->())
and place the call where the data will be available:
func loadJson(completion: (json: JSON)->()) {
let url = NSURL(string: "https://api.myjson.com/bins/49jw2")
NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, response, error) in
if let error = error {
print("Error: \(error.localizedDescription)")
} else {
if let data = data {
// the callback executes *when the data is ready*
completion(json: JSON(data: data))
} else {
print("no data")
}
}
}).resume()
}
and use it with a trailing closure like this:
loadJson { (json) in
// populate your array, do stuff with "json" here, this is the result of the callback
}

Related

How to extract data from networking class into variable in same class so that it can be accessed from other class in ios

How to extract data from networking class into variable in same class so that it can be accessed from other class in ios
Code for Networking Class
import Foundation
struct NetworkManager {
func fetchData(url : String) {
print("Neeraj here")
let sessionURL = URL(string: url)
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: sessionURL!) { (data, response, error) in
if error == nil {
if let safeData = data {
if let parsedData = self.parseData(data : safeData) {
print("got data")
}
}
}
else {
print("error in data task is \(String(describing: error))")
}
}
dataTask.resume()
}
func parseData(data : Data) -> DataModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(DataModel.self, from: data)
return decodedData
} catch {
print("error while parsing data \(error)")
return nil
}
}
}
Here where i am getting data, i want that data to be stored somewhere or in same class so that i can access it from class i am calling this method fetchData
You can use the closure to return the value out of the function. This practice is functional programming, almost using for async function.
func fetchData(url: String, completion: (DataModel?) -> ()) {
...
if let parsedData = self.parseData(data : safeData) {
print("got data")
completion(parsedData)
} else {
completion(nil)
}
}
And then, to use it:
NetworkManager().fetchData(url: "https://google.com", completion: { data in
// handle the “data”
})

How to use a escaping closure in a func to return some data?

In my Code,I want to return some data in a fun after a network request.
func funcName()->String{
var data = "demo"
DataLoader.fetch { result in
if case .success(let fetchedData) = result {
data = fetchedData
} else {
data = "Fail"
}
}
return data
}
The DataLoader is used to get some data from API, the code just like:
struct DataLoader {
static func fetch(completion: #escaping (Result<String, Error>) -> Void) {
let url = URL(string: "URL LINK")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else {
completion(.failure(error!))
return
}
completion(.success(data!))
}
task.resume()
}
}
But as you know data will always be "demo" because of escaping closure.
So how can I return the data after the network request complete without modify the function funcName's params?
If the parameters of the function are not modified.
I'm new in swift, and really at a loss.Thanks a lot if you could help me out!
You have to modify the funcName also to completion closure, instead of return.
func funcName(completion: #escaping (String) -> Void) {
var data = "demo"
DataLoader.fetch { result in
if case .success(let fetchedData) = result {
data = fetchedData
} else {
data = "Fail"
}
completion(data)
}
}
And usage:
funcName { name in
print(name)
}

Add a return value to a closure [duplicate]

This question already has answers here:
Run code only after asynchronous function finishes executing
(2 answers)
Closed 5 years ago.
I'm not very familiar with closure. I'm using this function to download a JSON file from a remote server
requestJson(){
// Asynchronous Http call to your api url, using NSURLSession:
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://api.site.com/json")!, completionHandler: { (data, response, error) -> Void in
// Check if data was received successfully
if error == nil && data != nil {
do {
// Convert NSData to Dictionary where keys are of type String, and values are of any type
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! [String:AnyObject]
// Access specific key with value of type String
let str = json["key"] as! String
} catch {
// Something went wrong
}
}
}).resume()
}
Is it possible to make the function requestJson() return the JSON file when its loaded? Or it's not possible because it's loaded asynchronously and could not be ready? Want I'm trying to do is something like following:
requestJson() -> **[String : AnyObject]**{
// Asynchronous Http call to your api url, using NSURLSession:
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://api.site.com/json")!, completionHandler: { (data, response, error) -> Void in
// Check if data was received successfully
if error == nil && data != nil {
do {
// Convert NSData to Dictionary where keys are of type String, and values are of any type
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! [String:AnyObject]
// Access specific key with value of type String
**return json**
} catch {
// Something went wrong
}
}
}).resume()
}
You can give the function params an #escaping callback returning an array or whatever you need;
An example of this for a network request;
class func getGenres(completionHandler: #escaping (genres: NSArray) -> ()) {
...
let task = session.dataTask(with:url) {
data, response, error in
...
resultsArray = results
completionHandler(genres: resultsArray)
}
...
task.resume()
}
Then to call it you could do something like this;
override func viewDidLoad() {
getGenres {
genres in
print("View Controller: \(genres)")
}
}
//MARK: Request method to get json
class func requestJSON(completion: #escaping (returnModel: String?) -> Void) {
//here you write code for calling API
}
//MARK: Calling function to retrieve return string
API.requestJSON(completion: { (string) in
//here you can get your string
})
func httpGet(request: NSURLRequest!, callback: (NSString, NSString?) -> Void)
{
var session = NSURLSession.sharedSession()
var task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if error != nil {
callback("", error.localizedDescription)
} else {
var result = NSString(data: data, encoding:
NSASCIIStringEncoding)!
callback(result, nil)
}
}
task.resume()
}
func makeRequest(callback: (NSString) ->Void) -> Void {
var request = NSMutableURLRequest(URL: NSURL(string: "http://sample_url")!)
var result:NSString = ""
httpGet(request){
(data, error) -> Void in
if error != nil {
result = error!
} else {
result = data
}
callback(data)
}
}
Usage:-
self.makeRequest(){
(data) -> Void in
println("response data:\(data)")
}

Include a return handler in async call in Swift

I'm experimenting with async calls but I'm a little lost. The print(json) in the viewDidLoad outputs an empty dictionary, but the one within the function prints correctly. This is unsurprising; it gets to that print before the async is completed. I can't figure out how to fix it; I tried putting the return within the completion handler, but I got an error that Unexpected non-void return value in void function. I tried changing the completion handler to expect a return value, but either that's not the right approach or I was doing it wrong.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let json = getJson("https://maps.googleapis.com/maps/api/geocode/json?address=WashingtonDC&sensor=false")
print(json)
}
func getJson(url: String) -> AnyObject {
var json:AnyObject = [:]
let urlPath = NSURL(string: url)
let urlRequest = NSURLRequest(URL: urlPath!)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: {
(data, response, error) in
if error != nil {
print("Error")
} else {
do {
json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
print(json)
} catch {
print("json error")
}
}
})
task.resume()
return json
}
}
You will need to have a completion handler based interface to your async API.
func getJson(url: String, completion : (success: Bool, json: AnyObject? ) ->Void ) -> Void {
var json:AnyObject = [:]
let urlPath = NSURL(string: url)
let urlRequest = NSURLRequest(URL: urlPath!)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: {
(data, response, error) in
if error != nil {
print("Error")
} else {
do {
json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
print(json)
//Call the completion handler here:
completion(success : true, json :json )
} catch {
print("json error")
completion(success : false, json :nil )
}
}
})
task.resume()
}
}
Now you call call this API as follows-
override func viewDidLoad() {
super.viewDidLoad()
getJson("https://maps.googleapis.com/maps/api/geocode/json?address=WashingtonDC&sensor=false") { (success, json) -> Void in
if success {
if let json = json {
print(json)
}
}
}
}

Chain multiple Alamofire requests

I'm looking for a good pattern with which I can chain multiple HTTP requests. I want to use Swift, and preferrably Alamofire.
Say, for example, I want to do the following:
Make a PUT request
Make a GET request
Reload table with data
It seems that the concept of promises may be a good fit for this. PromiseKit could be a good option if I could do something like this:
NSURLConnection.promise(
Alamofire.request(
Router.Put(url: "http://httbin.org/put")
)
).then { (request, response, data, error) in
Alamofire.request(
Router.Get(url: "http://httbin.org/get")
)
}.then { (request, response, data, error) in
// Process data
}.then { () -> () in
// Reload table
}
but that's not possible or at least I'm not aware of it.
How can I achieve this functionality without nesting multiple methods?
I'm new to iOS so maybe there's something more fundamental that I'm missing. What I've done in other frameworks such as Android is to perform these operations in a background process and make the requests synchronous. But Alamofire is inherently asynchronous, so that pattern is not an option.
Wrapping other asynchronous stuff in promises works like this:
func myThingy() -> Promise<AnyObject> {
return Promise{ fulfill, reject in
Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]).response { (_, _, data, error) in
if error == nil {
fulfill(data)
} else {
reject(error)
}
}
}
}
Edit: Nowadays, use: https://github.com/PromiseKit/Alamofire-
I wrote a class which handles a chain of request one by one.
I created a class RequestChain wich takes Alamofire.Request as parameter
class RequestChain {
typealias CompletionHandler = (success:Bool, errorResult:ErrorResult?) -> Void
struct ErrorResult {
let request:Request?
let error:ErrorType?
}
private var requests:[Request] = []
init(requests:[Request]) {
self.requests = requests
}
func start(completionHandler:CompletionHandler) {
if let request = requests.first {
request.response(completionHandler: { (_, _, _, error) in
if error != nil {
completionHandler(success: false, errorResult: ErrorResult(request: request, error: error))
return
}
self.requests.removeFirst()
self.start(completionHandler)
})
request.resume()
}else {
completionHandler(success: true, errorResult: nil)
return
}
}
}
And I use it like this
let r1 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("1")
}
let r2 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("2")
}
let r3 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("3")
}
let chain = RequestChain(requests: [r1,r2,r3])
chain.start { (success, errorResult) in
if success {
print("all have been success")
}else {
print("failed with error \(errorResult?.error) for request \(errorResult?.request)")
}
}
Importent is that you are telling the Manager to not execute the request immediately
let manager = Manager.sharedInstance
manager.startRequestsImmediately = false
Hope it will help someone else
Swift 3.0 Update
class RequestChain {
typealias CompletionHandler = (_ success:Bool, _ errorResult:ErrorResult?) -> Void
struct ErrorResult {
let request:DataRequest?
let error:Error?
}
fileprivate var requests:[DataRequest] = []
init(requests:[DataRequest]) {
self.requests = requests
}
func start(_ completionHandler:#escaping CompletionHandler) {
if let request = requests.first {
request.response(completionHandler: { (response:DefaultDataResponse) in
if let error = response.error {
completionHandler(false, ErrorResult(request: request, error: error))
return
}
self.requests.removeFirst()
self.start(completionHandler)
})
request.resume()
}else {
completionHandler(true, nil)
return
}
}
}
Usage Example Swift 3
/// set Alamofire default manager to start request immediatly to false
SessionManager.default.startRequestsImmediately = false
let firstRequest = Alamofire.request("https://httpbin.org/get")
let secondRequest = Alamofire.request("https://httpbin.org/get")
let chain = RequestChain(requests: [firstRequest, secondRequest])
chain.start { (done, error) in
}
You have multiple options.
Option 1 - Nesting Calls
func runTieredRequests() {
let putRequest = Alamofire.request(.PUT, "http://httpbin.org/put")
putRequest.response { putRequest, putResponse, putData, putError in
let getRequest = Alamofire.request(.GET, "http://httpbin.org/get")
getRequest.response { getRequest, getResponse, getData, getError in
// Process data
// Reload table
}
}
}
This is definitely the approach I would recommend. Nesting one call into another is very simple and is pretty easy to follow. It also keeps things simple.
Option 2 - Splitting into Multiple Methods
func runPutRequest() {
let putRequest = Alamofire.request(.PUT, "http://httpbin.org/put")
putRequest.response { [weak self] putRequest, putResponse, putData, putError in
if let strongSelf = self {
// Probably store some data
strongSelf.runGetRequest()
}
}
}
func runGetRequest() {
let getRequest = Alamofire.request(.GET, "http://httpbin.org/get")
getRequest.response { [weak self] getRequest, getResponse, getData, getError in
if let strongSelf = self {
// Probably store more data
strongSelf.processResponse()
}
}
}
func processResponse() {
// Process that data
}
func reloadData() {
// Reload that data
}
This option is less dense and splits things up into smaller chunks. Depending on your needs and the complexity of your response parsing, this may be a more readable approach.
Option 3 - PromiseKit and Alamofire
Alamofire can handle this pretty easily without having to pull in PromiseKit. If you really want to go this route, you can use the approach provided by #mxcl.
Here is another way to do this (Swift 3, Alamofire 4.x) using a DispatchGroup
import Alamofire
struct SequentialRequest {
static func fetchData() {
let authRequestGroup = DispatchGroup()
let requestGroup = DispatchGroup()
var results = [String: String]()
//First request - this would be the authentication request
authRequestGroup.enter()
Alamofire.request("http://httpbin.org/get").responseData { response in
print("DEBUG: FIRST Request")
results["FIRST"] = response.result.description
if response.result.isSuccess { //Authentication successful, you may use your own tests to confirm that authentication was successful
authRequestGroup.enter() //request for data behind authentication
Alamofire.request("http://httpbin.org/get").responseData { response in
print("DEBUG: SECOND Request")
results["SECOND"] = response.result.description
authRequestGroup.leave()
}
authRequestGroup.enter() //request for data behind authentication
Alamofire.request("http://httpbin.org/get").responseData { response in
print("DEBUG: THIRD Request")
results["THIRD"] = response.result.description
authRequestGroup.leave()
}
}
authRequestGroup.leave()
}
//This only gets executed once all the requests in the authRequestGroup are done (i.e. FIRST, SECOND AND THIRD requests)
authRequestGroup.notify(queue: DispatchQueue.main, execute: {
// Here you can perform additional request that depends on data fetched from the FIRST, SECOND or THIRD requests
requestGroup.enter()
Alamofire.request("http://httpbin.org/get").responseData { response in
print("DEBUG: FOURTH Request")
results["FOURTH"] = response.result.description
requestGroup.leave()
}
//Note: Any code placed here will be executed before the FORTH request completes! To execute code after the FOURTH request, we need the request requestGroup.notify like below
print("This gets executed before the FOURTH request completes")
//This only gets executed once all the requests in the requestGroup are done (i.e. FORTH request)
requestGroup.notify(queue: DispatchQueue.main, execute: {
//Here, you can update the UI, HUD and turn off the network activity indicator
for (request, result) in results {
print("\(request): \(result)")
}
print("DEBUG: all Done")
})
})
}
}
Details
Alamofire 4.7.2
PromiseKit 6.3.4
Xcode 9.4.1
Swift 4.1
Full Sample
NetworkService
import Foundation
import Alamofire
import PromiseKit
class NetworkService {
static fileprivate let queue = DispatchQueue(label: "requests.queue", qos: .utility)
fileprivate class func make(request: DataRequest) -> Promise <(json: [String: Any]?, error: Error?)> {
return Promise <(json: [String: Any]?, error: Error?)> { seal in
request.responseJSON(queue: queue) { response in
// print(response.request ?? "nil") // original URL request
// print(response.response ?? "nil") // HTTP URL response
// print(response.data ?? "nil") // server data
//print(response.result ?? "nil") // result of response serialization
switch response.result {
case .failure(let error):
DispatchQueue.main.async {
seal.fulfill((nil, error))
}
case .success(let data):
DispatchQueue.main.async {
seal.fulfill(((data as? [String: Any]) ?? [:], nil))
}
}
}
}
}
class func searchRequest(term: String) -> Promise<(json: [String: Any]?, error: Error?)>{
let request = Alamofire.request("https://itunes.apple.com/search?term=\(term.replacingOccurrences(of: " ", with: "+"))")
return make(request: request)
}
}
Main func
func run() {
_ = firstly {
return Promise<Void> { seal in
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + .seconds(2)) {
print("1 task finished")
DispatchQueue.main.async {
seal.fulfill(Void())
}
}
}
}.then {
return NetworkService.searchRequest(term: "John").then { json, error -> Promise<Void> in
print("2 task finished")
//print(error ?? "nil")
//print(json ?? "nil")
return Promise { $0.fulfill(Void())}
}
}.then {_ -> Promise<Bool> in
print("Update UI")
return Promise { $0.fulfill(true)}
}.then { previousResult -> Promise<Void> in
print("previous result: \(previousResult)")
return Promise { $0.fulfill(Void())}
}
}
Result
You can use the when method in PromiseKit to attach/append as many calls you want.
Here's an example from PromiseKit docs:
firstly {
when(fulfilled: operation1(), operation2())
}.done { result1, result2 in
//…
}
It worked perfectly for me and it's a much cleaner solution.
Call itself infinitely and DEFINE END CONDITION.
urlring for API link and Dictionary for json
WE may construct the queue model or delegate
func getData(urlring : String , para : Dictionary<String, String>) {
if intCount > 0 {
Alamofire.request( urlring,method: .post, parameters: para , encoding: JSONEncoding.default, headers: nil) .validate()
.downloadProgress {_ in
}
.responseSwiftyJSON {
dataResponse in
switch dataResponse.result {
case .success(let json):
print(json)
let loginStatus : String = json["login_status"].stringValue
print(loginStatus)
if loginStatus == "Y" {
print("go this")
print("login success : int \(self.intCount)")
self.intCount-=1
self.getData(urlring: urlring , para : para)
}
case .failure(let err) :
print(err.localizedDescription)
}
}
}else{
//end condition workout
}
}

Resources