Adding a callback to a class function in swift - ios

In my project I created a public class to handle my network interfacing for data requests (JSON, images, etc). The function inside of the class uses Alamofire to establish a network connect and download the JSON file.
The class and the function are below:
import Foundation
import Alamofire
public class DataConnectionManager {
public class func getJSON(AppModule:String, callback:(Int) -> Void) -> Void {
switch(AppModule) {
case "Newsfeed":
Alamofire.request(.GET, "http://some-site.com/api/", encoding: .JSON).responseJSON { (_, _, JSONData, _) in
if JSONData != nil {
jsonHolder.jsonData = JSONData!
print("start")
callback(1)
}
else {
callback(0)
}
}
break
default:
break
}
}
}
I call the function in my project as seen below:
DataConnectionManager.getJSON("Newsfeed", callback: { (intCheck : Int) -> Void in
if intCheck == 1 {
println("Success")
}
else {
println("Failure")
}
})
The app will launch without any errors, however my sanity checks don't print out. In fact, when I do it this way the Alamofire.request doesn't grab the JSON feed either.
Am I heading in the right direction with this?

I got this to work, but I'm not sure how, exactly. I changed a couple of things based on user suggestions (adding error checking, etc) and it magically started working. Here's my updated code so people can see how to add a callback to their functions.
My "ConnectionManager":
import Foundation
import Alamofire
public class DataConnectionManager {
public class func getJSON(AppModule:String, callback:(Int) -> Void) -> Void {
switch(AppModule) {
case "Newsfeed":
Alamofire.request(.GET, "http://some-site.com/api/", encoding: .JSON).responseJSON { (_, _, alamoResponse, error) in
if (error != nil){
println("You've got a response error!")
callback(0)
}
else {
if alamoResponse != nil {
jsonHolder.jsonData = alamoResponse!
callback(1)
}
else {
println("You've got some random error")
callback(0)
}
}
}
break
default:
break
}
}
}
My call to the function:
DataConnectionManager.getJSON("Newsfeed", callback: { (intCheck : Int) -> Void in
if intCheck == 1 {
self.createTable()
}
else {
println("Failure")
}
})

I'm using swift 2.0 + SwiftyJSON and this is my code to implement this:
class func getJSON(AppModule:String, urlToRequest: String, resultJSON:(JSON) -> Void) -> Void {
var returnResult: JSON = JSON.nullJSON
switch(AppModule) {
case "all":
request(.GET, urlToRequest)
.responseJSON { (_, _, result) in
if (result.isFailure){
print("You've got a response error!")
resultJSON(nil)
}
else {
if (JSON(result.value!) != nil) {
returnResult = JSON(result.value!)
resultJSON(returnResult)
}
else {
print("You've got some random error")
}
}
}
break
default:
break
}
}
And call the function like this:
DataManager.getJSON("all",urlToRequest: "myurl.com", resultJSON: { (result: JSON) -> Void in
if (result == nil){
// error with result json == nil
return
}else{
//do something with json result
}
})
Hope this can be helpful.

Related

return array of object with Alamofire

In my app I am using AlamofireObjectMapper.
I want to make a method that returns an array of objects. With the help of Alamofire I made a GET request, which gives the response as responseArray.
With using void function array listOfType always has values.
But when I use non-void function that should return array of object MedicineType, array listOfType is nil.
So here is my code.
func getAll() -> [MedicineType] {
var listOfTypes: [MedicineType]?;
Alamofire.request(BASE_URL, method:.get)
.responseArray(keyPath:"value") {(response: DataResponse<[MedicineType]>) in
if let status = response.response?.statusCode {
switch(status) {
case 200:
guard response.result.isSuccess else {
//error handling
return
}
listOfTypes = response.result.value;
default:
log.error("Error", status);
}
}
}
return listOfTypes!;
}
As i said in my comment, you need to do this in closure, instead of return it, because your call for Alamofire is async so your response will be async
This is an example, you need to add your error handle
func getAll(fishedCallback:(_ medicineTypes:[MedicineType]?)->Void){
var listOfTypes: [MedicineType]?;
Alamofire.request(BASE_URL, method:.get)
.responseArray(keyPath:"value") {(response: DataResponse<[MedicineType]>) in
if let status = response.response?.statusCode {
switch(status) {
case 200:
guard response.result.isSuccess else {
//error handling
return
}
finishedCallback(response.result.value as! [MedicineType])
default:
log.error("Error", status);
finishedCallback(nil)
}
}
}
}
Use it
classObject.getAll { (arrayOfMedicines) in
debugPrint(arrayOfMedicines) //do whatever you need
}
Hope this helps
Try closure
func getAll(_ callback :(medicineTypes:[MedicineType]?) -> Void) -> Void {
Alamofire.request(BASE_URL, method:.get)
.responseArray(keyPath:"value") {(response: DataResponse<[MedicineType]>) in
if let status = response.response?.statusCode {
switch(status) {
case 200:
guard response.result.isSuccess else {
//error handling
return
}
listOfTypes = response.result.value;
callback(listOfTypes)
default:
log.error("Error", status);
callback({})
}
}
}
}

Unit testing Alamofire + PromiseKit with Mockingjay

I have the following code in my project:
import PromiseKit
import SwiftyJSON
class ChartHandler {
var alamofireManager: Alamofire.Manager?
init() {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.timeoutIntervalForRequest = 30
self.alamofireManager = Alamofire.Manager(configuration: configuration)
}
func requestTabsJSONWithLinkKey(linkKey: String) -> Promise<JSON> {
return Promise { fulfill, reject in
alamofireManager?.request(.GET, "https://.../\(linkKey)/").responseJSON {
response in
switch response.result {
case .Success:
if let value = response.result.value {
fulfill(JSON(value))
}
case .Failure(let error):
if error.code == NSURLErrorTimedOut {
reject(RequestResult.TimedOut)
} else {
reject(RequestResult.ConnectionFailed)
}
}
}
}
}
}
And the unit test for this function using Mockingjay framework
func testChartHandlerTabsRequest() {
let expectation = expectationWithDescription("tabs json test")
let body = ["json": "test"]
stub(everything, builder: json(body))
chartHandler.requestTabsJSONWithLinkKey("test_link_key").then { jsonFromRequest -> Void in
print(jsonFromRequest)
XCTAssertTrue(jsonFromRequest.dynamicType == JSON([String: AnyObject]()).dynamicType, "Pass")
expectation.fulfill()
}.error { err in
print("Error: \(err)")
}
waitForExpectationsWithTimeout(5.0, handler: nil)
}
Problem is the following: the execution going into "error" branch of code instead of "then" it is result of the stubing is not working properly in that case and i dont know how to figure it out. Any suggestions?

Cannot call value of non-function type 'NSHTTPURLResponse?'

I've seen this problem a few times on this site but none of the solutions seem to work.
I am extending the alamofire request to have an array of repo objects as a result; however, I keep getting the error in the title. Here is the code:
extension Alamofire.Request {
class func repoArrayResponseSerializer() -> ResponseSerializer<Array<Repo>, NSError> {
return ResponseSerializer { request, response, data, error in
guard error == nil else { return .Failure(error!) }
guard data != nil else { return .Failure(error!) }
do {
let jsonData: AnyObject? = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
let json = JSON(jsonData!)
if json.error != nil || json == nil {
return .Failure(error!)
}
var repos: Array = Array<Repo>()
for (_, jsonRepo) in json {
let repo = Repo(json: jsonRepo)
repos.append(repo)
}
return .Success(repos)
} catch {
return .Failure(error as NSError)
}
}
}
func responseRepoArray(completionHandler: Result<Array<Repo>, NSError> -> Void) -> Self {
return response(responseSerializer: Request.repoArrayResponseSerializer(), completionHandler: completionHandler)
}
}
Any and all help is appreciated.

How do I load JSON data from multiple URLs stored in an array, one by one?

I have an array of urls (selected by the user) from which I want to load JSON data one by one and populate a UITableView. Following is the code I've used but it doesn't seem to work. Can anyone provide me with a solution?
while (i < fbPagesURLS.count)
{
let newUrl = NSURL(string: fbPagesURLS[i])!
let task = NSURLSession.sharedSession().dataTaskWithURL(newUrl, completionHandler: { (data, response, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if error == nil
{
print(data)
print("----------------------------")
i++;
}
})
})
task.resume()
}
It doesn't work so something is definitely wrong. I'd appreciate it if someone could point out my mistake.
Thanks!
EDIT: As advised, I wrote a recursive function. It is working but not all of the JSONs are being displayed. I don't understand what'w wrong.
func loader(let i : Int, var array : [String])
{
if i < array.count
{
let url = NSURL(string: array[i])
let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if error == nil
{
do
{
let jsondata = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
print(jsondata)
print("--------------------------")
}
catch
{
}
}
})
})
task.resume()
self.loader(i+1, array: array)
}
else
{
return
}
}
EDIT 2:
Thanks for all the valuable input, guys, I fixed it!.
I enclosed the function call in dispatch_async() and it works fine now.
Here is the updated code:
fbFeedMessages.removeAll()
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.loader(fbPagesURLS)
}
try like this , assign your urls to fbPagesURLS and call the loadData method
var currentItem = 0
func loadData()
{
if fbPagesURLS.count > currentItem
{
currentItem++
}else{
return
}
let task = NSURLSession.sharedSession().dataTaskWithURL(fbPagesURLS[currentItem], completionHandler: { (data, response, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if error == nil
{
print(data)
print("----------------------------")
self.loadData()
}
})
})
task.resume()
}

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