RxSwift toArray() not subscribing - ios

When i add toArray() before subscribing i get no callback.
googleCalendarUseCase.getEventsFromCalendars(calendars: selectedCalendars).subscribe(onNext: { (event) in
print(event.summary) //print thousands of elements
}).addDisposableTo(disposeBag)
googleCalendarUseCase.getEventsFromCalendars(calendars: selectedCalendars).toArray().subscribe(onNext: { (events) in
print(events.count) // Never gets called
}).addDisposableTo(disposeBag)
Maybe the problem is with the function getEventsFromCalendar but unsure why it works if i dont do toArray():
func getEventsFromCalendars(calendars: [GoogleCalendar.Calendar], nextPageToken: String? = nil) -> Observable<GoogleCalendar.Event> {
return Observable<GoogleCalendar.Event>.create { observer -> Disposable in
var parameters: [String: Any] = [:]
if let nextPageToken = nextPageToken {
parameters["pageToken"] = nextPageToken
}
_ = self.oauthswift.client.get(GoogleCalendarAPI.events, parameters: parameters, success: { (data, response) in
if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: AnyObject] {
if let nextPageToken = self.nextPageToken(json: json) {
_ = Observable.of(Observable.from(self.getEventsFromJSON(json: json)), self.getEventsFromCalendars(calendars: calendars, nextPageToken: nextPageToken))
.merge().subscribe(observer)
} else {
_ = Observable.from(self.getEventsFromJSON(json: json))
}
} else {
observer.onError(CustomError.other)
}}, failure: { (error) in observer.onError(CustomError.noInet) }
)
return Disposables.create()
}
}

Use a debug() to check and ensure that you're getting a Completed event (without toArray()). toArray() will only emit an Array once the source sequence completes.

Well you can only subscribe once to an observable if it not shared (.share)
like
let sharedObservable = googleCalendarUseCase.getEventsFromCalendars(calendars: selectedCalendars).share()
sharedObservable.subscribe(onNext: { (event) in
print(event.summary)
}).addDisposableTo(disposeBag)
sharedObservable.toArray().subscribe(onNext: { (events) in
print(events.count)
}).addDisposableTo(disposeBag)

Related

Call next function after terminate the previous

I need to call func fillFields after that func getJsonData is over.
The func getJsonData is a Async Task for get data on server for URLRequest.
func getAPIData() {
let initial = URL(string: "http://10.0.0.2/Blower/app/api/inicial.php")
DispatchQueue.main.async {
_ = URLSession.shared.dataTask(with: initial!) { (dados, requisicao, erro) in
if requisicao != nil {}
if let dados = dados {
do {
let json = try JSONSerialization.jsonObject(with: dados, options: []) as! [String: Any]
/*
*
*/
} catch {
print(erro as Any)
}
}
}.resume()
}
}
How can I know if the function getAPIData is finished?
You can Identify with Completion handler when the task is complete like this.
func getAPIData(complition:#escaping (AnyObject?, Error?) -> Void) {
let initial = URL(string: "http://10.0.0.2/Blower/app/api/inicial.php")
DispatchQueue.main.async {
_ = URLSession.shared.dataTask(with: initial!) { (dados, requisicao, erro) in
if requisicao != nil {}
if let dados = dados {
do {
let json = try JSONSerialization.jsonObject(with: dados, options: []) as! [String: Any]
complition(json as AnyObject, nil) // When Complete task
// Call next function Here
} catch {
print(erro as Any)
complition(nil, erro)
}
} else {
complition(nil, erro)
}
}.resume()
}
}
Call like this
self.getAPIData { (response,error) in
print(response) // Your response is here after complete task
}

ios - Cannot get data from Alamofire return

I want to get the data from server api using Alamofire call. But after the api function executed, the data return is empty because Swift is asynchronous...
This code is call the server api:
func getAllModels(completion: #escaping (_ result: [String]?) -> ()) {
var _modelList:[String] = []
let url = BASE_URL + "getAllProductAndModelv2"
Alamofire.request(url, method:.get, parameters: [:], encoding: JSONEncoding.default).responseJSON { response in
switch response.result {
case .success:
if((response.result.value) != nil) {
let data = NSData(contentsOf: URL(string: url)!)
do {
if let data = data, let json = try JSONSerialization.jsonObject(with: data as Data) as? [String: Any], let models = json["models"] as? [[String:Any]] {
for model in models {
if let name = model["name"] as? String {
_modelList.append(name)
}
}
}
}catch {
print("error")
}
completion(_modelList)
}
case .failure(let error):
print(error)
completion(nil)
}
}
}
And this code is get data from getAllModels function:
var models:[VirtualObject] = []
RestApiManager().getAllModels(){ (result) -> () in
print("************************************************")
if let result = result {
var array = result as Array
for item in array {
print(item)
models.append(VirtualObject(url: URL(string: item)!)!)
}
}
print("************************************************")
}
print(models)
return models
I don't know how to use the callback function exactly to bind the data to return model.. Please help!
Use didSet observer of variables. And call api in viewDidload.
class ViewController: UIViewController {
var arrModals = [Any]() {
didSet {
print("this call when get all modals from server")
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
RestApiManager().getAllModels(){ (result) -> () in
var arrTempModals = [Any]()
if let result = result {
var array = result as Array
for item in array {
print(item)
arrTempModals.append(item)
}
}
self.arrModals = arrTempModals
}
}
}

Validate JSON response with Alamofire

Is it possible to have a Alamofire validator that gets the parsed JSON response, check a property and return true / false depending on that value?
I have an API that always returns 200 response codes, but the response has a success property.
I would like to check this property before the responseJSON callback is fired and only call responseJSON if success == true.
Is this possible with custom validators?
Found a solution I feel ok with. First I created extension methods that check for errors and extract the data I'm interested in. I have one success callback and one error callback.
import Foundation
import Alamofire
extension Request {
public func apiSuccess(
queue queue: dispatch_queue_t? = nil,
options: NSJSONReadingOptions = .AllowFragments,
completionHandler: [String:AnyObject] -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: Request.JSONResponseSerializer(options: options),
completionHandler: { response in
if let jsonValue = response.result.value as? [String:AnyObject] {
let success = jsonValue["success"] as! Bool
if (success) {
completionHandler(jsonValue["object"] as! [String:AnyObject])
}
}
}
)
}
public func apiError(
queue queue: dispatch_queue_t? = nil,
options: NSJSONReadingOptions = .AllowFragments,
completionHandler: [String] -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: Request.JSONResponseSerializer(options: options),
completionHandler: { response in
if let jsonValue = response.result.value as? [String:AnyObject] {
let success = jsonValue["success"] as! Bool
if (!success) {
let errorDict = jsonValue["errors"] as! [String:[String]]
var errors : [String] = []
errorDict.keys.forEach { key in
errors += errorDict[key] as [String]!
}
completionHandler(errors)
}
}
}
)
}
}
Then I can use it like this:
Alamofire.request(.POST, url,
parameters: parameters,
encoding: .JSON)
.apiSuccess { response in
print("Success Callback", response)
}
.apiError { errors in
print("Errors ", errors)
}
I don't think it is. Validator blocks don't receive the response data as arguments, only headers and such.

Return Bool in Alamofire closure

I use Swift 2 and Xcode 7.1.
I have a function who connect my users, but it will connect at my database with HTTP. I use Alamofire for execute this request. I want to know, from a view controller if the user is connected.
I have my function connect in a class. And i test connection in a ViewController.
Like this :
class user {
// ...
func connectUser(username: String, password: String){
let urlHost = "http://localhost:8888/project350705/web/app_dev.php/API/connect/"
let parametersSymfonyG = [
username, password
]
let url = UrlConstruct(urlHost: urlHost).setSymfonyParam(parametersSymfonyG).getUrl()
//var userArray = [String:AnyObject]()
Alamofire.request(.GET, url)
.responseString { response in
if let JSON = response.result.value {
var result = self.convertStringToDictionary(JSON)!
if result["status"] as! String == "success"{
let userArray = result["user"] as! [String:AnyObject]
userConnect = self.saveUser(userArray)
} else{
print("ERROR-CONNECTION :\n Status :\(result["status"]!)\n Code :\(result["code"]!)")
}
return ""
}
}
}
// ...
}
class MyViewController: UIViewController {
// ...
#IBAction func connect(sender: AnyObject?) {
// CONNECTION
User.connectUser(self.username.text!, password: self.password.text!)
// CHECK
if userConnect != nil {
print("connected")
}else{
print("NotConnected")
}
}
// ...
}
First solution : Return
To do so would require that my function returns a Boolean.
Only I can not use return.
Alamofire.request(.GET, url)
.responseString { response in
if let JSON = response.result.value {
var result = self.convertStringToDictionary(JSON)!
if result["status"] as! String == "success"{
let userArray = result["user"] as! [String:AnyObject]
userConnect = self.saveUser(userArray)
} else{
print("ERROR-CONNECTION :\n Status :\(result["status"]!)\n Code :\(result["code"]!)")
}
return "" // Unexpected non-void return value in void function
}
}
Second solution :
I can also test if the user has been logged, but before testing, I must wait for the function have finished loading.
users.connectUser(self.username.text!, password: self.password.text!)
// after
if userConnect != nil {
print("connected")
}else{
print("NotConnected")
}
I would prefer return a boolean. It will facilitate the processing.
Do you have a solution ?
I would suggest employing a completion handler in your connectUser method:
func connectUser(username: String, password: String, completion: #escaping (Bool) -> Void) {
// build the URL
// now perform request
Alamofire.request(url)
.responseString { response in
if let json = response.result.value, let result = self.convertStringToDictionary(json) {
completion(result["status"] as? String == "success")
} else {
completion(false)
}
}
}
You can then call it using:
users.connectUser(username.text!, password: password.text!) { success in
if success {
print("successful")
} else {
print("not successful")
}
}
// But don't use `success` here yet, because the above runs asynchronously
BTW, if your server is really generating JSON, you might use responseJSON rather than responseString, further streamlining the code and eliminating the need for convertStringToDictionary:
func connectUser(username: String, password: String, completion: #escaping (Bool) -> Void) {
// build the URL
// now perform request
Alamofire.request(url)
.responseJSON { response in
if let dictionary = response.result.value as? [String: Any], let status = dictionary["status"] as? String {
completion(status == "success")
} else {
completion(false)
}
}
}
If you've written your own server code to authenticate the user, just make sure you set the right header (because responseJSON not only does the JSON parsing for you, but as part of its validation process, it makes sure that the header specifies JSON body; it's good practice to set the header, regardless). For example in PHP, before you echo the JSON, set the header like so:
header("Content-Type: application/json");
The completion handler of your Alamofire.request method is asynchronous and it doesn't have a return type specified in its signature. Thats why you see an error when you provide a return statement in your completion handler closure.
You will have to split your request and response processing to separate methods and call the response processing method instead of using return statement.
Alamofire.request(.GET, url).responseString { response in
if let JSON = response.result.value {
var result = self.convertStringToDictionary(JSON)!
if result["status"] as! String == "success"{
let userArray = result["user"] as! [String:AnyObject]
userConnect = self.saveUser(userArray)
processSuccessResponse() //Pass any parameter if needed
} else{
print("ERROR-CONNECTION :\n Status :\(result["status"]!)\n Code :\(result["code"]!)")
processFailureResponse() //Pass any parameter if needed
}
}
}
func processSuccessResponse() {
//Process code for success
}
func processFailureResponse() {
//Process code for failure
}
My preferred way of doing this is to call a function in the completion handler. You can also set a boolean flag in order to check if the user is connected at any given time.
func connectUser(username: String, password: String, ref: MyClass) {
Alamofire.request(.GET, url)
.responseString { response in
var userIsConnected = false
if let JSON = response.result.value {
var result = self.convertStringToDictionary(JSON)!
if result["status"] as! String == "success"{
let userArray = result["user"] as! [String:AnyObject]
userConnect = self.saveUser(userArray)
userIsConnected = true
} else {
print("ERROR-CONNECTION :\n Status :\(result["status"]!)\n Code :\(result["code"]!)")
}
} else {
print("Response result nil")
}
ref.finishedConnecting(userIsConnected)
}
}
}
class MyClass {
var userIsConnected = false
func startConnecting() {
connectUser(username, password: password, ref: self)
}
func finishedConnecting(success: Bool) {
userIsConnected = success
... post-connection code here
}
}

Return a string from a web scraping function in swift

Ok so I am scraping some basic data of a web page. I wanted to refactor out my code to another class and return a string from what I retrieved but it is difficult with the asynchronous function and I'm new with swift.
I now realize that this function is incapable of returning a string but I can't quite figure out how to configure the completion handler and how to call the function after from the main class using the completion handler.
Any help would be greatly appreciated, thanks.
func getNameFromProfileUrl(profileUrl: NSURL) -> String {
var playerName = ""
let task = NSURLSession.sharedSession().dataTaskWithURL(profileUrl, completionHandler: { (data, response, error) -> Void in
if error == nil {
var urlContent = NSString(data: data, encoding: NSUTF8StringEncoding) as NSString!
var urlContentArray = urlContent.componentsSeparatedByString("<title>")
var statusArray = urlContentArray[1].componentsSeparatedByString("</title>")
playerName = statusArray[0] as! String
}
})
task.resume()
return playerName
}
Essentially, you'll want to provide a completion handler to this function from the main class that can handle just the return of the player name (or not). You'd change the function to not have a return value, but to accept a second parameter that is a completion handler:
func getNameFromProfileUrl(profileUrl: NSURL, completionHandler: (String?) -> Void) {
let task = NSURLSession.sharedSession().dataTaskWithURL(profileUrl, completionHandler: { (data, response, error) -> Void in
if error == nil {
var urlContent = NSString(data: data, encoding: NSUTF8StringEncoding) as NSString!
var urlContentArray = urlContent.componentsSeparatedByString("<title>")
var statusArray = urlContentArray[1].componentsSeparatedByString("</title>")
let playerName = statusArray[0] as? String
completionHandler(playerName)
} else {
completionHandler(nil)
}
})
task.resume()
}
From your main class, you'd then call it with something like this:
myWebScraper.getNameFromProfileUrl(profileURL) { playerName in
// always update UI from the main thread
NSOperationQueue.mainQueue().addOperationWithBlock {
if let playerName = playerName {
playerNameField.text = playerName
} else {
playerNameField.text = "Player Not Found"
}
}
}

Resources